2. 建立沙箱政策
取得執行器後,您可能會想為 Sandboxee 定義 Sandbox 政策。否則,Sandboxee 只會受到預設系統呼叫政策保護。
沙箱政策的目標是限制 Sandboxee 可發出的系統呼叫和引數,以及可存取檔案。您必須詳細瞭解要將程式碼沙箱化時所需的系統呼叫。如要觀察系統呼叫,其中一種方式是使用 Linux 的指令列工具 strace 執行程式碼。
取得系統呼叫清單後,您可以使用 PolicyBuilder 定義政策。PolicyBuilder 隨附許多便利和輔助函式,可執行多項常見作業。以下僅列出部分可用函式:
- 允許程序啟動的任何系統呼叫:
AllowStaticStartup();
AllowDynamicStartup();
- 將任何 open/read/write* 系統呼叫加入許可清單:
AllowOpen();
AllowRead();
AllowWrite();
- 將任何與結束/存取/狀態相關的系統呼叫加入許可清單:
AllowExit();
AllowStat();
AllowAccess();
- 將任何睡眠/時間相關的系統呼叫加入許可清單:
AllowTime();
AllowSleep();
這些便利函式會將所有相關的系統呼叫加入許可清單。這項做法的優點是,您可以在不同架構中使用相同政策 (某些系統呼叫不適用於這些架構,例如 ARM64 沒有 OPEN 系統呼叫),但缺點是可能會啟用不必要的系統呼叫,造成輕微的安全風險。舉例來說,AllowOpen() 可讓 Sandboxee 呼叫任何與開啟相關的系統呼叫。如要只將一個特定系統呼叫加入允許清單,可以使用 AllowSyscall();
。如要一次加入多個系統呼叫,可以使用 AllowSyscalls()
。
這項政策目前只會檢查系統呼叫 ID。如果您需要進一步強化政策,並想定義只允許使用特定引數的系統呼叫政策,則必須使用 AddPolicyOnSyscall()
或 AddPolicyOnSyscalls()
。這些函式不僅會將系統呼叫 ID 做為引數,也會使用 Linux 核心的 bpf 輔助巨集,將原始 seccomp-bpf 篩選器做為引數。如要進一步瞭解 BPF,請參閱核心說明文件。如果您發現自己編寫的 BPF 程式碼重複,且認為應該有可用性包裝函式,歡迎提出功能要求。
除了與系統呼叫相關的函式,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();
}