
Z-Jail v1.1.0
Лёгкая многоуровневая Linux-песочница, объединяющая namespaces, pivot_root, seccomp-bpf, сброс capabilities и основанный на доказательствах механизм вынесения вердикта (Truthimatics Public Version) для безопасного и аудируемого выполнения кода.
Z-Jail
Многослойная песочница для выполнения нативного кода на 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) │
└──────────────────────────────────────────────────────┘
Содержание
- Быстрый старт
- Зачем 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 | |
|---|---|---|---|---|---|
| Внешние зависимости | нет | 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]
Порядок слоёв
Каждый слой упорядочен так, чтобы более поздний слой нельзя было отменить более ранним:
- setrlimit — ограничить CPU, адресное пространство, число файлов и процессов до всего остального
- fd scrub — закрыть все унаследованные файловые дескрипторы, кроме канала отчёта
- PR_SET_DUMPABLE=0 — отключены core dump,
/proc/self/memзаблокирован - pivot_root — отсоединение от файловой системы хоста; старый корень размонтируется лениво
- PR_SET_NO_NEW_PRIVS — никаких setuid и повышения привилегий через
capsetпосле этой точки - drop_caps — обнулить все capabilities, заблокировать 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
Движок вынесения вердиктов на основе доказательств. Собирает взвешенные наблюдения о выполненном бинарнике и выносит итоговый вердикт (DETERMINISTIC, REJECT или UNCERTAIN). Каждое наблюдение имеет вес; любое отдельное наблюдение с весом >50% от общего определяет вердикт.
2. Пространства имён
Пять пространств имён создаются через clone():
| Пространство имён | Флаг | Назначение |
|---|---|---|
| Mount | CLONE_NEWNS | Изолированное дерево файловой системы |
| PID | CLONE_NEWPID | Пространство идентификаторов процессов (дочерний процесс — 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)
Все 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 заблокированы — повторное включение невозможно.
5. NO_NEW_PRIVS
prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
Предотвращает получение процессом или его потомками новых привилегий через setuid-бинарники, file capabilities или переходы LSM. Необратимо.
6. seccomp-BPF (whitelist-v1)
Белый список из 15 системных вызовов — всё, чего нет в списке, получает SECCOMP_RET_KILL:
| Системный вызов | Номер | Примечания |
|---|---|---|
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 | одиночный exec при запуске |
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 успешных), который запускает тестовые случаи через fork+execve с реальным prctl(PR_SET_SECCOMP) без необходимости 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/<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 | Показать идентификатор сборки (Z-Jail/v1+dev) |
--help | Показать справку и выйти |
Примеры
# Run a static binary with all protections
sudo z_jail --root=./roots --seccomp-enforce -- bin/hello_static
# Run with binary integrity verification
sudo z_jail --root=./roots --seccomp-enforce \
--self-hash=$(sha256sum z_jail | cut -c1-64) -- bin/program
# Quiet mode (no audit JSON)
sudo z_jail --root=./roots --quiet -- bin/program
Коды возврата
| Код | Значение |
|---|---|
| 0 | Дочерний процесс завершился нормально (вердикт: DETERMINISTIC) |
| 1 | Дочерний процесс был убит сигналом (вердикт: REJECT) |
| 2 | Self-hash: неверная hex-строка или файл не читается |
| 3 | Self-hash: несовпадение (бинарник был изменён) |
| 101 | Ошибка настройки дочернего процесса (rlimit и т.п.) |
| 102 | Не удалась установка seccomp-фильтра в дочернем процессе |
| 103 | Ошибка execve в дочернем процессе (бинарник не найден, нет прав на исполнение) |
| 104 | Ошибка pivot_root в дочернем процессе |
| 105 | Ошибка сброса capabilities в дочернем процессе |
| 125 | Не удалось создать пространства имён (запуск от root? поддержка ядра?) |
Сборка и установка
Требования
- Ядро Linux ≥ 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)
Бинарник собирается как Position Independent Executable с -fstack-protector-strong, -D_FORTIFY_SOURCE=2, полным RELRO и -z now.
Параметры времени компиляции
make CC=clang CFLAGS="-O3 -march=native" # custom compiler/flags
Тестирование
Быстрый тест (без root)
# seccomp filter logic (8 tests)
tests/build/seccomp_filter_test
# BLAKE2b known-answer test
tests/build/blake2b_known
Эти тесты не требуют root и выполняются менее чем за 100 мс.
Полный набор тестов
make -C tests setup # build payloads + test roots
sudo bash tests/run_tests.sh # 17 scenarios
Требуется root для создания пространств имён. Набор тестов покрывает:
| # | Сценарий | Тип | Что проверяет |
|---|---|---|---|
| 0 | blake2b_regress | известный ответ | Корректность реализации BLAKE2b |
| 1 | seccomp_filter | автономный BPF | 8 под-тестов логики BPF-фильтра |
| 2 | hello_static | успех | Базовое выполнение статического бинарника |
| 3 | hello_dynamic | успех | Динамический бинарник с ld-linux + libc |
| 4 | execve_replacement | успех | execve в песочнице (заблокирован seccomp) |
| 5 | fd_inherited_read | успех | stdin/stdout наследуются корректно |
| 6 | mmap_bad_flags | блокирован | mmap с MAP_SHARED заблокирован |
| 7 | mmap_good_allowed | успех | mmap с MAP_PRIVATE|ANONYMOUS разрешён |
| 8 | mmap_prot_exec | блокирован | mmap с PROT_EXEC заблокирован |
| 9 | mmap_self_modify | блокирован | Самомодифицирующийся код заблокирован |
| 10 | ptrace | блокирован | ptrace заблокирован |
| 11 | socket | блокирован | Создание сокета заблокировано |
| 12 | chroot_escape | блокирован | Системный вызов chroot заблокирован |
| 13 | double_chroot | блокирован | Двойной chroot заблокирован |
| 14 | mount_replay | блокирован | Системный вызов mount заблокирован |
| 15 | cpu_exhaust | блокирован | RLIMIT_NPROC блокирует fork bomb |
| 16 | signal_parent | блокирован | Сигнал родителю заблокирован |
| 17 | self_hash | успех | Проверка целостности бинарника |
Производительность
Измерено на WSL2 (ядро 6.18.x-microsoft-standard-WSL2, Kali Linux),
50 выборок на инструмент, единообразная нагрузка (freestanding-статический бинарник, тело
которого — exit_group(0)), замеры с помощью обвязки getrusage. См.
docs/BENCHMARKS.md — методология.
| Метрика | Значение |
|---|---|
| Размер бинарника | ~73 KiB без удаления символов (~28 KiB со stripped) |
| Средняя задержка песочницы | 5.85 ± 1.45 ms (95% ДИ [5.45, 6.25]) |
| Пиковый RSS | 1.62 MiB |
| Строк кода (ядро) | ~900 |
Сравнение (тот же хост, та же методология)
| Инструмент | Средняя задержка ± ст. откл. | Пиковый RSS | Seccomp по умолчанию |
|---|---|---|---|
| Z-Jail | 5.85 ± 1.45 ms | 1.62 MiB | да |
| bwrap | 3.56 ± 0.40 ms | 2.19 MiB | нет |
| nsjail | 8.98 ± 1.68 ms | 7.91 MiB | да |
В протестированных условиях Z-Jail имеет наименьший резидентный набор среди трёх песочниц уровня процесса и задержку между bwrap и nsjail. Bubblewrap — самый быстрый, но по умолчанию не выполняет фильтрацию seccomp, поэтому делает меньше подготовительной работы; Z-Jail при каждом запуске устанавливает белый список seccomp, сбрасывает capabilities и выполняет pivot_root. gVisor (runsc) падает с segfault на этом ядре WSL2 и не мог быть измерен; Firecracker изолирует через microVM (холодный старт VM — другая метрика) и исключён из таблицы fork-to-exec. Это цифры одного хоста — относитесь к ним как к относительным.
Примечание: более старые задокументированные цифры (~8 ms, ~4 MiB, ~130 KiB) не соответствуют текущей сборке
make(~73 KiB, ~5.9 ms) и, по-видимому, были неточными; цифры выше пересчитаны на этой кодовой базе. Truthimatics по-прежнему часть кода и не удалялась (старые отчётыaxiom_jailпросто использовали другое имя бинарника и обвязку). Баг с распространением монтирований, найденный во время бенчмаркинга, был исправлен вsrc/sandbox.c(MS_REC|MS_PRIVATEперед bind mount); рекурсивный remount вносит часть измеренной задержки.
Модель угроз
В зоне действия
- Выполнение произвольного нативного кода недоверенной нагрузкой
- Побег через
chroot,mount,ptrace,socket,process_vm_writev - Форк-бомбы, исчерпание CPU (
RLIMIT_CPU), исчерпание памяти (RLIMIT_AS) - Утечки файловых дескрипторов через
execve - Эскалация через
setuid/ динамический линковщик /LD_PRELOAD - Удаление seccomp-фильтра или повторное включение capabilities
Вне зоны действия
- Zero-day уязвимости ядра за пределами разрешённой поверхности системных вызовов
- Аппаратные побочные каналы (Spectre, Meltdown)
- Побег из совмещённой VM через общие
/proc,/sysмонтирования - Сетевой трафик за пределами того, что обеспечивают
CLONE_NEWNET+ заблокированныйsocket - Истощение ресурсов соседних песочниц (требуется поддержка cgroup)
Допущения
- Ядро хоста — немодифицированный Linux ≥ 5.4
clone(CLONE_NEWNS|CLONE_NEWPID|...)успешен (требуетсяCAP_SYS_ADMIN)- Целевой бинарник статически слинкован (или динамические библиотеки доступны в
--root) --self-hash=<hex>настроен в production-развёртываниях
Документация
| Файл | Описание |
|---|---|
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 | Известные пробелы и запланированные работы |
План развития
v1 (текущая версия)
- Песочница с 7 уровнями защиты (defence-in-depth)
- BLAKE2b-256 хеширование содержимого
- Вывод аудита в JSON
- 17 тестовых сценариев
- man-страница, автодополнения (bash, zsh, fish)
v2 (планируется)
- Внешний файл политики seccomp (JSON или исходник BPF)
- Пользовательские флаги namespace для каждого экземпляра песочницы
- Настраиваемый белый список системных вызовов через CLI
- Хуки профилирования производительности для интеграции с CI
- Подпись релизов (minisign/signify)
Статус
Лицензия
MIT — полный текст см. в LICENSE.
Z-Jail был создан на WSL2 (Kali Linux, GCC 15.2.0), ориентирован на Linux 5.4+. Поддерживается Division-36. Сообщайте об ошибках в issue tracker.