- An toàn bộ nhớ và sandboxing là hai khái niệm bảo mật độc lập với nhau, và cần có cả hai để hình thành một cơ chế phòng vệ mạnh mẽ
- Fil-C là một triển khai an toàn bộ nhớ cho C/C++, bảo đảm tính an toàn tới cấp độ system call của Linux và có thể được sử dụng cả trong các thành phần hệ thống như OpenSSH
- Trong quá trình chuyển sandbox dựa trên seccomp-BPF của OpenSSH sang Fil-C, việc hạn chế tạo luồng và điều chỉnh bộ lọc seccomp là các thách thức then chốt
- Để quản lý các luồng nền của runtime Fil-C, API
zlock_runtime_threads() đã được bổ sung nhằm kiểm soát hoạt động của luồng bên trong sandbox
- Fil-C triển khai việc áp dụng đồng bộ lệnh gọi
prctl tới mọi luồng runtime, để no_new_privs và bộ lọc seccomp được áp dụng nhất quán trên toàn bộ tiến trình
Mối quan hệ giữa an toàn bộ nhớ và sandboxing
- An toàn bộ nhớ và sandboxing là các lớp bảo mật khác nhau, và chỉ một bên thôi thì không thể cung cấp sự bảo vệ hoàn chỉnh
- Ví dụ an toàn bộ nhớ nhưng không có sandbox: chương trình Java có thể ghi đè tệp thông qua đầu vào người dùng
- Ví dụ có sandbox nhưng không an toàn bộ nhớ: chương trình viết bằng assembly với quyền bị giới hạn
- Sandbox thực tế vẫn tồn tại lỗ hổng mang tính cấu trúc, chẳng hạn như cho phép giao tiếp với tiến trình broker
- Vì vậy, kết hợp an toàn bộ nhớ và sandboxing là cách phòng vệ tốt nhất
Kết hợp Fil-C với sandbox OpenSSH
- Fil-C là một triển khai an toàn bộ nhớ cho C/C++, duy trì tính an toàn ở cấp độ system call của Linux
- Runtime Fil-C có thể chạy cả trong các thành phần hệ thống cấp thấp như
init, udevd
- OpenSSH hoạt động bình thường trên Fil-C và sử dụng sandbox seccomp-BPF
- Trên Linux, OpenSSH xây dựng sandbox bằng các công cụ sau
- Hạn chế truy cập hệ thống tệp bằng
chroot
- Chạy bằng người dùng/nhóm
sshd
- Giới hạn mở tệp và tạo tiến trình bằng
setrlimit
- Chỉ cho phép các system call được chỉ định bằng bộ lọc seccomp-BPF
- Fil-C hỗ trợ sẵn
chroot và chuyển đổi người dùng, nhưng setrlimit và seccomp-BPF có thể xung đột với hoạt động của runtime nên cần điều chỉnh bổ sung
Kiểm soát luồng trong runtime Fil-C
- Runtime Fil-C sử dụng các luồng nền cho garbage collection và tự động dừng/khởi động lại khi cần
- Sandbox
setrlimit của OpenSSH nhằm mục tiêu cấm tạo tiến trình mới, nên việc runtime tạo luồng có thể vi phạm điều này
- Để giải quyết, API
zlock_runtime_threads() đã được thêm vào <stdfil.h>
- Runtime sẽ tạo ngay các luồng cần thiết và sau đó vô hiệu hóa việc tự động kết thúc
- Được gọi trong hàm
ssh_sandbox_child của OpenSSH trước khi gọi setrlimit hoặc seccomp-BPF
Điều chỉnh bộ lọc seccomp của OpenSSH
- Sau khi áp dụng
zlock_runtime_threads(), phần lớn chức năng sandbox vẫn hoạt động như cũ
- Các thay đổi sau đã được thực hiện trong bộ lọc seccomp
- Khi vi phạm, đặt thành
SECCOMP_RET_KILL_PROCESS để các luồng nền của Fil-C cũng bị chấm dứt cùng lúc
- Thêm
MAP_NORESERVE vào danh sách cho phép để hỗ trợ dùng bộ cấp phát bộ nhớ của Fil-C
- Cho phép lệnh gọi
sched_yield, được dùng trong cơ chế khóa của Fil-C
- Lệnh gọi
futex dùng cho đồng bộ hóa của Fil-C đã được cho phép sẵn nên không cần thay đổi thêm
Cách Fil-C triển khai prctl
- Khi cài đặt bộ lọc seccomp, OpenSSH sử dụng hai lệnh gọi
prctl
PR_SET_NO_NEW_PRIVS để chặn việc có thêm đặc quyền
PR_SET_SECCOMP, SECCOMP_MODE_FILTER để kích hoạt bộ lọc
- Vấn đề là
prctl chỉ áp dụng cho luồng gọi nó, nên các luồng runtime khác của Fil-C có nguy cơ vẫn tồn tại mà không có bộ lọc
- Để ngăn điều đó, Fil-C dùng API
filc_runtime_threads_handshake() để áp dụng đồng bộ lên mọi luồng runtime
- Bảo đảm mỗi luồng đều thực hiện cùng một lệnh gọi
prctl
- Nếu có nhiều luồng người dùng tồn tại, sẽ phát sinh lỗi an toàn Fil-C để tăng cường bảo vệ
Kết luận
- Sự kết hợp giữa an toàn bộ nhớ và sandboxing là tổ hợp bảo mật mạnh nhất
- Fil-C tích hợp đầy đủ sandbox dựa trên seccomp của OpenSSH mà vẫn duy trì an toàn bộ nhớ mà không làm suy giảm mức bảo vệ
- Trong môi trường Linux, việc sử dụng Fil-C cho phép đồng thời đạt được bảo mật cấp hệ thống và an toàn ở cấp độ ngôn ngữ
1 bình luận
Ý kiến trên Hacker News
Thắc mắc vì sao không có nhắc đến landlock
Có một cách tiếp cận biên dịch lai theo hướng C → WASM → C
Làm như vậy có thể kiểm soát hoàn toàn tương tác với OS, đồng thời sandbox truy cập bộ nhớ như WASM nhưng về mặt kỹ thuật vẫn giữ dưới dạng mã C
Có thể xem tài liệu liên quan tại RLBox
Nó có thể làm hỏng bộ nhớ, chỉ là phạm vi bị giới hạn trong sandbox mà thôi
Những hệ thống như SECCOMP đòi hỏi phải định nghĩa tỉ mỉ mọi chính sách tương tác nên khá quan liêu
Trong khi đó Fil-C chọn cách tiếp cận mà chính ngôn ngữ và runtime cố gắng đảm bảo hành vi đúng đắn của chương trình
Binary Fil-C là file thực thi thông thường nên cũng có thể dùng cùng các sandbox như SECCOMP
Linux mất 20 năm để tạo ra giao diện prctl, nên có lẽ phải chờ 10 năm nữa mới thấy thứ tương tự trong WASI
Ngay cả bên trong sandbox vẫn có thể tạo ra luồng thực thi bất thường
Tác giả của Fil-C rất giỏi tạo ra những phát minh thú vị về mặt kỹ thuật, nhưng vẫn lo không biết việc kiểm chứng triển khai đã đủ chưa
Có nói rằng có thể biên dịch chương trình setuid bằng Fil-C, nhưng phần chỉnh sửa ld.so có thể nguy hiểm
Ứng dụng setuid phải được viết cực kỳ phòng thủ trước biến môi trường, file descriptor, rlimit, signal v.v.
Những phần này vẫn còn chưa hoàn thiện nên dùng trong hạ tầng thực tế sẽ có rủi ro
Dù vậy, thử nghiệm codebase với Fil-C có thể giúp phát hiện những lỗi thú vị
Việc sửa ld.so chỉ là thay đổi nhỏ ở mức dạy cho nó về layout của libc
Lỗi getenv liên quan đến setuid cũng đã được sửa bằng secure_getenv
Trong những chỉ trích đó có phần đúng và cũng có phần FUD
Trong tình huống data race, pointer và capability trong Fil-C có thể không khớp nhau
Điều này có thể dẫn đến vi phạm an toàn bộ nhớ
Tác giả phủ nhận điều này, nhưng việc so sánh với Java là không phù hợp
Công nghệ thì tuyệt vời, nhưng thái độ của tác giả làm giảm niềm tin
WASM vừa là sandbox vừa là môi trường thực thi, và tùy cách sử dụng mà có thể đạt được mức độ an toàn bộ nhớ nhất định
Khi biên dịch C sang WASM thì bug vẫn còn đó, nhưng phạm vi ảnh hưởng bị giới hạn
Vì vậy xếp WASM vào nhóm công nghệ sandbox là đúng, nhưng với vai trò môi trường thực thi thì nó còn nhiều khả năng hơn
Bug trong mô-đun B có thể đọc được dữ liệu của mô-đun A
Nghĩa là WASM chỉ là một dạng thay thế cho sandbox tiến trình nhẹ
Vì cũng có thể nói C “an toàn tùy cách dùng”
WASM ngăn việc thoát khỏi runtime, nhưng không ngăn được việc thoát bộ nhớ của chương trình bên trong
Có người đề nghị so sánh Fil-C với Rust
Fil-C phù hợp để tăng cường các chương trình C hiện có theo hướng tương thích và bảo mật
Rust phù hợp để xây dựng codebase mới với an toàn tĩnh và hiệu năng
Fil-C có tổn thất hiệu năng nhất định nhưng hữu ích cho mã C hiện có như ffmpeg, nginx, sudo v.v.
Rust mạnh ở multithreading và hệ thống kiểu
Mục tiêu là đảm bảo an toàn bộ nhớ hơn là cải thiện thiết kế ngôn ngữ
Nhóm cạnh tranh của nó gần với D, Nim, Go hơn là Rust
Rust thì ngăn chặn ở thời điểm biên dịch
Hai cách tiếp cận này là trực giao, và Rust cũng có thể bổ sung kiểu kiểm tra runtime theo phong cách Fil-C
MicroVM đang ngày càng phổ biến
Thắc mắc Fil-C có thể tận dụng điều này như thế nào
Mong dự án này được chú ý hơn
Sẽ rất tốt nếu những công cụ cốt lõi như sudo hay polkit được phân phối theo cách an toàn bộ nhớ
Mong rằng ngay cả trong các ngôn ngữ an toàn bộ nhớ cũng sẽ có nhiều ứng dụng sandboxing hơn
Đáng tiếc là ngay cả Rust hay Go cũng ít dùng sandbox ở cấp độ OS
Khó cấu hình ở cấp thư viện, lại nhạy với phiên bản kernel hay cách triển khai libc
Nó cũng không thể lọc đầu vào nằm sau pointer như đường dẫn file nên có giới hạn
Vì vậy thường phải cấu hình trực tiếp ở cấp ứng dụng
Trong khi đó Go có runtime lớn nên khó làm an toàn kiểu Fil-C
Có người hỏi Fil-C khác gì với Address Sanitizer (ASan) của clang
Nếu chỉ ở mức tạo runtime panic thì có khó gọi là “an toàn bộ nhớ” hay không
Một số bug vẫn không bị phát hiện
Nó dùng cách đặt “red zone” quanh bộ nhớ nên chỉ phát hiện được nếu may mắn
An toàn bộ nhớ không có nghĩa là “không crash” mà là “truy cập sai không thể tạo ra hiệu ứng”
Có người hỏi vì sao không dùng một VM hoàn chỉnh làm sandbox
Một tiến trình sẽ parse đầu vào mà không có quyền, tiến trình khác chạy với quyền cao hơn
Hai tiến trình giao tiếp với nhau bằng IPC
Dùng VM thì bảo mật cao hơn nhưng overhead lớn, và các chức năng như GPU hay truy cập file trở nên phức tạp
Vì vậy thông thường sandbox ở cấp OS vẫn gọn gàng hơn
Phải gán riêng GPU, và ngay cả Qubes cũng chỉ kết nối bằng X11 forwarding nên không có tăng tốc