ชุมชน Rust กำลังอภิปรายเกี่ยวกับไลบรารี่ mem-isolate
ที่เพิ่งเปิดตัว ซึ่งสัญญาว่าจะทำให้สามารถรันโค้ดที่ไม่ปลอดภัยได้อย่างปลอดภัยผ่านเทคนิคที่แยกการทำงานที่อาจเป็นอันตรายไว้ในโปรเซสที่แยกออกมา (forked processes) แม้ว่าแนวทางนี้จะชาญฉลาด แต่ผู้เชี่ยวชาญด้านความปลอดภัยและการเขียนโปรแกรมระบบได้แสดงความกังวลอย่างมากเกี่ยวกับข้อจำกัดและการใช้งานที่อาจผิดวัตถุประสงค์
mem-isolate
ทำงานโดยการประมวลผลฟังก์ชันในโปรเซสลูกที่แยกออกมา ซึ่งสร้างขึ้นผ่านคำสั่งระบบ POSIX fork()
แนวทางนี้สร้างสำเนาของหน่วยความจำของโปรเซสหลัก ทำให้การดำเนินการที่ไม่ปลอดภัยสามารถทำงานได้โดยไม่ส่งผลกระทบต่อพื้นที่หน่วยความจำของโปรเซสต้นฉบับ เมื่อฟังก์ชันทำงานเสร็จ ผลลัพธ์จะถูกแปลงเป็นข้อมูลอนุกรม (serialized) กลับไปยังโปรเซสหลักผ่านท่อ (pipe) และโปรเซสลูกจะถูกยกเลิก
ข้อจำกัดด้านความปลอดภัย
ผู้เชี่ยวชาญด้านความปลอดภัยในชุมชนได้ชี้ให้เห็นว่า mem-isolate
ไม่ได้ให้ระดับความปลอดภัยตามที่ชื่อบ่งบอก ไลบรารี่นี้ไม่เข้าเกณฑ์นิยามของโค้ดที่ปลอดภัยใน Rust ซึ่งต้องการการรับประกันความปลอดภัยของหน่วยความจำทั้งในเชิงพื้นที่ (spatial) และเวลา (temporal)
ผมไม่คิดว่านี่เข้าเกณฑ์นิยามของความปลอดภัยใน safe Rust: ความปลอดภัยไม่ได้หมายถึงแค่จะไม่เกิดการล่มเนื่องจากข้อผิดพลาดของหน่วยความจำเชิงพื้นที่เท่านั้น แต่หมายถึงโค้ดนั้นต้องมีความปลอดภัยของหน่วยความจำทั้งในเชิงพื้นที่และเวลาด้วย
จากมุมมองด้านความปลอดภัย การแยกที่จัดเตรียมไว้นั้นเป็นเพียงผิวเผิน โปรเซสที่ถูก fork ยังคงรักษาสำเนาสมบูรณ์ของสถานะโปรแกรม รวมถึงข้อมูลลับใดๆ ในหน่วยความจำ นั่นหมายความว่าโค้ดที่ไม่ปลอดภัยและสามารถถูกโจมตีได้ยังคงสามารถเข้าถึงข้อมูลที่ละเอียดอ่อนภายในสภาพแวดล้อมที่แยกออกมา นอกจากนี้ โปรเซสลูกยังคงมีสิทธิ์เดียวกันกับโปรเซสหลัก ดังนั้นช่องโหว่ในการประมวลผลโค้ดยังคงถูกโจมตีได้
ข้อจำกัดหลักของ mem-isolate:
- ทำงานได้เฉพาะบนระบบ POSIX (Linux, macOS, BSD)
- เพิ่มภาระการทำงานประมาณ 1.9 มิลลิวินาทีต่อการเรียกฟังก์ชัน (เทียบกับ 1.5 นาโนวินาทีสำหรับการเรียกโดยตรง)
- ต้องมีการแปลงข้อมูลที่ส่งคืนระหว่างโปรเซส
- อาจเป็นอันตรายในแอปพลิเคชันแบบมัลติเธรด
- ไม่สามารถป้องกันการโจมตีที่ไม่ทำให้เกิดการหยุดทำงานของระบบ
- โปรเซสลูกมีสิทธิ์และการเข้าถึงหน่วยความจำเหมือนกับโปรเซสหลัก
- อาจทำให้เกิดการติดตาย (deadlocks) หากมีการแยกโปรเซส (fork) ในขณะที่มิวเท็กซ์ถูกล็อค
ข้อกังวลทางเทคนิคเกี่ยวกับ Fork
ผู้เชี่ยวชาญด้านการเขียนโปรแกรมระบบได้เน้นย้ำว่า fork()
เป็น API ที่มีปัญหาสำหรับกรณีการใช้งานนี้ โดยเฉพาะในแอปพลิเคชันแบบมัลติเธรด เมื่อโปรเซสทำการ fork สถานะหน่วยความจำทั้งหมดจะถูกทำซ้ำ รวมถึงสถานะของ mutex และล็อค หากเธรดใดกำลังถือล็อคในช่วงเวลาที่ทำการ fork โปรเซสลูกอาจประสบปัญหา deadlock
แม้แต่การดำเนินการง่ายๆ เช่น การพิมพ์หรือการจัดสรรหน่วยความจำก็สามารถทำให้เกิดการค้างในโปรเซสที่ถูก fork บัฟเฟอร์ในพื้นที่ผู้ใช้ที่ไม่ได้ถูกล้าง (flush) ก่อนที่การเรียกกลับ (callback) จะเสร็จสิ้นจะสูญหายไป ปัญหาเหล่านี้ทำให้ mem-isolate
อาจเป็นอันตรายในแอปพลิเคชันที่ซับซ้อน
ผลกระทบต่อประสิทธิภาพ
ไลบรารี่นี้ทำให้เกิดค่าโสหุ้ยด้านประสิทธิภาพที่สำคัญ โดยการทดสอบประสิทธิภาพแสดงให้เห็นว่า execute_in_isolated_process()
ใช้เวลาประมาณ 1.9 มิลลิวินาที เทียบกับ 1.5 นาโนวินาทีสำหรับการเรียกฟังก์ชันโดยตรง – ช้ากว่าเดิมมากกว่าหนึ่งล้านเท่า แม้ว่าผู้เขียนจะยอมรับข้อจำกัดนี้ด้วยคำพูดติดตลกเกี่ยวกับการรันโค้ดช้าลง 1 มิลลิวินาที สมาชิกในชุมชนชี้ให้เห็นว่าค่าโสหุ้ยนี้ทำให้ไลบรารี่ไม่สามารถใช้งานได้จริงในหลายกรณี
นักพัฒนาหลายคนใช้โค้ดที่ไม่ปลอดภัยโดยเฉพาะสำหรับการปรับปรุงประสิทธิภาพ ดังนั้นการห่อหุ้มโค้ดดังกล่าวในกลไกที่ทำให้เกิดค่าโสหุ้ยที่สำคัญจึงทำลายวัตถุประสงค์เดิม ตามที่ผู้แสดงความคิดเห็นรายหนึ่งระบุ การใช้แนวทางนี้อย่างกว้างขวางอาจลดข้อได้เปรียบด้านประสิทธิภาพของ Rust เมื่อเทียบกับภาษาเช่น Python หรือ Ruby ที่พึ่งพาการ fork อย่างมากสำหรับการทำงานแบบขนาน
ผลการทดสอบประสิทธิภาพ:
- การเรียกฟังก์ชันโดยตรง: ~1.5 นาโนวินาที
- fork() + wait: ~1.7 มิลลิวินาที
- execute_in_isolated_process(): ~1.9 มิลลิวินาที
การประยุกต์ใช้งานจริง
แม้จะมีข้อจำกัด นักพัฒนาบางคนเห็นคุณค่าใน mem-isolate
สำหรับสถานการณ์เฉพาะ ไลบรารี่นี้อาจมีประโยชน์เมื่อทำงานกับโค้ดของบุคคลที่สามที่ไม่ปลอดภัยโดยธรรมชาติและไม่สามารถปรับปรุงได้ โดยเฉพาะอย่างยิ่งเมื่อเชื่อมต่อกับไลบรารี่ C ในกรณีเหล่านี้ การแยกที่จัดเตรียมไว้อาจเป็นการแลกเปลี่ยนที่ยอมรับได้สำหรับความปลอดภัยที่เพิ่มขึ้น โดยเฉพาะในเส้นทางที่ไม่สำคัญต่อประสิทธิภาพ
อย่างไรก็ตาม สำหรับแอปพลิเคชันส่วนใหญ่ ผู้เชี่ยวชาญแนะนำให้ใช้แนวทางการแยกที่แข็งแกร่งกว่า สถาปัตยกรรมแบบหลายโปรเซสที่ออกแบบอย่างเหมาะสมพร้อมช่องทาง IPC ที่จำกัดและ API การแซนด์บ็อกซ์ระดับระบบปฏิบัติการจะให้การรับประกันความปลอดภัยที่แข็งแกร่งกว่า เอนจินเบราว์เซอร์เช่น Chrome ใช้แนวทางนี้แทนการแยกโปรเซสอย่างง่าย
การอภิปรายเกี่ยวกับ mem-isolate
เน้นย้ำถึงความท้าทายที่ดำเนินอยู่ในการสร้างสมดุลระหว่างความปลอดภัย ประสิทธิภาพ และความสามารถในการใช้งานจริงในการเขียนโปรแกรมระบบ แม้ว่าแนวทางนวัตกรรมเพื่อความปลอดภัยของหน่วยความจำจะได้รับการต้อนรับในระบบนิเวศ Rust แต่จำเป็นต้องได้รับการประเมินอย่างรอบคอบเทียบกับแนวปฏิบัติที่ดีที่สุดและโมเดลความปลอดภัยที่ได้รับการยอมรับ
อ้างอิง: mem-isolate: Run unsafe code safely