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