
Лёгкая многоуровневая Linux-песочница, объединяющая namespaces, pivot_root, seccomp-bpf, сброс capabilities и основанный на доказательствах механизм вынесения вердикта (Truthimatics Public Version) для безопасного и аудируемого выполнения кода.
Многослойная песочница для выполнения нативного кода на Linux.
Семь независимых уровней защиты — без внешних зависимостей, PIE-бинарник ~73 KiB.
┌──────────────────────────────────────────────────────┐
│ 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) │
└──────────────────────────────────────────────────────┘
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 | Firecracker | gVisor | bwrap | nsjail | |
|---|---|---|---|---|---|
| Внешние зависимости | нет | libc, seccomp | Go runtime | libc | libc, protobuf |
| Размер бинарника | ~73 KiB | 20+ MiB | 40+ MiB | ~70 KiB | ~1 MiB |
| Изоляция VM | нет | да (microVM) | нет (песочница) | нет | нет |
| Белый список seccomp | да | нет | да | опционально | да |
| Хеширование содержимого | да | нет | нет | нет | нет |
| Аудит JSON | да | нет | да | нет | частично |
| Сложность сборки | один make | сложная | сложная | тривиальная | умеренная |
Z-Jail занимает нишу между bwrap (минимализм, без seccomp по умолчанию) и nsjail (много возможностей, тяжёлые зависимости). Он создан для CI-конвейеров, 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]
Каждый слой упорядочен так, чтобы более поздний слой нельзя было отменить более ранним:
/proc/self/mem заблокированcapset после этой точки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
Движок вынесения вердиктов на основе доказательств. Собирает взвешенные наблюдения о выполненном бинарнике и выносит итоговый вердикт (DETERMINISTIC, REJECT или UNCERTAIN). Каждое наблюдение имеет вес; любое отдельное наблюдение с весом >50% от общего определяет вердикт.
Пять пространств имён создаются через clone():
| Пространство имён | Флаг | Назначение |
|---|---|---|
| Mount | CLONE_NEWNS | Изолированное дерево файловой системы |
| PID | CLONE_NEWPID | Пространство идентификаторов процессов (дочерний процесс — pid 1) |
| Net | CLONE_NEWNET | Нет сетевых интерфейсов |
| IPC | CLONE_NEWIPC | Нет общей памяти / семафоров |
| UTS | CLONE_NEWUTS | Отдельное имя хоста |
Требуется 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 | ...)
Процесс сбрасывает setuid/setgid до capset, чтобы смена uid вступила в силу, пока CAP_SETUID ещё удержан. После capset все capabilities исчезают, а securebits заблокированы — повторное включение невозможно.
prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
Предотвращает получение процессом или его потомками новых привилегий через setuid-бинарники, file capabilities или переходы LSM. Необратимо.
Белый список из 15 системных вызовов — всё, чего нет в списке, получает SECCOMP_RET_KILL: