用于Linux上原生代码执行的多层沙箱。
七层独立防御——无外部依赖,约73 KiB的PIE二进制文件。
┌──────────────────────────────────────────────────────┐
│ Z-Jail │
├──────────────────────────────────────────────────────┤
│ Truthimatics PV (基于证据的裁决引擎) │
│ Namespaces (mount, pid, net, ipc, uts) │
│ pivot_root (加强版 chroot) │
│ Capabilities (丢弃全部,锁定 securebits) │
│ NO_NEW_PRIVS (禁止权限提升) │
│ seccomp-BPF (白名单:仅15个系统调用) │
│ Audit (JSON 日志 + BLAKE2b 哈希) │
└──────────────────────────────────────────────────────┘
git clone https://github.com/Division-36/Z-Jail.git
cd Z-Jail
make
sudo ./z_jail --root=/path/to/rootfs --seccomp-enforce -- /bin/ls
--root 目录应包含一个最小的文件系统,其中有目标二进制文件及其依赖(对于静态二进制文件,只需二进制文件本身即可)。
现有的沙箱解决方案需要做出权衡:
Z-Jail 填补了 bwrap(最小化,默认无 seccomp)和 nsjail(功能丰富,依赖重)之间的空白。它专为 CI 流水线、CTF 监狱挑战和轻量级代码评估 而设计,无需引入容器运行时即可实现纵深防御。
flowchart LR
CLI[CLI 参数] --> P[parse_args]
P --> C{clone 命名空间}
C -->|子进程| CR[child_run]
C -->|父进程| W[waitpid]
CR --> RL[setrlimit]
RL --> FD[关闭 >=3 的文件描述符]
FD --> DUMP[PR_SET_DUMPABLE=0]
DUMP --> PV[pivot_root]
PV --> NNP[PR_SET_NO_NEW_PRIVS]
NNP --> CAP[丢弃 capabilities]
CAP --> SC[seccomp-BPF]
SC --> SIG[通知父进程]
SIG --> EX[execve 目标程序]
W --> A[审计 JSON]
A --> EXIT[退出]
每一层都经过排序,使得后一层无法被前一层撤销:
sequenceDiagram
participant P as 父进程
participant C as 子进程
P->>C: clone (NEWNS|NEWPID|NEWNET|NEWIPC|NEWUTS)
Note over C: setrlimit(CPU, AS, NOFILE, NPROC)
Note over C: close(所有 >2 的 fd)
Note over C: PR_SET_DUMPABLE=0
Note over C: pivot_root → chdir("/") → umount -l
Note over C: PR_SET_NO_NEW_PRIVS
Note over C: capset(全零) + securebits
Note over C: seccomp(SECCOMP_MODE_FILTER, 白名单)
C->>P: write(pipe, ready=1)
Note over C: execve(目标)
P->>P: waitpid
P->>P: 写入审计 JSON
基于证据的裁决引擎。收集有关已执行二进制文件的加权观察,并确定最终裁决(DETERMINISTIC、REJECT 或 UNCERTAIN)。每个观察都带有权重;任何单个观察的权重超过总权重的 50% 即可决定裁决。
通过 clone() 创建五个命名空间:
需要在初始命名空间中有 CAP_SYS_ADMIN。
用 --root 目录替换挂载命名空间的根:
MS_BIND|MS_REC)pivot_root(new_root, put_old) — 交换挂载树chdir("/") — 进入新根umount2("/.pivot_old", MNT_DETACH) — 分离旧根rmdir("/.pivot_old") — 清理这严格强于 chroot(2) —— 沙箱进程无法逃脱回主机根,即使通过沙箱内部的 CLONE_NEWNS 也无法做到(但这已被 seccomp 禁止)。
通过以下方式丢弃所有 capabilities:
capset(hdr, data) // data = {0, 0, 0}
prctl(SECBIT_KEEP_CAPS_LOCKED | SECBIT_NO_SETUID_FIXUP | ...)
进程在 capset 之前丢弃 setuid/setgid,以便在仍持有 CAP_SETUID 时使 uid 更改生效。capset 之后,所有 caps 都被移除且 securebits 被锁定——无法重新启用。
prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
阻止进程或其子进程通过 setuid 二进制文件、文件 capabilities 或 LSM 转换获得新权限。不可逆。
15 个系统调用的允许列表——不在列表中的任何调用都会触发 SECCOMP_RET_KILL:
BPF 过滤器是动态生成的:对于每个白名单条目,生成一条跳转链,要么允许(如果系统调用匹配),要么落空到 KILL。首先检查架构(AUDIT_ARCH_X86_64)。
该过滤器由一个独立的测试(tests/seccomp_filter_test.c,8/8 通过)验证,该测试 fork+execve 测试用例,针对真实的 prctl(PR_SET_SECCOMP) 运行,无需 root。
每次执行都会生成一个 JSON 审计记录:
{
"schema": "z-jail.audit/v1",
"build_id": "Z-Jail/v1+dev",
"timestamp": 1749000000,
"duration_ns": 8500000,
"executable": "/bin/ls",
"verdict": "DETERMINISTIC",
"exit_code": 0,
"sandbox": {
"seccomp_filter": "whitelist-v1",
"seccomp_whitelist_size": 15,
"seccomp_arg_rules_size": 2,
"namespaces": ["mount","pid","net","ipc","uts"],
"pivot_root": "/var/run/z-jail/roots/default",
"no_new_privs": true,
"capabilities_dropped": true
},
"content_fingerprint": "0e5751c026e543b2e8ab2eb06099daa1..."
}
写入 build/audits/<binary-name>.audit.json。content_fingerprint 是目标二进制文件的 BLAKE2b-256 哈希,由父进程在子进程结束后计算。
z_jail --root=<目录> [--seccomp-enforce] [--self-hash=<十六进制>]
[--quiet] [--verbose] -- <程序> [参数...]
# 使用所有保护运行静态二进制文件
sudo z_jail --root=./roots --seccomp-enforce -- bin/hello_static
# 使用二进制完整性验证运行
sudo z_jail --root=./roots --seccomp-enforce \
--self-hash=$(sha256sum z_jail | cut -c1-64) -- bin/program
# 静默模式(无审计 JSON)
sudo z_jail --root=./roots --quiet -- bin/program
make # 构建 z_jail(约 130 KiB 的 PIE 二进制文件)
make install # 安装到 /usr/local/bin + man 页面
make clean # 删除构建产物
make dist # 创建发布 tarball
make check # 冒烟测试(--version + --help)
该二进制文件构建为位置无关可执行文件,启用 -fstack-protector-strong、-D_FORTIFY_SOURCE=2、完全 RELRO 和 -z now。
make CC=clang CFLAGS="-O3 -march=native" # 自定义编译器/标志
# seccomp 过滤器逻辑(8 个测试)
tests/build/seccomp_filter_test
# BLAKE2b 已知答案测试
tests/build/blake2b_known
这些操作不需要 root,并且在 100 毫秒内完成。
make -C tests setup # 构建 payload + 测试根目录
sudo bash tests/run_tests.sh # 17 个场景
需要 root 权限来创建命名空间。测试套件涵盖:
在 WSL2(内核 6.18.x-microsoft-standard-WSL2,Kali Linux)上测量,
每个工具 50 个样本,统一工作负载(一个独立静态二进制文件,其主体是 exit_group(0)),使用 getrusage 工具计时。有关方法,请参见 docs/BENCHMARKS.md。
| 指标 | 值 |
|---|---|
| 二进制文件大小 | 约 73 KiB 未剥离(约 28 KiB 剥离后) |
| 平均沙箱延迟 | 5.85 ± 1.45 毫秒(95% CI [5.45, 6.25]) |
| 峰值 RSS | 1.62 MiB |
| 核心代码行数 | 约 900 |
在测试条件下,Z-Jail 在三个进程级沙箱中具有 最小的驻留内存,延迟介于 bwrap 和 nsjail 之间。Bubblewrap 最快,但默认不执行 seccomp 过滤,因此设置工作较少;Z-Jail 在每次运行时都会安装 seccomp 白名单、丢弃 capabilities 并执行 pivot_root。gVisor (runsc) 在此 WSL2 内核上出现段错误,无法测量;Firecracker 通过 microVM(VM 冷启动,属于不同的指标)进行隔离,因此从 fork 到 exec 的表格中排除。这些是单主机数据——请视为相对值。
注意:旧文档中的数字(约 8 毫秒,约 4 MiB,约 130 KiB)与当前
make构建(约 73 KiB,约 5.9 毫秒)不匹配,似乎不准确;上面的数字是在此代码库上重新测量的。Truthimatics 仍为代码的一部分,并未移除(旧的axiom_jail报告只是使用了不同的二进制名称和工具)。在基准测试期间发现的挂载传播错误已在src/sandbox.c中修复(在绑定挂载前使用MS_REC|MS_PRIVATE);递归重新挂载是所测延迟的一部分。
chroot、mount、ptrace、socket、process_vm_writev 逃脱RLIMIT_CPU)、内存耗尽(RLIMIT_AS)execve 的文件描述符泄漏setuid / 动态链接器 / LD_PRELOAD 提权/proc、/sys 挂载实现的同驻 VM 逃脱CLONE_NEWNET + 被阻止的 socket 所提供的网络出口clone(CLONE_NEWNS|CLONE_NEWPID|...) 成功(需要 CAP_SYS_ADMIN)--root 中可用)--self-hash=<hex>MIT — 完整文本见 LICENSE。
Z-Jail 构建于 WSL2(Kali Linux,GCC 15.2.0),目标为 Linux 5.4+。 由 Division-36 维护。请在问题跟踪器报告问题。
| Z-Jail | Firecracker | gVisor | bwrap | nsjail |
|---|
| 外部依赖 | 零 | libc, seccomp | Go 运行时 | libc | libc, protobuf |
| 二进制大小 | ~73 KiB | 20+ MiB | 40+ MiB | ~70 KiB | ~1 MiB |
| 虚拟机隔离 | 否 | 是 (microVM) | 否 (沙箱) | 否 | 否 |
| seccomp 白名单 | 是 | 否 | 是 | 可选 | 是 |
| 内容哈希 | 是 | 否 | 否 | 否 | 否 |
| 审计 JSON | 是 | 否 | 是 | 否 | 部分 |
| 构建复杂度 | 一个 make | 复杂 | 复杂 | 简单 | 中等 |
| 命名空间 | 标志 | 用途 |
|---|
| Mount | CLONE_NEWNS | 隔离的文件系统树 |
| PID | CLONE_NEWPID | 进程 ID 空间(子进程为 pid 1) |
| Net | CLONE_NEWNET | 无网络接口 |
| IPC | CLONE_NEWIPC | 无共享内存 / 信号量 |
| UTS | CLONE_NEWUTS | 独立的主机名 |
| 系统调用 | 编号 | 说明 |
|---|
read | 0 | 标准输入 |
write | 1 | 标准输出/错误 + 报告管道 |
openat | 257 | 文件访问(不是 open) |
close | 3 | — |
lseek | 8 | — |
brk | 12 | 堆管理 |
mmap | 9 | 参数受限:flags & 4 == 0(无 MAP_SHARED),flags == 0x22(MAP_PRIVATE|MAP_ANONYMOUS) |
munmap | 11 | — |
execve | 59 | 启动时单次执行 |
exit_group | 231 | 干净退出进程 |
rt_sigaction | 13 | 信号处理函数 |
rt_sigprocmask | 14 | 信号掩码 |
getrandom | 318 | 随机数源 |
clock_gettime | 228 | 计时 |
fstat | 5 | 文件元数据 |
| 标志 | 描述 |
|---|
--root=<目录> | 沙箱根目录(必需) |
--seccomp-enforce | 启用 seccomp-BPF 系统调用白名单 |
--self-hash=<十六进制> | 验证二进制文件是否与预期的 BLAKE2b-256 哈希匹配 |
--quiet | 抑制审计输出 |
--verbose | 启用调试日志 |
--version | 显示构建 ID(Z-Jail/v1+dev) |
--help | 显示用法并退出 |
| 代码 | 含义 |
|---|
| 0 | 子进程正常退出(裁决:DETERMINISTIC) |
| 1 | 子进程被信号杀死(裁决:REJECT) |
| 2 | Self-hash:十六进制字符串错误或文件不可读 |
| 3 | Self-hash:不匹配(二进制文件已被篡改) |
| 101 | 子进程设置错误(rlimit 等) |
| 102 | 子进程 seccomp 过滤器安装失败 |
| 103 | 子进程 execve 失败(未找到二进制文件,无执行权限) |
| 104 | 子进程 pivot_root 失败 |
| 105 | 子进程 capability 丢弃失败 |
| 125 | 命名空间创建失败(以 root 运行?内核支持?) |
| # | 场景 | 类型 | 测试内容 |
|---|
| 0 | blake2b_regress | 已知答案 | BLAKE2b 实现正确性 |
| 1 | seccomp_filter | 独立 BPF | BPF 过滤器逻辑的 8 个子测试 |
| 2 | hello_static | ok | 基本静态二进制执行 |
| 3 | hello_dynamic | ok | 带 ld-linux + libc 的动态二进制 |
| 4 | execve_replacement | ok | 沙箱中的 execve(被 seccomp 阻止) |
| 5 | fd_inherited_read | ok | 标准输入/输出正确继承 |
| 6 | mmap_bad_flags | killed | 带 MAP_SHARED 的 mmap 被阻止 |
| 7 | mmap_good_allowed | ok | 带 MAP_PRIVATE|ANONYMOUS 的 mmap 被允许 |
| 8 | mmap_prot_exec | killed | 带 PROT_EXEC 的 mmap 被阻止 |
| 9 | mmap_self_modify | killed | 自修改代码被阻止 |
| 10 | ptrace | killed | ptrace 被阻止 |
| 11 | socket | killed | 套接字创建被阻止 |
| 12 | chroot_escape | killed | chroot 系统调用被阻止 |
| 13 | double_chroot | killed | 双重 chroot 被阻止 |
| 14 | mount_replay | killed | 挂载系统调用被阻止 |
| 15 | cpu_exhaust | killed | RLIMIT_NPROC 阻止 fork 炸弹 |
| 16 | signal_parent | killed | 向父进程发送信号被阻止 |
| 17 | self_hash | ok | 二进制完整性验证 |
| 工具 | 延迟均值 ± 标准差 | 峰值 RSS | 默认 seccomp |
|---|
| Z-Jail | 5.85 ± 1.45 毫秒 | 1.62 MiB | 是 |
| bwrap | 3.56 ± 0.40 毫秒 | 2.19 MiB | 否 |
| nsjail | 8.98 ± 1.68 毫秒 | 7.91 MiB | 是 |
| 文件 | 描述 |
|---|
README.md | 本文件 |
docs/ARCHITECTURE.md | 架构概览 |
docs/SANDBOX.md | 逐层沙箱内部实现 |
docs/SECCOMP.md | seccomp-BPF 白名单设计 |
docs/AUDIT_SCHEMA.md | 审计 JSON 模式参考 |
docs/THREAT_MODEL.md | 安全假设和范围 |
docs/BLAKE2B.md | BLAKE2b 实现细节 |
docs/BENCHMARKS.md | 性能基准 |
docs/BUILD.md | 构建说明 |
docs/adr/ | 架构决策记录(4 份文档) |
man/z_jail.1 | Man 页面 |
SECURITY.md | 安全策略和报告 |
CONTRIBUTING.md | 如何贡献 |
CHANGELOG.md | 发布历史 |
ROADMAP.md | 未来计划 |
TODO.md | 已知差距和计划工作 |