- ความปลอดภัยของหน่วยความจำ และ แซนด์บ็อกซ์ เป็นแนวคิดด้านความปลอดภัยที่แยกจากกัน และต้องมีทั้งสองอย่างจึงจะสร้างระบบป้องกันที่แข็งแกร่งได้
- Fil-C เป็น อิมพลีเมนเทชันที่ทำให้ C/C++ ปลอดภัยด้านหน่วยความจำ โดยรับประกันความปลอดภัยไปถึงระดับ system call ของ Linux และสามารถใช้ได้แม้กับองค์ประกอบของระบบอย่าง OpenSSH
- ในกระบวนการพอร์ต แซนด์บ็อกซ์แบบ seccomp-BPF ของ OpenSSH มายัง Fil-C ประเด็นสำคัญคือข้อจำกัดในการสร้างเธรดและการปรับแต่งตัวกรอง seccomp
- เพื่อจัดการ เธรดเบื้องหลังของรันไทม์ ของ Fil-C จึงมีการเพิ่ม API
zlock_runtime_threads() เพื่อควบคุมการทำงานของเธรดภายในแซนด์บ็อกซ์
- Fil-C ออกแบบให้ใช้การเรียก
prctl แบบซิงโครไนซ์กับเธรดรันไทม์ทั้งหมด เพื่อให้ no_new_privs และ ตัวกรอง seccomp ถูกบังคับใช้อย่างสอดคล้องกันกับทั้งโปรเซส
ความสัมพันธ์ระหว่างความปลอดภัยของหน่วยความจำกับแซนด์บ็อกซ์
- ความปลอดภัยของหน่วยความจำและแซนด์บ็อกซ์เป็นคนละชั้นของความปลอดภัย ซึ่งมีเพียงอย่างเดียวไม่สามารถให้การป้องกันที่สมบูรณ์ได้
- ตัวอย่างที่ปลอดภัยด้านหน่วยความจำแต่ไม่ถูกแซนด์บ็อกซ์: โปรแกรม Java ที่สามารถเขียนทับไฟล์ผ่านข้อมูลที่ผู้ใช้ป้อนเข้ามาได้
- ตัวอย่างที่ถูกแซนด์บ็อกซ์แต่ไม่ปลอดภัยด้านหน่วยความจำ: โปรแกรมที่เขียนด้วยแอสเซมบลีและถูกจำกัดสิทธิ์
- แซนด์บ็อกซ์จริงยังมีช่องโหว่เชิงโครงสร้างอยู่ เช่น การอนุญาตให้สื่อสารกับโปรเซสนายหน้า
- ดังนั้น การใช้ทั้งความปลอดภัยของหน่วยความจำและแซนด์บ็อกซ์ร่วมกัน จึงเป็นแนวทางป้องกันที่ดีที่สุด
การผสาน Fil-C กับแซนด์บ็อกซ์ของ OpenSSH
- Fil-C เป็น อิมพลีเมนเทชันที่ทำให้ C/C++ ปลอดภัยด้านหน่วยความจำ และคงความปลอดภัยไว้ได้ถึงระดับ system call ของ Linux
- รันไทม์ของ Fil-C สามารถทำงานได้แม้ใน องค์ประกอบระบบระดับล่าง อย่าง
init, udevd
- OpenSSH ทำงานบน Fil-C ได้ตามปกติ และใช้ แซนด์บ็อกซ์ seccomp-BPF
- บน Linux นั้น OpenSSH สร้างแซนด์บ็อกซ์ด้วยเครื่องมือต่อไปนี้
- จำกัดการเข้าถึงระบบไฟล์ด้วย
chroot
- รันด้วยผู้ใช้/กลุ่ม
sshd
- จำกัดการเปิดไฟล์และการสร้างโปรเซสด้วย
setrlimit
- อนุญาตเฉพาะ system call ที่กำหนดด้วย ตัวกรอง seccomp-BPF
- Fil-C รองรับ
chroot และการสลับผู้ใช้โดยพื้นฐานอยู่แล้ว แต่ setrlimit และ seccomp-BPF อาจขัดกับการทำงานของรันไทม์ จึงต้องมีการปรับเพิ่มเติม
การควบคุมเธรดของรันไทม์ Fil-C
- รันไทม์ของ Fil-C ใช้ เธรดเบื้องหลังสำหรับ garbage collection และจะหยุดหรือเริ่มใหม่โดยอัตโนมัติเมื่อจำเป็น
- แซนด์บ็อกซ์
setrlimit ของ OpenSSH มีเป้าหมายเพื่อ ห้ามสร้างโปรเซสใหม่ ดังนั้นการสร้างเธรดของรันไทม์อาจละเมิดเงื่อนไขนี้ได้
- เพื่อแก้ปัญหานี้ จึงเพิ่ม API
zlock_runtime_threads() ใน <stdfil.h>
- ให้รันไทม์สร้างเธรดที่จำเป็นทั้งหมดทันที และปิดการหยุดทำงานอัตโนมัติหลังจากนั้น
- เรียกใช้ในฟังก์ชัน
ssh_sandbox_child ของ OpenSSH ก่อนเรียก setrlimit หรือ seccomp-BPF
การปรับตัวกรอง seccomp ของ OpenSSH
- หลังใช้
zlock_runtime_threads() แล้ว ฟังก์ชันส่วนใหญ่ของแซนด์บ็อกซ์ยังทำงานได้ตามเดิม
- มีการเปลี่ยนแปลงต่อไปนี้ในตัวกรอง seccomp
- เมื่อละเมิดให้ตั้งค่าเป็น
SECCOMP_RET_KILL_PROCESS เพื่อให้ เธรดเบื้องหลังของ Fil-C ถูกปิดพร้อมกันด้วย
- เพิ่ม
MAP_NORESERVE เข้าในรายการที่อนุญาต เพื่อรองรับการใช้งานตัวจัดสรรหน่วยความจำของ Fil-C
- อนุญาตการเรียก
sched_yield ซึ่งถูกใช้ใน อิมพลีเมนเทชันล็อก ของ Fil-C
- การเรียก
futex สำหรับการซิงโครไนซ์ของ Fil-C ได้รับอนุญาตอยู่แล้ว จึงไม่ต้องแก้ไขเพิ่มเติม
วิธีการอิมพลีเมนต์ prctl ของ Fil-C
- OpenSSH ใช้การเรียก
prctl สองแบบเมื่อติดตั้งตัวกรอง seccomp
PR_SET_NO_NEW_PRIVS เพื่อ ป้องกันการได้สิทธิ์เพิ่มเติม
PR_SET_SECCOMP, SECCOMP_MODE_FILTER เพื่อ เปิดใช้งานตัวกรอง
- ปัญหาคือ
prctl มีผล เฉพาะกับเธรดที่เรียกใช้เท่านั้น ทำให้มีความเสี่ยงที่เธรดรันไทม์อื่นของ Fil-C จะยังคงอยู่โดยไม่มีตัวกรอง
- เพื่อป้องกันเรื่องนี้ Fil-C ใช้ API
filc_runtime_threads_handshake() เพื่อ บังคับใช้แบบซิงโครไนซ์ กับเธรดรันไทม์ทั้งหมด
- รับประกันว่าแต่ละเธรดจะเรียก
prctl เดียวกัน
- หากมีหลาย user thread อยู่ จะทำให้เกิด ข้อผิดพลาดด้านความปลอดภัยของ Fil-C เพื่อเพิ่มการป้องกัน
บทสรุป
- การผสานความปลอดภัยของหน่วยความจำกับแซนด์บ็อกซ์ คือชุดความปลอดภัยที่ทรงพลังที่สุด
- Fil-C ผสานแซนด์บ็อกซ์แบบ seccomp ของ OpenSSH ได้อย่างสมบูรณ์ พร้อม คงความปลอดภัยของหน่วยความจำไว้โดยไม่ลดระดับการป้องกัน
- ในสภาพแวดล้อม Linux การใช้ Fil-C ช่วยให้ได้ทั้งความปลอดภัยระดับระบบและความปลอดภัยระดับภาษาไปพร้อมกัน
1 ความคิดเห็น
ความเห็นจาก Hacker News
สงสัยว่าทำไมถึงไม่มีการพูดถึง landlock
มี แนวทางคอมไพล์แบบไฮบริด ที่เป็น C → WASM → C
วิธีนี้ทำให้ควบคุมการโต้ตอบกับ OS ได้ทั้งหมด และทำ sandbox การเข้าถึงหน่วยความจำแบบ WASM ได้ ขณะที่ในเชิงเทคนิคก็ยังคงเป็นโค้ด C อยู่
ดูข้อมูลที่เกี่ยวข้องได้ที่ RLBox
มันยังทำให้หน่วยความจำเสียหายได้ เพียงแต่ขอบเขตจะถูกจำกัดให้อยู่ภายใน sandbox เท่านั้น
ระบบอย่าง SECCOMP ต้องกำหนดนโยบายการโต้ตอบทั้งหมดอย่างละเอียด จึงค่อนข้าง เป็นงานเชิงระบบระเบียบมาก
ตรงกันข้าม Fil-C ใช้แนวทางที่ให้ภาษาและรันไทม์พยายามรับประกันการทำงานที่ถูกต้องของโปรแกรมเอง
ไบนารีของ Fil-C เป็นไฟล์รันได้ปกติ จึงสามารถใช้ร่วมกับ sandbox อย่าง SECCOMP ได้ด้วย
Linux ใช้เวลา 20 ปีกว่าจะสร้างอินเทอร์เฟซ prctl ได้ ดังนั้นถ้าจะเห็นอะไรคล้ายกันใน WASI ก็น่าจะต้องรออีก 10 ปี
แม้แต่ภายใน sandbox ก็ยังสร้าง execution flow แปลก ๆ ได้
ผู้สร้าง Fil-C มักทำ สิ่งประดิษฐ์ที่น่าสนใจในเชิงเทคนิค ได้ดี แต่ก็กังวลว่ายังมีการตรวจสอบความถูกต้องของ implementation ไม่เพียงพอหรือไม่
มีการบอกว่าสามารถคอมไพล์โปรแกรม setuid ด้วย Fil-C ได้ แต่ส่วนที่แก้ไข ld.so อาจมีความเสี่ยง
แอป setuid ต้องเขียนแบบป้องกันตัวอย่างมากในเรื่อง environment variable, file descriptor, rlimit, signal ฯลฯ
ส่วนเหล่านี้ยังไม่สมบูรณ์ จึงมี ความเสี่ยง หากจะนำไปใช้กับโครงสร้างพื้นฐานจริง
ถึงอย่างนั้น การลองทดสอบ codebase ด้วย Fil-C ก็อาจช่วยค้นพบบั๊กที่น่าสนใจได้
การแก้ไข ld.so เป็นเพียงการเปลี่ยนเล็กน้อยระดับสอนให้มันเข้าใจ layout ของ libc
บั๊ก getenv ที่เกี่ยวกับ setuid ก็แก้เป็น secure_getenv แล้ว
สิ่งที่วิจารณ์มามีทั้งส่วนที่จริงและส่วนที่เป็น FUD ปนกันอยู่
ในสถานการณ์ data race, pointer และ capability ของ Fil-C อาจไม่สอดคล้องกัน
ซึ่งอาจนำไปสู่ การละเมิด memory safety ได้
ผู้สร้างปฏิเสธเรื่องนี้ แต่การเอาไปเทียบกับ Java ก็ไม่เหมาะสม
เทคโนโลยีนั้นยอดเยี่ยม แต่ท่าทีของผู้สร้างทำให้ความน่าเชื่อถือลดลง
WASM เป็นทั้ง sandbox และ execution environment ซึ่งอาจให้ memory safety ได้ในระดับหนึ่งขึ้นอยู่กับวิธีใช้งาน
ถ้าคอมไพล์ C ไปเป็น WASM บั๊กก็ยังคงอยู่ แต่ขอบเขตผลกระทบจะถูกจำกัด
ดังนั้นการจัด WASM เป็นเทคโนโลยี sandboxing ก็ถูกต้อง แต่ในฐานะ execution environment มันยังมีความเป็นไปได้มากกว่านั้น
บั๊กในโมดูล B อาจทำให้อ่านข้อมูลของโมดูล A ได้
กล่าวคือ WASM เป็นเพียงตัวแทนของ process sandbox แบบเบาเท่านั้น
เพราะกับ C ก็สามารถพูดได้เหมือนกันว่า “ปลอดภัยได้ ขึ้นอยู่กับวิธีใช้”
WASM กันการหนีออกจากรันไทม์ได้ แต่กันการหนีออกจากหน่วยความจำของโปรแกรมภายในไม่ได้
มีคนขอให้เปรียบเทียบ Fil-C กับ Rust
Fil-C เหมาะกับการ เสริมความแข็งแรงให้โปรแกรม C เดิมโดยเน้นความเข้ากันได้และความปลอดภัย
Rust เหมาะกับการสร้าง codebase ใหม่ที่ต้องการ ความปลอดภัยแบบสถิต และ ประสิทธิภาพ
Fil-C มีการเสียประสิทธิภาพอยู่บ้าง แต่มีประโยชน์กับโค้ด C เดิมอย่าง ffmpeg, nginx, sudo เป็นต้น
Rust เด่นเรื่อง multithreading และ type system
เป้าหมายคือทำให้ได้ memory safety มากกว่าปรับปรุงการออกแบบภาษา
คู่แข่งที่ใกล้เคียงจึงน่าจะเป็น D, Nim, Go มากกว่า Rust
ส่วน Rust ป้องกันตั้งแต่ตอน compile time
ทั้งสองแนวทางเป็นคนละแกนกัน และใน Rust เองก็สามารถเพิ่ม runtime check แบบ Fil-C ได้
MicroVM กำลังได้รับความนิยมมากขึ้นเรื่อย ๆ
เลยสงสัยว่า Fil-C จะใช้ประโยชน์จากมันได้อย่างไร
อยากให้โปรเจกต์นี้ได้รับความสนใจมากกว่านี้
คงจะดีถ้าเครื่องมือสำคัญอย่าง sudo หรือ polkit ถูกแจกจ่ายในรูปแบบที่ปลอดภัยต่อหน่วยความจำ
อยากเห็นการใช้ sandboxing มากขึ้นแม้ในภาษาที่มี memory safety
น่าเสียดายที่แม้แต่ใน Rust หรือ Go ก็ยังไม่ค่อยใช้ sandbox ระดับ OS กัน
ตั้งค่าในระดับไลบรารีได้ยาก และอ่อนไหวต่อเวอร์ชันเคอร์เนลหรือ implementation ของ libc
อีกทั้งยังกรองอินพุตที่อยู่หลัง pointer อย่าง file path ไม่ได้ จึงมีข้อจำกัด
เพราะแบบนี้จึงมักต้องตั้งค่าโดยตรงในระดับแอปพลิเคชัน
ส่วน Go มีรันไทม์ขนาดใหญ่ จึงทำให้ปลอดภัยแบบ Fil-C ได้ยากกว่า
มีคนสงสัยว่า Fil-C ต่างจาก Address Sanitizer (ASan) ของ clang อย่างไร
ถ้าแค่ทำให้เกิด runtime panic ก็คงเรียกว่า “memory safe” ได้ยากไม่ใช่หรือ
บางบั๊กก็ตรวจไม่พบ
มันใช้วิธีวาง “red zone” รอบหน่วยความจำ จึงจะตรวจพบได้ถ้าโชคดี
memory safety ไม่ได้หมายถึง “ไม่ crash” แต่หมายถึง “การเข้าถึงที่ผิดพลาดไม่สามารถก่อผลได้”
มีคนถามว่าทำไมไม่ใช้ VM เต็มรูปแบบเป็น sandbox ไปเลย
โปรเซสหนึ่งจะ parse อินพุตโดยไม่มีสิทธิ์ ส่วนอีกโปรเซสทำงานด้วยสิทธิ์สูง
ทั้งสองโปรเซสสื่อสารกันผ่าน IPC
ถ้าใช้ VM ความปลอดภัยจะสูงขึ้น แต่ก็มี overhead สูง และความสามารถอย่างการเข้าถึง GPU หรือไฟล์ก็ซับซ้อนขึ้น
เพราะฉะนั้นโดยทั่วไป sandbox ระดับ OS จึงสะอาดกว่า
ต้องจัดสรร GPU แบบเฉพาะให้ และแม้แต่ Qubes ก็ยังเชื่อมต่อผ่าน X11 forwarding เท่านั้น จึง ไม่มีการเร่งความเร็ว