2. Tạo chính sách Hộp cát

Sau khi có executor, bạn có thể muốn xác định Chính sách Sandbox cho Sandboxee. Nếu không, Sandboxee chỉ được bảo vệ theo Chính sách Syscall mặc định.

Mục tiêu của chính sách Hộp cát là hạn chế các lệnh gọi hệ thống và đối số mà Sandboxee có thể thực hiện, cũng như các tệp mà Hộp cát có thể truy cập. Bạn cần nắm rõ chi tiết về các syscall bắt buộc phải có theo mã mà bạn định tạo hộp cát. Một cách để quan sát syscall là chạy mã bằng công cụ dòng lệnh strace của Linux.

Sau khi có danh sách các lệnh gọi hệ thống, bạn có thể sử dụng PolicyBuilder để xác định chính sách. PolicyBuilder có nhiều chức năng trợ giúp và tiện lợi cho phép thực hiện nhiều thao tác phổ biến. Danh sách sau đây chỉ là một phần trích dẫn nhỏ về các hàm có sẵn:

  • Đưa mọi lệnh gọi hệ thống (syscall) để khởi động quy trình vào danh sách cho phép:
    • AllowStaticStartup();
    • AllowDynamicStartup();
  • Đưa bất kỳ lệnh gọi hệ thống nào mở/read/write* vào danh sách cho phép:
    • AllowOpen();
    • AllowRead();
    • AllowWrite();
  • Đưa mọi lệnh gọi hệ thống liên quan đến thoát/truy cập/trạng thái vào danh sách cho phép:
    • AllowExit();
    • AllowStat();
    • AllowAccess();
  • Đưa mọi lệnh gọi hệ thống liên quan đến giấc ngủ/thời gian vào danh sách cho phép:
    • AllowTime();
    • AllowSleep();

Các hàm tiện lợi này đã đưa mọi lệnh gọi hệ thống có liên quan vào danh sách cho phép. Điều này có lợi thế là có thể sử dụng cùng một chính sách trên nhiều cấu trúc khi một số lệnh gọi hệ thống nhất định không hoạt động (ví dụ: ARM64 không có lệnh gọi hệ thống MỞ), nhưng có rủi ro bảo mật nhỏ khi bật nhiều sycsall hơn mức cần thiết. Ví dụ: AllowOpen() cho phép Sandboxee gọi bất kỳ lệnh gọi syscall đang mở nào có liên quan. Nếu chỉ muốn thêm một syscall cụ thể vào danh sách cho phép, bạn có thể dùng AllowSyscall(); để cho phép nhiều syscall cùng lúc có thể dùng AllowSyscalls().

Cho đến nay, chính sách này chỉ kiểm tra giá trị nhận dạng lệnh gọi hệ thống. Nếu cần củng cố chính sách hơn nữa và muốn xác định một chính sách mà trong đó bạn chỉ cho phép lệnh gọi hệ thống có các đối số cụ thể, bạn cần sử dụng AddPolicyOnSyscall() hoặc AddPolicyOnSyscalls(). Các hàm này không chỉ lấy mã nhận dạng syscall làm đối số mà còn sử dụng bộ lọc seccomp-bpf thô bằng cách sử dụng các macro trợ giúp bpf từ nhân Linux. Xem tài liệu về hạt nhân để biết thêm thông tin về BPF. Nếu bạn thấy mình phải viết mã BPF lặp lại mà bạn cho rằng cần có một trình bao bọc khả năng hữu dụng, hãy gửi yêu cầu về tính năng.

Ngoài các hàm liên quan đến syscall, PolicyBuilder cũng cung cấp một số hàm liên quan đến hệ thống tệp như AddFile() hoặc AddDirectory() để liên kết gắn một tệp/thư mục vào hộp cát. Bạn có thể sử dụng trình trợ giúp AddTmpfs() để thêm bộ nhớ tệp tạm thời vào hộp cát.

Một hàm đặc biệt hữu ích là AddLibrariesForBinary() có chức năng thêm các thư viện và trình liên kết mà một tệp nhị phân yêu cầu.

Rất tiếc, việc thiết lập hệ thống lệnh gọi cho danh sách cho phép vẫn còn khá nhiều công việc thủ công. Tạo chính sách với các lệnh gọi hệ thống mà bạn biết rõ nhu cầu nhị phân của mình và chạy chính sách đó với khối lượng công việc chung. Nếu có lỗi vi phạm được kích hoạt, hãy đưa lệnh gọi hệ thống vào danh sách cho phép và lặp lại quy trình này. Nếu gặp phải một lỗi vi phạm mà bạn cho rằng có khả năng gây rủi ro khi đưa vào danh sách cho phép và chương trình này xử lý lỗi một cách linh hoạt, thì bạn có thể thử yêu cầu BlockSyscallWithErrno() trả về lỗi.

#include "sandboxed_api/sandbox2/policy.h"
#include "sandboxed_api/sandbox2/policybuilder.h"
#include "sandboxed_api/sandbox2/util/bpf_helper.h"

std::unique_ptr<sandbox2::Policy> CreatePolicy() {
  return sandbox2::PolicyBuilder()
    .AllowSyscall(__NR_read)  // See also AllowRead()
    .AllowTime()              // Allow time, gettimeofday and clock_gettime
    .AddPolicyOnSyscall(__NR_write, {
        ARG(0),        // fd is the first argument of write (argument #0)
        JEQ(1, ALLOW), // allow write only on fd 1
        KILL,          // kill if not fd 1
    })
    .AddPolicyOnSyscall(__NR_mprotect, {
        ARG_32(2), // prot is a 32-bit wide argument, so it's OK to use *_32
                   // macro here
        JNE32(PROT_READ | PROT_WRITE, KILL), // prot must be the RW, otherwise
                                             // kill the process
        ARG(1), // len is a 64-bit argument
        JNE(0x1000, KILL),  // Allow single page syscalls only, otherwise kill
                            // the process
        ALLOW,              // Allow for the syscall to proceed, if prot and
                            // size match
    })
    // Allow the openat() syscall but always return "not found".
    .BlockSyscallWithErrno(__NR_openat, ENOENT)
    .BuildOrDie();
}