2. 创建沙盒政策
拥有执行器后,您可能需要为 Sandboxee 定义沙盒 政策。 否则,Sandboxee 仅受 默认系统调用 政策的保护。
沙盒政策的目标是限制 Sandboxee 可以发出的系统调用和参数,以及它可以访问的文件。您需要详细了解计划沙盒化的代码所需的系统调用。观察系统调用的一种方法是使用 Linux 的命令行工具 strace 运行代码。
获得系统调用列表后,您可以使用 PolicyBuilder 定义政策。
PolicyBuilder 附带许多便捷函数和帮助程序函数,可实现许多
常见操作(其中许多已包含在
DefaultPolicyBuilder() 中)。以下列表仅是可用函数的一小部分:
- 允许任何用于进程启动的系统调用:
AllowStaticStartup();AllowDynamicStartup();
- 允许任何 open/read/write* 系统调用:
AllowOpen();AllowRead();AllowWrite();
- 允许任何与退出/访问/状态相关的系统调用:
AllowExit();AllowStat();AllowAccess();
- 允许任何与休眠/时间相关的系统调用:
AllowTime();AllowSleep();
这些便捷函数允许任何相关系统调用。这样做的好处是,在某些系统调用不可用(例如 ARM64 没有 OPEN 系统调用)的不同架构中,可以使用相同的政策,但可能会启用比必要更多的系统调用,存在较小的安全风险。例如,AllowOpen() 使 Sandboxee 能够调用任何与 open 相关的系统调用。如果您只想允许一个特定的系统调用,可以使用 AllowSyscall();,如需一次允许多个系统调用,可以使用 AllowSyscalls()。
到目前为止,该政策仅检查系统调用标识符。如果您需要进一步加强政策,并希望定义一项政策,其中仅允许使用特定参数的系统调用,则需要使用 AddPolicyOnSyscall() 或 AddPolicyOnSyscalls()。这些函数不仅将系统调用 ID 作为参数,还使用来自 Linux 内核的 bpf 帮助程序宏作为原始 seccomp-bpf 过滤器。如需详细了解 BPF,请参阅 内核
文档
。如果您发现自己编写了重复的 BPF 代码,并且认为应该有一个可用性封装容器,请随时提交功能请求。
除了与系统调用相关的函数之外,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();
}