ข้อเรียกร้องเรื่องความปลอดภัยของหน่วยความจำในภาษา Go ถูกโจมตีหลังพบ Data Races ทำให้เกิด Undefined Behavior

ทีมชุมชน BigGo
ข้อเรียกร้องเรื่องความปลอดภัยของหน่วยความจำในภาษา Go ถูกโจมตีหลังพบ Data Races ทำให้เกิด Undefined Behavior

ชุมชนโปรแกรมเมอร์กำลังมีการถกเถียงกันอย่างเข้มข้นเกี่ยวกับว่าภาษา Go สามารถถือได้ว่าเป็นภาษาที่มีความปลอดภัยของหน่วยความจำจริงหรือไม่ การอภิปรายนี้มีจุดศูนย์กลางอยู่ที่คำถามพื้นฐาน: ภาษาหนึ่งสามารถอ้างว่ามีความปลอดภัยของหน่วยความจำได้หรือไม่ เมื่อการเขียนโปรแกรมแบบ concurrent สามารถนำไปสู่ undefined behavior และช่องโหว่ด้านความปลอดภัยที่อาจเกิดขึ้นได้

ความขัดแย้งนี้เกิดจากแนวทางของ Go ในการจัดการกับโครงสร้างข้อมูลแบบ multi-word เช่น interfaces และ slices ในสภาพแวดล้อมแบบ concurrent ซึ่งแตกต่างจากภาษาอื่นๆ เช่น Java หรือ C# ที่ลงทุนอย่างหนักในการรับประกันว่าแม้แต่โปรแกรมที่มี data races ยังคงมีการกำหนดไว้อย่างชัดเจน Go กลับอนุญาตให้เงื่อนไข race บางอย่างทำลายการรับประกันความปลอดภัยพื้นฐานของภาษา

ปัญหาทางเทคนิค

เมื่อ goroutines หลายตัวเข้าถึง interface types หรือ slices ของ Go พร้อมกันโดยไม่มีการซิงโครไนซ์ที่เหมาะสม runtime อาจประสบกับ torn reads ซึ่งเป็นสถานการณ์ที่ thread อ่านข้อมูลที่ถูกอัปเดตเพียงบางส่วน สิ่งนี้เกิดขึ้นเพราะโครงสร้างข้อมูลเหล่านี้ถูกนำมาใช้เป็นค่า multi-word ที่ไม่สามารถอัปเดตแบบ atomic บนโปรเซสเซอร์ส่วนใหญ่

ปัญหานี้จะปรากฏเมื่อ goroutine หนึ่งอัปเดตตัวแปร interface ในขณะที่อีกตัวหนึ่งกำลังอ่านมัน goroutine ที่อ่านอาจสังเกตเห็นข้อมูลเก่าและใหม่ปนกัน ซึ่งอาจทำให้ถือว่าค่า integer เป็น memory pointer สิ่งนี้สามารถนำไปสู่ segmentation faults เมื่อโปรแกรมพยายาม dereference memory addresses ที่ไม่ถูกต้อง

โครงสร้างข้อมูลที่มีปัญหาของ Go

  • Interface types: เก็บข้อมูลในรูปแบบคู่ pointer (ข้อมูล + vtable) ที่ไม่สามารถอัปเดตแบบ atomic ได้
  • Slices: โครงสร้างแบบ multi-word ที่ประกอบด้วย data pointer, length และ capacity
  • Maps: ออกแบบมาให้ไม่ปลอดภัยสำหรับ thread อาจทำให้เกิดการเสียหายของข้อมูลเมื่อมีการเข้าถึงพร้อมกัน
  • Channels: ปลอดภัยสำหรับกรณีการใช้งานที่ออกแบบมา แต่ไม่สามารถป้องกันการแชร์ข้อมูลที่ไม่ปลอดภัยได้

การตอบสนองของชุมชนและผลกระทบต่ออุตสาหกรรม

ชุมชนโปรแกรมเมอร์มีปฏิกิริยาที่หลากหลายต่อการค้นพบเหล่านี้ นักพัฒนาบางคนโต้แย้งว่าสิ่งนี้แสดงถึงช่องว่างที่สำคัญระหว่างความปลอดภัยที่ Go โฆษณาและการรับประกันที่แท้จริง คนอื่นๆ โต้แย้งว่าสถานการณ์เช่นนี้หายากในทางปฏิบัติ และ race detector ในตัวของ Go ช่วยระบุปัญหาเหล่านี้ระหว่างการพัฒนา

ทุกครั้งที่มีการสนทนาเรื่องนี้ ฉันนึกถึงทีมของฉันที่ Dropbox ที่การแนะนำ segfault ใน Go server ของเราโดยไม่ซิงโครไนซ์การเขียนไปยังโครงสร้างข้อมูลเป็นพิธีกรรมสำหรับวิศวกรใหม่

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

ภูมิทัศน์ความปลอดภัยของหน่วยความจำในวงกว้าง

การอภิปรายนี้เน้นให้เห็นความไม่เห็นด้วยพื้นฐานเกี่ยวกับคำศัพท์ในโลกการเขียนโปรแกรม ผู้เชี่ยวชาญด้านความปลอดภัยมักจะกำหนดความปลอดภัยของหน่วยความจำตามศักยภาพในการใช้ประโยชน์ในทางปฏิบัติ ในขณะที่นักทฤษฎีภาษาโปรแกรมมุ่งเน้นไปที่การรับประกันอย่างเป็นทางการและการป้องกัน undefined behavior

ภาษาเช่น Rust และ Swift ใช้แนวทางที่แตกต่างกัน โดยใช้ระบบ type ที่ซับซ้อนเพื่อป้องกัน data races ทั้งหมด Java และ C# ลงทุนใน memory models ที่รับประกันว่าแม้แต่โปรแกรมที่มี race ยังคงมีการกำหนดไว้อย่างชัดเจน แม้ว่าอาจไม่ถูกต้องในตรรกะของมัน

การเปรียบเทียบความปลอดภัยของหน่วยความจำตามภาษาโปรแกรม

ภาษา โมเดลหน่วยความจำ การจัดการ Data Race ความปลอดภัยอย่างเป็นทางการ
Go พื้นฐาน อาจทำให้เกิด UB จำกัด
Java JMM (Java Memory Model) พฤติกรรมที่กำหนดไว้อย่างชัดเจน แข็งแกร่ง
C CLR Memory Model พฤติกรรมที่กำหนดไว้อย่างชัดเจน แข็งแกร่ง
Rust Ownership + Send/Sync ป้องกันในขณะ Compile แข็งแกร่ง
Swift Strict Concurrency ป้องกันในขณะ Compile แข็งแกร่ง
JavaScript Single-threaded + Web Workers การส่งผ่านข้อความ แข็งแกร่ง

บทสรุป

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

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

อ้างอิง: There is no memory safety without thread safety