セキュリティ
Show HN: Z-Jail – 7つの防御レイヤーとゼロ依存性を持つ130KBのLinuxサンドボックス-C99
Show HN: Z-Jail – A 130 KB Linux sandbox-C99 with 7 defense layers and zero deps (github.com)
要約
Z-Jailは、Linux上でネイティブコードを実行するための多層サンドボックスで、外部依存性がなく、約130KBのPIEバイナリです。7つの独立した防御レイヤー(名前空間、pivot_root、機能、NO_NEW_PRIVS、seccomp-BPF、監査、そして真理判定エンジン)を備えており、最小限のリソースで高いセキュリティを提供します。CIパイプラインやCTFチャレンジ、軽量なコード評価など、コンテナランタイムを必要としない防御が必要な場合に最適です。
全文翻訳
Z-Jail Linux上でネイティブコードを実行するための多層サンドボックス。7つの独立した防御レイヤー — 外部依存性なし、約130 KiB PIEバイナリ。
┌──────────────────────────────────────────────────────┐
│ Z-Jail │
├──────────────────────────────────────────────────────┤
│ Truthimatics Public Version (根拠に基づく判定エンジン) │
│ Namespaces (mount, pid, net, ipc, uts) │
│ pivot_root (chrootを強化したもの) │
│ Capabilities (すべて破棄し、securebitsをロック) │
│ NO_NEW_PRIVS (特権昇格なし) │
│ seccomp-BPF (ホワイトリスト: 15個のシステムコールのみ) │
│ Audit (JSONロギング + BLAKE2bハッシュ) │
└──────────────────────────────────────────────────────┘
目次
クイックスタート
Z-Jailの理由
アーキテクチャ
レイヤー
使い方
ビルドとインストール
テスト
パフォーマンス
脅威モデル
ドキュメント
ロードマップ
ライセンス
クイックスタート
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 |
|---|---|---|---|---|---|
| 外部依存性 | zero | libc, seccomp | Go runtime | libc | libc, protobuf |
| バイナリサイズ | ~130 KiB | 20+ MiB | 40+ MiB | ~70 KiB | ~1 MiB |
| VM隔離 | no | yes (microVM) | no (sandbox) | no | no |
| seccompホワイトリスト | yes | no | yes | optional | yes |
| コンテンツハッシュ | yes | no | no | no | no |
| 監査JSON | yes | no | yes | no | partial |
| ビルドの複雑さ | one make | complex | complex | trivial | moderate |
Z-Jailは、bwrap(最小限だがseccompがデフォルトではない)とnsjail(機能豊富だが重い依存性がある)の間のニッチを埋めます。CIパイプライン、CTFのjailチャレンジ、およびコンテナランタイムを必要としない多層防御が必要な軽量コード評価向けに設計されています。
アーキテクチャ
データフロー
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]
レイヤーの順序
各レイヤーは、後のレイヤーが前のレイヤーによって元に戻されないように順序付けられています:
setrlimit — 他の何よりも先にCPU、アドレス空間、ファイル数、プロセスを制限
fd scrub — レポートパイプを除くすべての継承されたfdを閉じる
PR_SET_DUMPABLE=0 — コアダンプ無効、/proc/self/memロックダウン
pivot_root — ホストファイルシステムから分離;古いルートは遅延アンマウント
PR_SET_NO_NEW_PRIVS — この時点以降、setuid、capsetによる特権昇格なし
drop_caps — すべての機能をゼロにし、securebitsをロック
seccomp-BPF — システムコールをホワイトリストのみに制限
signal parent — サンドボックスの準備ができたことを親に通知
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
レイヤー
1. Truthimatics Public Version
根拠に基づく判定エンジン。実行されたバイナリに関する重み付けされた観測値を収集し、最終的な判定(DETERMINISTIC、REJECT、またはUNCERTAIN)を決定します。各観測値には重みがあり、合計の50%以上の重みを持つ単一の観測値が判定を決定します。
2. 名前空間
clone()を介して5つの名前空間が作成されます:
| 名前空間 | フラグ | 目的 |
|----------|----------------|-------------------------|
| Mount | CLONE_NEWNS | 独立したファイルシステムツリー |
| PID | CLONE_NEWPID | プロセスID空間 (子プロセスはpid 1) |
| Net | 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. Capabilities
すべての機能は以下によって破棄されます:
capset(hdr, data) // data = {0, 0, 0}
prctl(SECBIT_KEEP_CAPS_LOCKED | SECBIT_NO_SETUID_FIXUP | ...)
プロセスはcapsetの前にsetuid/setgidを破棄し、CAP_SETUIDがまだ保持されている間にuid変更が有効になります。capsetの後、すべてのキャップがなくなり、securebitsがロックされます — 再有効化は不可能です。
5. NO_NEW_PRIVS
prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
プロセスまたはその子が、setuidバイナリ、ファイル機能、またはLSM遷移を介して新しい特権を獲得するのを防ぎます。不可逆的です。
6. seccomp-BPF (whitelist-v1)
15個のシステムコールの許可リスト — リストにないものはすべてSECCOMP_RET_KILLを受け取ります:
| システムコール | 番号 | 注記 |
|----------------|----|--------------------|
| read | 0 | stdin |
| write | 1 | stdout/stderr + report pipe |
| openat | 257| ファイルアクセス (openではない) |
| close | 3 | — |
| lseek | 8 | — |
| brk | 12 | ヒープ管理 |
| mmap | 9 | 引数制限: flags & 4 == 0 (no MAP_SHARED), flags == 0x22 (MAP_PRIVATE|MAP_ANONYMOUS) |
| munmap | 11 | — |
| execve | 59 | 起動時に1回実行 |
| exit_group | 231| クリーンなプロセス終了 |
| rt_sigaction | 13 | シグナルハンドラ |
| rt_sigprocmask | 14 | シグナルマスキング |
| getrandom | 318| 乱数ソース |
| clock_gettime | 228| タイミング |
| fstat | 5 | ファイルメタデータ |
BPFフィルターは動的に生成されます:各ホワイトリストエントリに対して、システムコールが一致すれば許可し、そうでなければKILLにフォールスルーするジャンプチェーンが生成されます。アーキテクチャが最初にチェックされます (AUDIT_ARCH_X86_64)。フィルターは、rootを必要とせずに実際のprctl(PR_SET_SECCOMP)に対してテストケースをfork+execveするスタンドアロンテスト (tests/seccomp_filter_test.c, 8/8 pass) によって独立して検証されます。
7. Audit
すべての実行は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=<dir> [--seccomp-enforce] [--self-hash=<hex>] [--quiet] [--verbose] -- <program> [args...]
| フラグ | 説明 |
|-----------------------|--------------------------------------------|
| --root=<dir> | サンドボックスのルートディレクトリ (必須) |
| --seccomp-enforce | seccomp-BPFシステムコールホワイトリストを有効にする |
| --self-hash=<hex> | バイナリが期待されるBLAKE2b-256ハッシュと一致することを確認 |
| --quiet | 監査出力抑制 |
| --verbose | デバッグロギングを有効にする |
| --version | ビルドIDを表示 (Z-Jail/v1+dev) |
| --help | 使い方を表示して終了 |
例
# すべての保護を有効にして静的バイナリを実行
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
終了コード
| コード | 意味 |
|------|----------------------------------------------|
| 0 | 子プロセスが正常に終了 (判定: DETERMINISTIC) |
| 1 | 子プロセスがシグナルによって終了 (判定: REJECT)|
| 2 | セルフハッシュ: 不正なhex文字列またはファイル読み込み不可 |
| 3 | セルフハッシュ: 不一致 (バイナリが改ざんされている) |
| 101 | 子プロセス設定エラー (rlimitなど) |
| 102 | 子seccompフィルターインストール失敗 |
| 103 | 子execve失敗 (バイナリが見つからない、実行権限なし) |
| 104 | 子pivot_root失敗 |
| 105 | 子capabilityドロップ失敗 |
| 125 | 名前空間作成失敗 (rootで実行?カーネルサポート) |