การถกเถียงเรื่องความปลอดภัยในการเขียนโปรแกรม C ปะทุขึ้นจากการนำ "Parse, Don't Validate" มาใช้งาน

ทีมชุมชน BigGo
การถกเถียงเรื่องความปลอดภัยในการเขียนโปรแกรม C ปะทุขึ้นจากการนำ "Parse, Don't Validate" มาใช้งาน

บล็อกโพสต์ล่าสุดที่สนับสนุนแนวทาง parse, don't validate ในการเขียนโปรแกรม C ได้จุดประกายการอย่างเข้มข้นในหมู่นักพัฒนาเกี่ยวกับความปลอดภัยของหน่วยความจำ การกำหนดรูปแบบประเภทข้อมูล และความท้าทายในการนำไปใช้งานจริง เทคนิคนี้ซึ่งมีต้นกำเนิดจาก functional programming มีเป้าหมายเพื่อลดช่องโหว่ด้านความปลอดภัยโดยการแปลงข้อมูลที่ไม่น่าเชื่อถือให้เป็นประเภทข้อมูลเฉพาะเจาะจงครั้งเดียวที่ขอบเขตของระบบ แทนที่จะตรวจสอบความถูกต้องของข้อมูลดิบซ้ำแล้วซ้ำเล่าตลอดทั้งโค้ดเบส

แนวคิดหลักนี้เกี่ยวข้องกับการสร้างประเภทข้อมูลแบบกำหนดเองเช่น email_t และ name_t แทนที่จะใช้ตัวชี้ char* ทั่วไปทุกที่ แนวทางนี้สัญญาว่าจะขจัดข้อผิดพลาดทั้งหมดในหลายประเภทโดยให้คอมไพเลอร์บังคับใช้ความปลอดภัยของประเภทข้อมูลและป้องกันการสับสนพารามิเตอร์

แนวทางเทคนิคหลักที่กล่าวถึง:

  • การสร้างประเภทข้อมูลแบบกำหนดเองโดยใช้ opaque structs (email_t, name_t)
  • การตรวจสอบความถูกต้องเฉพาะที่ขอบเขตระบบ โดย char* ถูกจำกัดให้ใช้เฉพาะที่ขอบของระบบ
  • การจัดการหน่วยความจำผ่าน buffer ที่จัดสรรโดยผู้เรียกใช้
  • การใช้งาน newtype pattern เพื่อความปลอดภัยของประเภทข้อมูล
  • การจัดการข้อผิดพลาดผ่านการคืนค่า NULL pointer เทียบกับสถานะข้อผิดพลาดที่ฝังอยู่ภายใน

ความขัดแย้งเรื่องการตั้งชื่อแบ่งแยกชุมชน

การอภิปรายได้เน้นไปที่ความไม่เห็นด้วยพื้นฐานเกี่ยวกับการกำหนดรูปแบบการตั้งชื่อใน C อย่างรวดเร็ว การใช้คำต่อท้าย _t สำหรับประเภทข้อมูลแบบกำหนดเองในบทความต้นฉบับได้รับการวิพากษ์วิจารณ์อย่างรุนแรงจากนักพัฒนาบางคนที่โต้แย้งว่าสิ่งนี้ละเมิดมาตรฐานการตั้งชื่อที่สงวนไว้ นักวิจารณ์ชี้ให้เห็นว่าคำต่อท้าย _t ถูกสงวนไว้โดยมาตรฐาน POSIX และอาจนำไปสู่ความขัดแย้งในการตั้งชื่อในอนาคต

อย่างไรก็ตาม สมาชิกชุมชนคนอื่นๆ ได้โต้แย้งกลับต่อความกังวลนี้ พวกเขาโต้แย้งว่า POSIX และ C เป็นมาตรฐานที่แยกจากกัน และคำนำหน้าเนมสเปซสามารถป้องกันการชนกันได้อย่างง่ายดาย การถกเถียงนี้เผยให้เห็นความตึงเครียดที่ลึกซึ้งกว่าเกี่ยวกับมาตรฐานการเขียนโค้ดและว่าความขัดแย้งในอนาคตที่เป็นเพียงทฤษฎีควรกำหนดแนวปฏิบัติปัจจุบันหรือไม่

การตรวจสอบความเป็นจริงของการจัดการหน่วยความจำ

การแลกเปลี่ยนความคิดเห็นที่เข้มข้นที่สุดเน้นไปที่การอ้างสิทธิ์ของบทความเกี่ยวกับการป้องกันข้อผิดพลาด double-free โพสต์ต้นฉบับแนะนำว่าการตั้งค่าตัวชี้เป็น NULL หลังจากปลดปล่อยหน่วยความจำจะแก้ไขช่องโหว่ C ทั่วไปนี้ได้ การตอบสนองของชุมชนเป็นไปอย่างรวดเร็วและวิพากษ์วิจารณ์

การอ้างสิทธิ์เรื่องการป้องกัน double free ทั้งหมดเป็นเรื่องเท็จอย่างสิ้นเชิง การตั้งค่าตัวแปรเป็น NULL ใช้ได้เฉพาะกรณีที่มีเจ้าของเพียงคนเดียวที่ชัดเจน ซึ่งไม่ใช่สถานการณ์ที่ double free มักจะเกิดขึ้นตั้งแต่แรก

