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

Sau khi có một trình thực thi, bạn có thể muốn xác định một Sandbox Policy (Chính sách hộp cát) cho Sandboxee. Nếu không, Sandboxee sẽ chỉ được bảo vệ bằng Default Syscall Policy (Chính sách mặc định về lệnh gọi hệ thống).

Với Chính sách hộp cát, mục tiêu 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à Sandboxee có thể truy cập. Bạn cần hiểu rõ về các lệnh gọi hệ thống mà mã bạn dự định tạo hộp cát yêu cầu. Một cách để quan sát các lệnh gọi hệ thống là chạy mã bằng công cụ dòng lệnh strace của Linux.

Sau khi có danh sách syscall, bạn có thể dùng PolicyBuilder để xác định chính sách. PolicyBuilder đi kèm với nhiều hàm 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 đoạn trích nhỏ về các hàm có sẵn:

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

Các hàm tiện lợi này cho phép mọi syscall có liên quan. Điều này có lợi thế là bạn có thể sử dụng cùng một chính sách trên nhiều cấu trúc mà một số lệnh gọi hệ thống không có sẵn (ví dụ: ARM64 không có lệnh gọi hệ thống OPEN), nhưng có rủi ro bảo mật nhỏ là cho phép nhiều lệnh gọi hệ thống hơn mức cần thiết. Ví dụ: AllowOpen() cho phép Sandboxee gọi bất kỳ syscall nào liên quan đến việc mở. Nếu chỉ muốn đưa một lệnh gọi hệ thống cụ thể vào danh sách cho phép, bạn có thể dùng AllowSyscall();. Để đưa nhiều lệnh gọi hệ thống vào danh sách cho phép cùng một lúc, bạn 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 tăng cường hơn nữa chính sách và muốn xác định một chính sách mà trong đó bạn chỉ cho phép một lệnh gọi hệ thống có các đối số cụ thể, 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 lấy bộ lọc seccomp-bpf thô bằng cách sử dụng các macro trợ giúp bpf từ nhân Linux. Hãy xem tài liệu về nhân để biết thêm thông tin về BPF. Nếu bạn thấy mình đang viết mã BPF lặp đi lặp lại mà bạn nghĩ nên có một trình bao bọc khả năng sử dụng, hãy thoải mái gửi yêu cầu về tính năng.

Ngoài các hàm liên quan đến lệnh gọi hệ thống, 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ể dùng trình trợ giúp AddTmpfs() để thêm bộ nhớ tệp tạm thời trong hộp cát.

Một hàm đặc biệt hữu ích là AddLibrariesForBinary(). Hàm này sẽ 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 đưa ra các lệnh gọi hệ thống để đưa vào danh sách cho phép vẫn cần một chút công sức thủ công. Tạo một chính sách với các lệnh gọi hệ thống mà bạn biết là tệp nhị phân của bạn cần và chạy chính sách đó với một khối lượng công việc phổ biến. Nếu một 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ếu gặp phải một lỗi vi phạm mà bạn cho rằng có thể gây rủi ro khi đưa vào danh sách cho phép và chương trình xử lý lỗi một cách hiệu quả, bạn có thể thử khiến lỗi đó trả về một lỗi khác bằng BlockSyscallWithErrno().

#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();
}