Z-Jail 是一个零依赖、约 130 KiB 的轻量级 Linux 多层沙箱,结合 namespace、seccomp-BPF 和审计日志,用于安全且可审计地隔离执行不可信原生代码。
```
┌──────────────────────────────────────────────────────┐
│ Z-Jail │
├──────────────────────────────────────────────────────┤
│ Truthimatics PV (evidence-based verdict engine) │
│ Namespaces (mount, pid, net, ipc, uts) │
│ pivot_root (chroot on steroids) │
│ Capabilities (drop all, lock securebits) │
│ NO_NEW_PRIVS (no privilege escalation) │
│ seccomp-BPF (whitelist: 15 syscalls only) │
│ Audit (JSON logging + BLAKE2b hashing) │
└──────────────────────────────────────────────────────┘
```
## 目录
- [快速开始](#quick-start)
- [为什么选择 Z-Jail](#why-z-jail)
- [架构](#architecture)
- [层级](#layers)
- [用法](#usage)
- [构建与安装](#build--install)
- [测试](#testing)
- [性能](#performance)
- [威胁模型](#threat-model)
- [文档](#documentation)
- [路线图](#roadmap)
- [许可证](#license)
## 快速开始
```
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
现有的沙箱解决方案各有取舍:
| | **Z-Jail** | **Firecracker** | **gVisor** | **bwrap** | **nsjail** |
|--------------------|-------------|-----------------|------------|-----------|------------|
| 外部依赖 | **零** | libc, seccomp | Go runtime | libc | libc, protobuf |
| 二进制文件大小 | **~130 KiB**| 20+ MiB | 40+ MiB | ~70 KiB | ~1 MiB |
| VM 隔离 | 否 | 是 (microVM) | 否 (沙箱)| 否 | 否 |
| seccomp 白名单 | **是** | 否 | 是 | 可选 | 是 |
| 内容哈希 | **是** | 否 | 否 | 否 | 否 |
| 审计 JSON | **是** | 否 | 是 | 否 | 部分 |
| 构建复杂度 | **一次 `make`** | 复杂 | 复杂 | 极简 | 中等 |
Z-Jail 填补了 `bwrap`(极简,默认不带 seccomp)和 `nsjail`(功能丰富,依赖繁重)之间的空白。它专为 **CI pipeline、CTF jail 挑战和轻量级代码评估**而设计,让您无需引入 container runtime 即可实现纵深防御。
## 架构
### 数据流
```
flowchart LR
CLI[CLI args] --> P[parse_args]
P --> C{clone namespaces}
C -->|child| CR[child_run]
C -->|parent| W[waitpid]
CR --> RL[setrlimit]
RL --> FD[close fds >= 3]
FD --> DUMP[PR_SET_DUMPABLE=0]
DUMP --> PV[pivot_root]
PV --> NNP[PR_SET_NO_NEW_PRIVS]
NNP --> CAP[drop capabilities]
CAP --> SC[seccomp-BPF]
SC --> SIG[signal parent]
SIG --> EX[execve target]
W --> A[audit JSON]
A --> EXIT[exit]
```
### 层级顺序
每一层的顺序都经过精心安排,使得前面的层无法被后面的层撤销:
1. **setrlimit** — 在执行任何操作前限制 CPU、地址空间、文件数量、进程数
2. **fd 清理** — 关闭所有继承的文件描述符 (fd),报告管道除外
3. **PR_SET_DUMPABLE=0** — 禁用核心转储,锁定 /proc/self/mem
4. **pivot_root** — 脱离主机文件系统;旧根目录以懒加载方式卸载
5. **PR_SET_NO_NEW_PRIVS** — 此后不再有 setuid,也无法通过 `capset` 提权
6. **drop_caps** — 清除所有 capabilities,锁定 securebits
7. **seccomp-BPF** — 将系统调用限制为仅限白名单
8. **signal parent** — 告诉父进程沙箱已就绪
9. **execve** — 用目标二进制文件替换当前进程
```
sequenceDiagram
participant P as Parent
participant C as Child
P->>C: clone (NEWNS|NEWPID|NEWNET|NEWIPC|NEWUTS)
Note over C: setrlimit(CPU, AS, NOFILE, NPROC)
Note over C: close(all fds > 2)
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(all zero) + securebits
Note over C: seccomp(SECCOMP_MODE_FILTER, whitelist)
C->>P: write(pipe, ready=1)
Note over C: execve(target)
P->>P: waitpid
P->>P: write audit JSON
```
## 层级
### Truthimatics 公开版本
基于证据的判定引擎。收集有关已执行二进制文件的加权观察结果,并确定最终判定(`DETERMINISTIC`、`REJECT` 或 `UNCERTAIN`)。每个观察结果都有一个权重;任何单个权重超过总权重 50% 的观察结果将直接决定最终判定。
### Namespaces
通过 `clone()` 创建五个 namespace:
| Namespace | Flag | 用途 |
|-----------|------|---------|
| Mount | `CLONE_NEWNS` | 隔离的文件系统树 |
| PID | `CLONE_NEWPID` | 进程 ID 空间(子进程为 pid 1) |
| Net | `CLONE_NEWNET` | 无网络接口 |
| IPC | `CLONE_NEWIPC` | 无共享内存 / 信号量 |
| UTS | `CLONE_NEWUTS` | 独立的 hostname |
初始 namespace 中需要 `CAP_SYS_ADMIN` 权限。
### pivot_root
使用 `--root` 目录替换 mount namespace 的根目录:
1. 将根目录绑定挂载到自身(`MS_BIND|MS_REC`)
2. `pivot_root(new_root, put_old)` — 交换挂载树
3. `chdir("/")` — 进入新根目录
4. `umount2("/.pivot_old", MNT_DETACH)` — 分离旧根目录
5. `rmdir("/.pivot_old")` — 清理
这比 `chroot(2)` 要严格得多 — 沙箱进程绝无可能逃回主机根目录,即使在沙箱内部使用 `CLONE_NEWNS` 也不行(这已经被 seccomp 阻止了)。
### Capabilities
所有 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 被锁定 — 无法再次启用。
### NO_NEW_PRIVS
```
prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
```
防止进程或其子进程通过 `setuid` 二进制文件、文件 capabilities 或 `LSM` 转换获取新特权。此操作不可逆。
### seccomp-BPF (whitelist-v1)
包含 15 个系统调用的允许列表 — 任何不在此列表上的系统调用都会触发 `SECCOMP_RET_KILL`:
| Syscall | 编号 | 备注 |
|---------|--------|-------|
| `read` | 0 | stdin |
| `write` | 1 | stdout/stderr + 报告管道 |
| `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 | 文件元数据 |
BPF 过滤器是动态生成的:对于每个白名单条目,都会生成一个跳转链,允许(如果系统调用匹配)或落入 KILL。首先会检查架构(`AUDIT_ARCH_X86_64`)。
该过滤器由一个独立测试(`tests/seccomp_filter_test.c`,8/8 通过)进行独立验证,该测试针对真实的 `prctl(PR_SET_SECCOMP)` 执行测试用例的 fork+execves,无需 root 权限。
### 7. 审计
每次执行都会生成一条 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/
.audit.json`。`content_fingerprint` 是目标二进制文件的 BLAKE2b-256 哈希值,由父进程在子进程结束后计算得出。
## 用法
```
z_jail --root= [--seccomp-enforce] [--self-hash=]
[--quiet] [--verbose] -- [args...]
```
| Flag | 描述 |
|------|-------------|
| `--root=` | 沙箱根目录(必填) |
| `--seccomp-enforce` | 启用 seccomp-BPF 系统调用白名单 |
| `--self-hash=` | 验证二进制文件是否与预期的 BLAKE2b-256 哈希匹配 |
| `--quiet` | 抑制审计输出 |
| `--verbose` | 启用 debug 日志 |
| `--version` | 显示构建 ID(`Z-Jail/v1+dev`) |
| `--help` | 显示用法并退出 |
### 示例
```
# 运行具有所有保护的 static binary
sudo z_jail --root=./roots --seccomp-enforce -- bin/hello_static
# 运行具有 binary 完整性验证
sudo z_jail --root=./roots --seccomp-enforce \
--self-hash=$(sha256sum z_jail | cut -c1-64) -- bin/program
# 安静模式(无 audit JSON)
sudo z_jail --root=./roots --quiet -- bin/program
```
### 退出码
| 代码 | 含义 |
|------|---------|
| 0 | 子进程正常退出(判定:DETERMINISTIC) |
| 1 | 子进程被信号杀死(判定:REJECT) |
| 2 | Self-hash:十六进制字符串错误或文件不可读 |
| 3 | Self-hash:不匹配(二进制文件已被篡改) |
| 101 | 子进程设置错误(rlimit 等) |
| 102 | 子进程 seccomp 过滤器安装失败 |
| 103 | 子进程 execve 失败(找不到二进制文件,无执行权限) |
| 104 | 子进程 pivot_root 失败 |
| 105 | 子进程 capability 丢弃失败 |
| 125 | Namespace 创建失败(是否以 root 运行?内核是否支持?) |
## 构建与安装
### 要求
- Linux kernel ≥ 5.4(namespaces、seccomp-BPF、pivot_root)
- GCC ≥ 11(已在 11.4、13.2、15.2 上测试)
- 无需外部库 — 仅需标准 C 工具链
### 命令
```
make # build z_jail (~130 KiB PIE binary)
make install # install to /usr/local/bin + man page
make clean # remove build artifacts
make dist # create release tarball
make check # smoke test (--version + --help)
```
该二进制文件被构建为位置无关可执行文件,启用了 `-fstack-protector-strong`、`-D_FORTIFY_SOURCE=2`、完全 RELRO 以及 `-z now`。
### 编译时选项
```
make CC=clang CFLAGS="-O3 -march=native" # custom compiler/flags
```
## 测试
### 快速测试(无需 root)
```
# seccomp 过滤逻辑(8 项测试)
tests/build/seccomp_filter_test
# BLAKE2b 已知答案测试
tests/build/blake2b_known
```
这些测试不需要 root,运行时间在 100 毫秒以内。
### 完整测试套件
```
make -C tests setup # build payloads + test roots
sudo bash tests/run_tests.sh # 17 scenarios
```
需要 root 权限来创建 namespace。测试套件涵盖:
| # | 场景 | 类型 | 测试内容 |
|---|----------|------|---------------|
| 0 | blake2b_regress | 已知答案 | BLAKE2b 实现的正确性 |
| 1 | seccomp_filter | 独立 BPF | 8 个针对 BPF 过滤器逻辑的子测试 |
| 2 | hello_static | ok | 基本静态二进制文件执行 |
| 3 | hello_dynamic | ok | 带有 ld-linux + libc 的动态二进制文件 |
| 4 | execve_replacement | ok | 沙箱中的 execve(被 seccomp 阻止) |
| 5 | fd_inherited_read | ok | stdin/stdout 正确继承 |
| 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 | 阻止 socket 创建 |
| 12 | chroot_escape | killed | 阻止 chroot 系统调用 |
| 13 | double_chroot | killed | 阻止双重 chroot |
| 14 | mount_replay | killed | 阻止 Mount 系统调用 |
| 15 | cpu_exhaust | killed | RLIMIT_NPROC 阻止 fork 炸弹 |
| 16 | signal_parent | killed | 阻止向父进程发送信号 |
| 17 | self_hash | ok | 二进制完整性验证 |
## 性能
数据来自 WSL2(Kali Linux,GCC 15.2.0,-O2 -g):
| 指标 | 数值 |
|--------|-------|
| 二进制文件大小 | ~130 KiB |
| 平均沙箱延迟 | ~8 ms |
| 峰值 RSS | ~4 MiB |
| 代码行数(核心) | ~900 |
| 测试套件运行时间 | ~5 s |
延迟细分(约):clone + namespaces ~3 ms,pivot_root ~2 ms,seccomp + caps ~1 ms,execve ~1 ms,waitpid + 审计 ~1 ms。
## 威胁模型
### 覆盖范围
- 不可信 payload 执行任意本机代码
- 通过 `chroot`、`mount`、`ptrace`、`socket`、`process_vm_writev` 逃逸
- Fork 炸弹、CPU 耗尽(`RLIMIT_CPU`)、内存耗尽(`RLIMIT_AS`)
- 跨 `execve` 的文件描述符泄漏- `setuid` / 动态链接器 / `LD_PRELOAD` 提权
- 移除 seccomp 过滤器或重新启用 capability
### 不覆盖范围
- 允许的系统调用面之外的内核 0-day 漏洞
- 硬件侧信道攻击(Spectre、Meltdown)
- 通过共享 `/proc`、`/sys` 挂载进行的同置 VM 逃逸
- 超出 `CLONE_NEWNET` + 阻止 `socket` 所提供范围的网络出口
- 同级沙箱的资源耗尽(需要 cgroup 支持)
### 假设前提
- 主机 kernel 是未修改的 Linux ≥ 5.4
- `clone(CLONE_NEWNS|CLONE_NEWPID|...)` 成功(需要 `CAP_SYS_ADMIN`)
- 目标二进制文件是静态链接的(或者 `--root` 中提供了动态库)
- 生产部署环境中已配置 `--self-hash=`
## 文档
| 文件 | 描述 |
|------|-------------|
| `README.md` | 本文件 |
| `docs/ARCHITECTURE.md` | 架构概述 |
| `docs/SANDBOX.md` | 逐层沙箱内部机制 |
| `docs/SECCOMP.md` | seccomp-BPF 白名单设计 |
| `docs/AUDIT_SCHEMA.md` | 审计 JSON schema 参考 |
| `docs/THREAT_MODEL.md` | 安全假设与范围 |
| `docs/BLAKE2B.md` | BLAKE2b 实现细节 |
| `docs/BENCHMARKS.md` | 性能基准测试 |
| `docs/BUILD.md` | 构建说明 |
| `docs/adr/` | 架构决策记录 (ADR)(4 份文档) |
| `man/z_jail.1` | Man 手册页 |
| `SECURITY.md` | 安全策略与报告 |
| `CONTRIBUTING.md` | 如何贡献 |
| `CHANGELOG.md` | 发布历史 |
| `ROADMAP.md` | 未来计划 |
| `TODO.md` | 已知差距与计划工作 |
## 路线图
### v1(当前)
- 7 层纵深防御沙箱
- BLAKE2b-256 内容指纹识别
- 审计 JSON 输出
- 17 个测试场景
- man 手册页,命令补全(bash、zsh、fish)
### v2(计划中)
- 外部 seccomp 策略文件(JSON 或 BPF 源码)
- 每个沙箱实例的自定义 namespace 标志
- 通过 CLI 配置系统调用白名单
- 用于 CI 集成的性能分析 hook
- 发布签名(minisign/signify)
## 状态
[](https://github.com/Division-36/Z-Jail/actions/workflows/build.yml)
[](https://github.com/Division-36/Z-Jail/actions/workflows/coverage.yml)
## 许可证
**MIT** — 完整文本请参见 `LICENSE`。
*Z-Jail 构建于 WSL2(Kali Linux,GCC 15.2.0),目标平台为 Linux 5.4+。*
*由 [Division-36](https://github.com/Division-36) 维护。请在[问题跟踪器](https://github.com/Division-36/Z-Jail/issues)报告问题。*