การวิพากษ์วิจารณ์นี้เน้นย้ำถึงความท้าทายพื้นฐานในการเขียนโปรแกรม C: ตัวชี้หลายตัวสามารถอ้างอิงไปยังตำแหน่งหน่วยความจำเดียวกันได้ และการตั้งค่าตัวหนึ่งเป็น NULL ไม่ส่งผลต่อตัวอื่นๆ ฉันทามติของชุมชนแนะนำว่าความปลอดภัยของหน่วยความจำที่แท้จริงต้องการการออกแบบความเป็นเจ้าของอย่างระมัดระวัง ซึ่งเป็นสิ่งที่ C ต่อสู้ด้วยโดยธรรมชาติ

ข้อจำกัดที่ระบุโดยชุมชน:

  • การใช้ส่วนต่อท้าย _t ขัดแย้งกับแนวทางการตั้งชื่อที่สงวนไว้ของ POSIX
  • การอ้างว่าป้องกันการ free หน่วยความจำซ้ำทำงานได้เฉพาะในสถานการณ์ที่มีเจ้าของเพียงรายเดียว
  • ความท้าทายด้านการใช้งานจริงเมื่อต้องแปลงประเภทข้อมูลกลับเป็นสตริง
  • ข้อจำกัดของระบบประเภทข้อมูลของ C ที่ต้องการการตรวจสอบข้อผิดพลาดด้วยตนเอง
  • ความซับซ้อนของการจัดสรรหน่วยความจำสำหรับฟังก์ชันแปลงสตริง

อุปสรรคในการนำไปใช้งานจริง

นักพัฒนายังแสดงความกังวลเกี่ยวกับความท้าทายในทางปฏิบัติของการนำแนวทางนี้ไปใช้ ปัญหาหลักเน้นไปที่วิธีการใช้ประเภทข้อมูลแบบกำหนดเองเหล่านี้จริงๆ เมื่อสร้างขึ้นแล้ว การแปลง email_t กลับเป็นสตริงที่พิมพ์ได้ต้องการฟังก์ชันเพิ่มเติมและการตัดสินใจจัดการหน่วยความจำอย่างระมัดระวัง

บางคนแนะนำว่าผู้เรียกควรจัดสรรหน่วยความจำสำหรับการแปลงสตริง คล้ายกับวิธีที่ strncpy ทำงาน คนอื่นๆ เสนอให้ทำประเภทข้อมูลแบบห่อหุ้มให้โปร่งใสน้อยลงเพื่อรักษาความสามารถในการใช้งานในขณะที่ยังคงได้รับประโยชน์จากความปลอดภัยของประเภทข้อมูล การอภิปรายเผยให้เห็นความตึงเครียดอย่างต่อเนื่องระหว่างความปลอดภัยและความเป็นจริงในการเขียนโปรแกรม C

ประโยชน์ด้านความปลอดภัยหลักที่อ้างว่ามี:

  • ความปลอดภัยของประเภทข้อมูลที่บังคับใช้โดย compiler เพื่อป้องกันการสลับพารามิเตอร์
  • ลดพื้นผิวการโจมตีโดยการกำจัดข้อมูลนำเข้าที่ไม่ได้รับการตรวจสอบในฟังก์ชันหลัก
  • การห่อหุ้ม raw char* strings ณ ขอบเขตของระบบ
  • การแยกวิเคราะห์จุดเดียวที่กำจัดการตรวจสอบความถูกต้องซ้ำซ้อนตลอดทั้ง codebase

ข้อจำกัดของระบบประเภทข้อมูลถูกเปิดเผย

การถกเถียงทางเทคนิคเฉพาะเจาะจงเกิดขึ้นรอบการจัดการข้อผิดพลาดในแนวทางการแปลง นักวิจารณ์สังเกตว่าการส่งคืนทั้งอีเมลที่ถูกต้องและข้อผิดพลาดในการแปลงจากฟังก์ชันเดียวกันละเมิดหลักการหลักของ parse, don't validate หาก email_t สามารถแสดงทั้งสถานะที่ถูกต้องและไม่ถูกต้อง นักพัฒนายังคงต้องจำที่จะตรวจสอบข้อผิดพลาด - ซึ่งโดยพื้นฐานแล้วนำปัญหาการตรวจสอบความถูกต้องที่แนวทางนี้ตั้งใจจะแก้ไขกลับมา

การวิพากษ์วิจารณ์นี้โจมตีหัวใจของข้อจำกัดระบบประเภทข้อมูลของ C ซึ่งแตกต่างจากภาษาที่มีกลไกการจัดการข้อผิดพลาดที่ซับซ้อน โปรแกรมเมอร์ C มักจะใช้ค่า sentinel หรือรหัสส่งคืนพิเศษ ซึ่งสามารถทำลายประโยชน์ด้านความปลอดภัยของประเภทข้อมูลที่แนวทางนี้สัญญาไว้

การถกเถียงแสดงให้เห็นความท้าทายที่กว้างขึ้นที่นักพัฒนา C เผชิญที่ต้องการเขียนโค้ดที่ปลอดภัยกว่าในขณะที่ทำงานภายในข้อจำกัดของภาษา แม้ว่าแนวคิด parse, don't validate จะให้ประโยชน์ในทางทฤษฎี แต่การนำไปใช้อย่างมีประสิทธิภาพใน C ต้องการการพิจารณาอย่างระมัดระวังต่อข้อจำกัดพื้นฐานของภาษาและการแลกเปลี่ยนในทางปฏิบัติ

อ้างอิง: parse, don't validate aka some c safety tips