2. 创建沙盒政策
获得执行器后,您可能需要为 Sandboxee 定义 Sandbox 政策。否则,Sandboxee 仅受默认系统调用政策保护。
沙盒政策的目标是限制 Sandboxee 可以发出的系统调用和参数,以及它可以访问的文件。您需要详细了解计划沙盒化的代码所需的系统调用。观察系统调用的方法之一是使用 Linux 的命令行工具 strace 运行代码。
获得系统调用的列表后,您可以使用 PolicyBuilder 来定义政策。PolicyBuilder 附带许多便捷的辅助函数,可用于执行许多常见操作。以下列表仅列出了部分可用函数:
- 允许进程启动的任何系统调用:
AllowStaticStartup();
AllowDynamicStartup();
- 将所有打开/读取/写入* 系统调用列入许可名单:
AllowOpen();
AllowRead();
AllowWrite();
- 将任何退出/访问/状态相关的系统调用列入许可名单:
AllowExit();
AllowStat();
AllowAccess();
- 将所有与休眠/时间相关的系统调用列入许可名单:
AllowTime();
AllowSleep();
这些便捷函数可将任何相关的系统调用列入许可名单。这样做的好处是,可以在某些系统调用不可用的不同架构上使用同一政策(例如,ARM64 没有 OPEN 系统调用),但可能会启用比必要更多的系统调用,从而带来轻微的安全风险。例如,AllowOpen() 可让沙盒进程调用任何与打开相关的系统调用。如果您只想将一个特定的系统调用列入许可名单,可以使用 AllowSyscall();
;如果您想同时将多个系统调用列入许可名单,可以使用 AllowSyscalls()
。
到目前为止,该政策仅检查系统调用标识符。如果您需要进一步加强政策,并希望定义一项只允许使用特定实参的系统调用政策,则需要使用 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();
}