2. 建立沙箱政策

取得執行程式後,建議您為沙箱定義沙箱政策。否則, Sandboxee 將僅受到預設 Syscall 政策的保護。

我們的目標是限製沙箱作業的系統呼叫和引數,以及沙箱政策可存取的檔案。您必須詳細瞭解規劃在沙箱中的程式碼所需的系統呼叫。觀察系統呼叫的其中一種方法是使用 Linux 的指令列工具追蹤記錄來執行程式碼。

取得系統呼叫的清單後,即可使用 PolicyBuilder 定義政策。PolicyBuilder 提供許多便利和輔助函式,可讓您執行許多常見作業。下列清單僅列舉部分可用函式:

  • 將任何程序啟動的系統呼叫加入許可清單:
    • AllowStaticStartup();
    • AllowDynamicStartup();
  • 將所有已開啟/讀取/寫入* syscall 加入許可清單:
    • AllowOpen();
    • AllowRead();
    • AllowWrite();
  • 將所有離開事件/存取權/狀態相關 syscall 加入許可清單:
    • AllowExit();
    • AllowStat();
    • AllowAccess();
  • 將任何與睡眠/時間相關的系統呼叫加入許可清單:
    • AllowTime();
    • AllowSleep();

這些便利函式會將所有相關的系統呼叫加入許可清單。相同的政策有利於無法使用某些系統呼叫的不同架構 (例如 ARM64 沒有開啟系統呼叫),但會帶來比必要的系統更多安全風險。舉例來說,AllowOpen() 可讓沙箱呼叫任何開放式相關的系統呼叫。如果只想將特定系統呼叫加入許可清單,可以使用 AllowSyscall(); 一次允許多個系統呼叫。您可以使用 AllowSyscalls()

到目前為止,政策只會檢查 syscall ID。如果您需要進一步強化政策,並想定義僅允許特定引數的系統呼叫的政策,您必須使用 AddPolicyOnSyscall()AddPolicyOnSyscalls()。這些函式不僅將 syscall ID 做為引數,也會使用 Linux kernel 中的 bpf 輔助程式巨集,來則是原始的 seccomp-bpf 篩選器。如要進一步瞭解 BPF,請參閱核心說明文件。如果您發現自己撰寫的重複 BPF 程式碼,且認為應該產生可用性包裝函式,歡迎提出功能要求。

除了 syscall 相關的函式外,PolicyBuilder 也提供多個與檔案系統相關的函式 (例如 AddFile()AddDirectory()),可將檔案/目錄繫結至沙箱。AddTmpfs() 輔助程式可用於在沙箱中新增暫存檔案儲存空間。

AddLibrariesForBinary() 特別有用,可以加入二進位檔所需的程式庫和連接器。

但處理系統呼叫後仍需要手動操作。使用已知二進位檔需求的系統呼叫建立政策,然後透過常見的工作負載執行該政策。如果觸發違規事件,請將系統呼叫加入許可清單,並重複執行該程序。如果發現疑似有風險的違規情事,您也許有危險,而程式能妥善處理錯誤,您可以嘗試使用 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();
}