2. 建立沙箱政策

有了執行器後,您可能需要為 Sandboxee 定義沙箱政策。否則,沙箱只會受到預設 Syscall 政策保護。

沙箱政策的目標是限制沙箱可執行的系統呼叫和引數,以及可存取的檔案。您需要詳細瞭解要將程式碼沙箱化時所需的系統呼叫。觀察系統呼叫的方法之一,是使用 Linux 的指令列工具 strace 執行程式碼。

取得系統呼叫清單後,您可以使用 PolicyBuilder 定義政策。

PolicyBuilder 隨附許多便利和輔助函式,可執行許多常見作業 (其中許多已納入 DefaultPolicyBuilder())。以下僅列出部分可用函式:

  • 允許程序啟動的任何系統呼叫:
    • AllowStaticStartup();
    • AllowDynamicStartup();
  • 將任何 open/read/write* 系統呼叫加入許可清單:
    • AllowOpen();
    • AllowRead();
    • AllowWrite();
  • 將任何與結束/存取/狀態相關的系統呼叫加入許可清單:
    • AllowExit();
    • AllowStat();
    • AllowAccess();
  • 將任何睡眠/時間相關的系統呼叫加入許可清單:
    • AllowTime();
    • AllowSleep();

這些便利函式會將所有相關系統呼叫加入許可清單。這種做法的優點是,相同的政策可用於無法使用特定系統呼叫的不同架構 (例如 ARM64 沒有 OPEN 系統呼叫),但會產生輕微的安全風險,因為啟用的系統呼叫可能超過必要數量。舉例來說,AllowOpen() 可讓 Sandboxee 呼叫任何開放相關的系統呼叫。如果只想將一個特定系統呼叫加入許可清單,可以使用 AllowSyscall();;如要一次允許使用多個系統呼叫,則可使用 AllowSyscalls()。

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

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

AddLibrariesForBinary() 是一項特別實用的函式,可新增二進位檔所需的程式庫和連結器。

很遺憾,您還是得手動找出要加入許可清單的系統呼叫。建立政策,並加入二進位檔所需的系統呼叫,然後以一般工作負載執行。如果觸發違規,請將系統呼叫加入許可清單,然後重複上述程序。如果遇到違規情形,但認為加入許可清單可能有風險,且程式能妥善處理錯誤,可以嘗試使用 BlockSyscallWithErrno() 讓程式改為傳回錯誤。

以下範例會直接建構 sandbox2::PolicyBuilder,示範如何限制 write 和 mprotect 引數,以及封鎖 openat (否則 DefaultPolicyBuilder() 會允許):

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