返回

文章详情

展示 HN: Z-Jail – 一个 130 KB 的 Linux 沙箱-C99,具有 7 层防御和零依赖

Hacker News2026年7月1日 19:18

用于本地代码执行的多层沙箱。七个独立的防御层 — 没有外部依赖,约 130 KiB 的自执行二进制文件。 ┌──────────────────────────────────────────────────────┐ │ Z-Jail │ ├──────────────────────────────────────────────────────┤ │ 真理数学公共版本 (基于证据的裁决引擎) │ │ 命名空间 (挂载, pid, net, ipc, uts) │ │ pivot_root (强化版 chroot) │ │ 能力 (丢弃所有, 锁定安全位) │ │ NO_NEW_PRIVS (没有特权提升) │ │ seccomp-BPF (白名单: 仅 15 个系统调用) │ │ 审计 (JSON 日志 + BLAKE2b 哈希) │ └──────────────────────────────────────────────────────┘ 目录 快速开始 为什么选择 Z-Jail 架构 层 用法 构建与安装 测试 性能 威胁模型 文档 路线图 许可证 快速开始 ```bash 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 管道、CTF 监狱挑战和轻量级代码评估,在这些情况下你需要深层防御而不拉入容器运行时。 架构 数据流 ```mermaid flowchart LR CLI[CLI args] --> P[parse_args] P --> C{clone namespaces} C -->|子进程| CR[child_run] C -->|父进程| 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] ``` 加载层排序 每一层的顺序是这样设定的,以确保后面的层不能被前面的层撤销: - setrlimit — 在其他事情之前限制 CPU、地址空间、文件数和进程 - fd scrub — 关闭所有继承的 fds,除了报告管道 - PR_SET_DUMPABLE=0 — 禁用核心转储,锁定 /proc/self/mem - pivot_root — 从主机文件系统分离;旧根文件系统惰性卸载 - PR_SET_NO_NEW_PRIVS — 从此时起无 setuid,无 capset 提升 - drop_caps — 将所有能力归零,锁定安全位 - seccomp-BPF — 将系统调用限制为仅白名单 - signal parent — 告诉父进程沙箱已准备好 - execve — 用目标二进制文件替换进程 加载层 1. **真理数学公共版本** 基于证据的裁决引擎。收集关于被执行二进制的加权观察,并确定最终裁决(**确定的**、**拒绝**或**不确定**)。每个观察都有一个权重;任何权重大于总权重 50% 的单个观察决定裁决。 2. **命名空间** 通过 clone() 创建的五个命名空间: | 命名空间 | 标志 | 目的 | |-----------|-------------------|-------------------| | 挂载 | CLONE_NEWNS | 隔离文件系统树 | | PID | CLONE_NEWPID | 进程 ID 空间(子进程是 pid 1) | | 网络 | CLONE_NEWNET | 无网络接口 | | IPC | CLONE_NEWIPC | 没有共享内存 / 信号量 | | UTS | CLONE_NEWUTS | 单独的主机名 | 在初始命名空间中需要 CAP_SYS_ADMIN。 3. **pivot_root** 用 `--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 阻止)。 4. **能力** 通过以下方式丢弃所有能力: ``` capset (hdr, data) // data = {0, 0, 0} prctl (SECBIT_KEEP_CAPS_LOCKED | SECBIT_NO_SETUID_FIXUP | ...) ``` 进程在 capset 之前丢弃 setuid/setgid,以便在仍持有 CAP_SETUID 的情况下生效 uid 更改。capset 之后,所有能力将消失且安全位被锁定 — 无法重新启用。 5. **NO_NEW_PRIVS** ``` prctl (PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0); ``` 防止该进程或其子进程获取新特权。

赞助内容

NordVPN Next-gen Antivirus

本站免费、广告极少。如果觉得有帮助,可以请我们喝杯咖啡 —— 任何金额都对持续运营有实际帮助。

请我喝杯咖啡