- โปรเจกต์ cURL หลังจากเคยถอด
strncpy() ออกไปก่อนหน้านี้ ตอนนี้ได้ สั่งห้าม strcpy() ในโค้ดเบสทั้งหมด แล้ว
- แม้
strcpy() จะมี API ที่เรียบง่าย แต่ก็มี ความเสี่ยงที่การตรวจสอบขนาดบัฟเฟอร์จะแยกออกจากกัน ทำให้ไม่ปลอดภัยสำหรับการบำรุงรักษาระยะยาว
- เพื่อใช้แทน จึงมีการนำฟังก์ชันใหม่ชื่อ
curlx_strcopy() เข้ามา โดยรับทั้งขนาดบัฟเฟอร์ปลายทางและความยาวสตริงเป็นอาร์กิวเมนต์ เพื่อ ตรวจสอบก่อนว่าสามารถคัดลอกได้หรือไม่แล้วจึงทำงาน
- ฟังก์ชันนี้ใช้
memcpy() ภายใน และ รับประกันการจัดการอักขระปิดท้ายแบบ null
- การเปลี่ยนแปลงนี้ช่วยเพิ่มทั้ง ความปลอดภัยและความสม่ำเสมอของโค้ด รวมถึงช่วยลดปัญหาที่ AI สร้างรายงานช่องโหว่ผิดพลาดได้
เบื้องหลังการถอด strcpy
- cURL เคยลบการเรียก
strncpy() ทั้งหมดมาก่อนแล้ว โดยชี้ปัญหาของฟังก์ชันนี้ว่าเป็น API ที่ไม่เป็นธรรมชาติ รับประกันการปิดท้ายด้วย null ไม่ได้ และมีการเติม 0 เกินจำเป็น
- ในกรณีที่ต้องคัดลอกบางส่วนของสตริง ได้เปลี่ยนไปใช้
memcpy() และจัดการการปิดท้ายด้วย null เอง
- แม้
strcpy() จะมี API ที่เรียบง่าย แต่เพราะ ไม่ได้ระบุขนาดบัฟเฟอร์อย่างชัดเจน จึงมีความเสี่ยงที่ระหว่างการบำรุงรักษา โค้ดตรวจสอบกับโค้ดคัดลอกจะถูกแยกจากกัน
- หากโค้ดถูกแก้ไขต่อเนื่องเป็นเวลาหลายสิบปีโดยนักพัฒนาหลายคน ก็มี โอกาสที่การตรวจสอบขนาดบัฟเฟอร์จะถูกทำให้ไร้ผล
การนำฟังก์ชันคัดลอกสตริงแบบใหม่มาใช้
- เพื่อป้องกันความเสี่ยงนี้ จึงมีการนำฟังก์ชันทดแทนชื่อ
curlx_strcopy() มาใช้
- รับอาร์กิวเมนต์เป็น บัฟเฟอร์ปลายทาง ขนาดบัฟเฟอร์ บัฟเฟอร์ต้นทาง และความยาวสตริงต้นทาง
- จะทำงานด้วย
memcpy() ก็ต่อเมื่อสามารถคัดลอกและปิดท้ายด้วย null ได้ครบถ้วนเท่านั้น
- หากล้มเหลว จะตั้งค่าบัฟเฟอร์ปลายทางให้เป็นสตริงว่าง
- ฟังก์ชันนี้ต้องใช้ อาร์กิวเมนต์และโค้ดมากกว่า
strcpy() แต่ช่วยให้ การตรวจสอบบัฟเฟอร์ผูกอยู่ใกล้กับการคัดลอกโดยตรง จึงปลอดภัยกว่า
- ในโค้ดเบสของ cURL จึงมีการสั่งห้ามการใช้
strcpy() อย่างสมบูรณ์ และถอดออกเช่นเดียวกับ strncpy()
รายละเอียดการติดตั้งใช้งาน
- ตัวอย่างนิยามฟังก์ชันมีดังนี้
void curlx_strcopy(char *dest, size_t dsize, const char *src, size_t slen)
{
DEBUGASSERT(slen < dsize);
if(slen < dsize) {
memcpy(dest, src, slen);
dest[slen] = 0;
}
else if(dsize)
dest[0] = 0;
}
- ใช้
DEBUGASSERT เพื่อ ตรวจจับข้อผิดพลาดได้ตั้งแต่ระหว่างพัฒนา และออกแบบให้ในสภาพแวดล้อมที่ใช้งานจริงทำงานสำเร็จเสมอ
- ไม่มีค่าที่ส่งกลับเหมือน
strcpy และเลือกใช้ แนวทางจับข้อผิดพลาดในขั้นตอนทดสอบและการทำ fuzzing
ปฏิกิริยาจากชุมชน
- นักพัฒนาบางส่วนชี้ว่ามันคล้ายกับ
strcpy_s() (C11 Annex K) แต่ cURL ยังคงใช้มาตรฐาน C89 อยู่
- ความเห็นอื่น ๆ เสนอว่า ควรเพิ่มค่าที่ส่งกลับ หรือ ปรับปรุงวิธีจัดการเมื่อบัฟเฟอร์ล้มเหลว
- ทาง cURL อธิบายว่า “ฟังก์ชันนี้ถูกออกแบบให้สำเร็จเสมอ จึงไม่จำเป็นต้องมีค่าที่ส่งกลับ”
ผลข้างเคียงที่เกี่ยวกับ AI
- การเปลี่ยนแปลงครั้งนี้ช่วยป้องกัน ปัญหาที่แชตบอต AI ตรวจพบการใช้ strcpy ในโค้ดของ cURL ผิดพลาด แล้วอ้างว่า ‘มีช่องโหว่’ ได้
- อย่างไรก็ดี ผู้เขียนก็ระบุว่า “AI ยังอาจสร้างรายงานเท็จแบบอื่นได้อยู่ดี” พร้อมกล่าวถึง ข้อจำกัดของการวิเคราะห์โค้ดด้วย AI
5 ความคิดเห็น
ควรใช้
snprintfแทนstrcpyถ้าในโค้ดยังมีstrcpyอยู่ ก็ควรตามหาที่อยู่ของนักพัฒนาที่เขียนมันขึ้นมานี่เป็นวิธีที่ผมเคยใช้ทำงานเป็นโค้ดดีบักตอนทำงานอยู่บริษัทเกมเมื่อ 25 ปีก่อน แต่จะมีแค่
strcpyตัวเดียวเสียเมื่อไหร่กันล่ะ พอเป็นรีลีสก็ปลดกลับเพื่อเพิ่มความเร็วแล้วเอาไปให้บริการจริง ฝั่งเกมจริง ๆ ไวต่อปัญหาหน่วยความจำชนกันมากที่สุด เลยต้องทำงานกันอย่างระมัดระวังและมีสติสุด ๆ จนถึงขั้นทำเมมโมรีดีบักเกอร์ใช้กันเอง แต่พอมามองในวันนี้ ที่ทำอยู่นั่นมันเท่ากับกำลังสร้าง garbage collection อยู่เลย ช่างเป็นความทรงจำที่ชวนให้หวนคิดจริง ๆข้อผิดพลาด C4996 'strcpy' : ฟังก์ชันหรือตัวแปรนี้อาจไม่ปลอดภัย พิจารณาใช้
strcpy_sแทน หากต้องการปิดการเลิกใช้งาน ให้ใช้_CRT_SECURE_NO_WARNINGSดูความช่วยเหลือออนไลน์สำหรับรายละเอียดความคิดเห็นจาก Hacker News
strcpy()ไม่ดีแค่ในด้านความปลอดภัย แต่ยังแย่ในแง่ของ ประสิทธิภาพ ด้วยเมื่อก่อนเคยคิดว่าเวลาที่ไม่รู้ความยาวสตริง
strcpy()จะมีประสิทธิภาพ แต่ในความเป็นจริงมันคัดลอกทีละไบต์ ทำให้ CPU ต้องคาดเดาการแตกแขนง และนี่ไม่มีประสิทธิภาพstrcpyใช้ scalar loop เลย ไม่แน่ใจว่าเป็นแบบนั้นเฉพาะบนสถาปัตยกรรม ARM หรือเปล่ารูทีนจัดการสตริงของ C แต่ละตัวต่างก็มี ข้อจำกัดใหญ่ จนรู้สึกว่าใช้งานจริงไม่ค่อยได้
เลยคิดว่าจำเป็นต้องมีไลบรารีที่เก็บ ขนาดหน่วยความจำที่จัดสรรไว้ ควบคู่กับตัวชี้สตริง
ตัวอย่างที่น่าดูคือ bstring library
strncpyถูกสร้างขึ้นมาเพื่อคัดลอกชื่อไฟล์ที่มีความยาวคงที่ ดูรายละเอียดได้ใน คำตอบบน StackOverflow นี้strncpyเป็นฟังก์ชันสำหรับจัดการ ฟิลด์สตริงความกว้างคงที่ เช่นใช้เติม NUL ลงในฟิลด์อย่างchar username[20]ดูเอกสารที่เกี่ยวข้องได้ใน string_copying.7 manualน่าแปลกที่
curlx_strcopyไม่คืนค่าว่าทำสำเร็จหรือไม่แม้จะตรวจ
dest[0]ได้ แต่แบบนั้น เสี่ยงต่อการทำให้เกิดข้อผิดพลาด และไม่ตรงไปตรงมาDEBUGASSERT(slen < dsize);ผ่าน แต่ใน release build นั้น assert อาจถูกตัดออก รหัสข้อผิดพลาดแบบชัดเจนน่าจะดีกว่าเดิมที
strncpy()ไม่ได้มีไว้สำหรับ null-terminated strings แต่สำหรับฟิลด์ความยาวคงที่ปัญหาเริ่มจาก static analyzer แนะนำให้ใช้
strncpyแทนstrcpyทั้งที่ทางเลือกจริงคือsnprintfหรือstrlcpystrlcpyเป็นฟังก์ชันสาย BSD จึงไม่อยู่ใน POSIX คำแนะนำอย่างเป็นทางการคือstpecpyแต่แทบไม่มี implementation จริง ดู เอกสารที่เกี่ยวข้องstrncpyต้อง padding หลัง NUL ก็เพื่อให้เปรียบเทียบ ฟิลด์ชื่อความยาวคงที่ อย่าง directory entry ได้อย่างมีประสิทธิภาพ ซึ่งในเอกสารประกอบมาตรฐาน ANSI C ก็ระบุไว้แบบนั้นAPI นี้ให้ความรู้สึกเหมือน Annex-K มาก ขนาดของบัฟเฟอร์ปลายทางนับรวมพื้นที่สำหรับ NUL แต่ขนาดของต้นทางกลับไม่นับรวม
คิดว่าเขียน
memcpyใช้เองตรง ๆ ยังดีกว่าในบทความมีประโยคที่น่าประทับใจว่า “
strcpyคือ เหยื่อล่อให้ AI สร้างรายงานช่องโหว่ผิด ๆ”strcpyมีปัญหา แต่ยังสร้าง คำอธิบายพิสูจน์ซับซ้อนที่มีตรรกะผิดพลาด ทำให้ผู้ดูแลโครงการต้องเสียเวลาไล่ตรวจสอบหลักการที่ว่า “ตรวจสอบใกล้จุดใช้งานของโค้ด” เป็นสิ่งที่ดี แต่จะกำกวมเมื่อจำเป็นต้อง ตรวจสอบตั้งแต่ช่วงต้นของวงจรชีวิตข้อมูล
น่าจะดีถ้ามีวิธีแยกข้อมูลที่ “ผ่านการตรวจสอบแล้ว” ด้วยชนิดข้อมูลแบบเดียวกับ
Resultของ RustResultเก็บเพียงความสำเร็จ/ล้มเหลว ไม่ได้ รับประกันสถานะว่าผ่านการตรวจสอบแล้ว ดังนั้นควรมีชนิดข้อมูลแยกต่างหากที่สร้างได้ก็ต่อเมื่อผ่านกระบวนการตรวจสอบเท่านั้น นี่คือแนวคิด “** parse, don’t validate**”ความต่างแบบ off-by-one ระหว่างขนาดบัฟเฟอร์กับความยาวสตริงเป็นปัญหาด้านการใช้งานที่เลวร้ายมาก และมีแนวโน้มจะทำให้เกิดข้อผิดพลาดต่อไปอีก
ฟังก์ชันคัดลอกสตริงที่เสนอขึ้นมาใหม่ หากคัดลอกไม่ได้จะ ล้างบัฟเฟอร์ปลายทางให้ว่าง แล้วคืนค่า
voidแต่คิดว่าในกรณีแบบนี้ควร จัดการเป็น error และไม่แตะต้องบัฟเฟอร์เลยจะดีกว่า การพึ่ง
DEBUGASSERTอย่างเดียวไม่น่าไว้วางใจขอแสดงความยินดีกับการทำโปรเจกต์นี้สำเร็จ C/C++ เองก็สามารถทำให้มี ความปลอดภัยของหน่วยความจำ ได้ถ้าพยายามมากพอ
แต่ในสภาพแวดล้อมมือถือ ขนาดฟอนต์ของกราฟ เล็กเกินไปจนอ่านยาก
strcpyออก ไม่ได้แปลว่าโค้ดจะ ปลอดภัยด้านหน่วยความจำ เสมอไปย้ายไปใช้ภาษา C3 เลยก็ดีนะครับ เป็นโปรเจ็กต์ที่คงไวยากรณ์ของภาษา C ไว้โดยเปลี่ยนแปลงให้น้อยที่สุด พร้อมเพิ่มฟีเจอร์สมัยใหม่เข้าไป ทำให้ย้ายไปใช้ได้ง่ายด้วยครับ