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ể sẽ muốn xác định một Chính sách hộp cát cho Sandboxee. Nếu không, Sandboxee sẽ chỉ được bảo vệ theo Chính sách Syscall mặc định.

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 định đưa và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 các lệnh gọi hệ thống, 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 (nhiều hàm trong số đó đã được đưa vào DefaultPolicyBuilder()). Danh sách sau đây chỉ là một đoạn trích nhỏ của các hàm hiện có:

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

Các hàm tiện lợi này cho phép mọi lệnh gọi hệ thống có liên quan. Điều này có ưu điểm 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ó (ví dụ: ARM64 không có lệnh gọi hệ thống OPEN), nhưng có rủi ro nhỏ về bảo mật khi bật 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 mọi lệnh gọi hệ thống liên quan đến việc mở. Nếu chỉ muốn cho phép một lệnh gọi hệ thống cụ thể, bạn có thể dùng AllowSyscall(); để cho phép nhiều lệnh gọi hệ thống 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 chính sách hơn nữa và muốn xác định một chính sách mà bạn chỉ cho phép một lệnh gọi hệ thống có các đối số cụ thể, bạn cần dùng AddPolicyOnSyscall() hoặc AddPolicyOnSyscalls(). Các hàm này không chỉ lấy mã lệnh gọi hệ thống làm đối số mà còn lấy cả bộ lọc seccomp-bpf thô bằng cách sử dụng 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 viết mã BPF lặp đi lặp lại mà bạn cho rằng 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() để 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.

AddLibrariesForBinary() là một hàm đặc biệt hữu ích. Hàm này 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 là một công việ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 tệp nhị phân của mình 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 hành vi vi phạm được kích hoạt, hãy cho phép lệnh gọi hệ thống và lặp lại quy trình. Nếu bạn gặp phải một hành vi 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 suôn sẻ, bạn có thể thử trả về lỗi bằng BlockSyscallWithErrno().

Ví dụ sau đây tạo trực tiếp sandbox2::PolicyBuilder để minh hoạ việc hạn chế các đối số write và mprotect và chặn openat (nếu không thì DefaultPolicyBuilder() sẽ cho phép):

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