
Z-Jail v1.1.0
Eine leichtgewichtige, mehrschichtige Linux-Sandbox, die Namensräume, pivot_root, seccomp-bpf, Capability-Dropping und eine evidenzbasierte Bewertungsengine (Truthimatics Public Version) für sichere, nachvollziehbare Codeausführung kombiniert.
Z-Jail
Mehrschichtige Sandbox für die Ausführung von nativem Code unter Linux.
Sieben unabhängige Verteidigungsschichten – keine externen Abhängigkeiten, ~73 KiB PIE-Binärdatei.
┌──────────────────────────────────────────────────────┐
│ Z-Jail │
├──────────────────────────────────────────────────────┤
│ Truthimatics PV (evidenzbasierte Bewertungsengine) │
│ Namespaces (mount, pid, net, ipc, uts) │
│ pivot_root (chroot unter Steroiden) │
│ Capabilities (alle entfernen, securebits sperren)│
│ NO_NEW_PRIVS (keine Privilegienerhöhung) │
│ seccomp-BPF (Whitelist: nur 15 Syscalls) │
│ Audit (JSON-Logging + BLAKE2b-Hashing) │
└──────────────────────────────────────────────────────┘
Inhaltsverzeichnis
- Schnellstart
- Warum Z-Jail
- Architektur
- Schichten
- Verwendung
- Build & Installation
- Tests
- Leistung
- Bedrohungsmodell
- Dokumentation
- Roadmap
- Lizenz
Schnellstart
git clone https://github.com/Division-36/Z-Jail.git
cd Z-Jail
make
sudo ./z_jail --root=/pfad/zu/rootfs --seccomp-enforce -- /bin/ls
Das Verzeichnis --root sollte ein minimales Dateisystem mit der Zielbinärdatei und ihren Abhängigkeiten enthalten (für statische Binärdateien reicht die Binärdatei selbst).
Warum Z-Jail
Bestehende Sandbox-Lösungen machen Kompromisse:
| Z-Jail | Firecracker | gVisor | bwrap | nsjail | |
|---|---|---|---|---|---|
| Externe Abhängigkeiten | null | libc, seccomp | Go-Laufzeit | libc | libc, protobuf |
| Binärgröße | ~73 KiB | 20+ MiB | 40+ MiB | ~70 KiB | ~1 MiB |
| VM-Isolation | nein | ja (MicroVM) | nein (Sandbox) | nein | nein |
| seccomp-Whitelist | ja | nein | ja | optional | ja |
| Inhalts-Hashing | ja | nein | nein | nein | nein |
| Audit-JSON | ja | nein | ja | nein | teilweise |
| Build-Komplexität | ein make | komplex | komplex | trivial | moderat |
Z-Jail füllt die Nische zwischen bwrap (minimal, kein seccomp standardmäßig) und nsjail (funktionsreich, schwere Abhängigkeiten). Es ist für CI-Pipelines, CTF-Jail-Challenges und leichtgewichtige Code-Auswertung konzipiert, bei denen eine abgestufte Verteidigung ohne Container-Laufzeitumgebung benötigt wird.
Architektur
Datenfluss
flowchart LR
CLI[CLI-Argumente] --> P[parse_args]
P --> C{clone namespaces}
C -->|Kind| CR[child_run]
C -->|Eltern| W[waitpid]
CR --> RL[setrlimit]
RL --> FD[schließe fds >= 3]
FD --> DUMP[PR_SET_DUMPABLE=0]
DUMP --> PV[pivot_root]
PV --> NNP[PR_SET_NO_NEW_PRIVS]
NNP --> CAP[Capabilities entfernen]
CAP --> SC[seccomp-BPF]
SC --> SIG[Eltern signalisieren]
SIG --> EX[execve-Ziel]
W --> A[Audit-JSON]
A --> EXIT[exit]
Schichten-Reihenfolge
Jede Schicht ist so angeordnet, dass eine spätere Schicht nicht von einer früheren rückgängig gemacht werden kann:
- setrlimit – CPU, Adressraum, Dateianzahl, Prozesse begrenzen, bevor etwas anderes passiert
- fd-Bereinigung – alle geerbten Dateideskriptoren schließen bis auf die Bericht-Pipe
- PR_SET_DUMPABLE=0 – Core-Dumps deaktiviert, /proc/self/mem gesperrt
- pivot_root – vom Host-Dateisystem trennen; altes Root wird lazy ausgehängt
- PR_SET_NO_NEW_PRIVS – keine setuid, keine
capset-Eskalation nach diesem Punkt - drop_caps – alle Capabilities auf null setzen, securebits sperren
- seccomp-BPF – Syscalls auf Whitelist beschränken
- Eltern signalisieren – dem Elternprozess mitteilen, dass die Sandbox bereit ist
- execve – Prozess durch die Zielbinärdatei ersetzen
sequenceDiagram
participant P as Eltern
participant C as Kind
P->>C: clone (NEWNS|NEWPID|NEWNET|NEWIPC|NEWUTS)
Note over C: setrlimit(CPU, AS, NOFILE, NPROC)
Note over C: close(alle 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(alle null) + securebits
Note over C: seccomp(SECCOMP_MODE_FILTER, Whitelist)
C->>P: write(pipe, ready=1)
Note over C: execve(Ziel)
P->>P: waitpid
P->>P: Audit-JSON schreiben
Schichten
1. Truthimatics Public Version
Evidenzbasierte Bewertungsengine. Sammelt gewichtete Beobachtungen über die ausgeführte Binärdatei und ermittelt ein endgültiges Urteil (DETERMINISTIC, REJECT oder UNCERTAIN). Jede Beobachtung hat ein Gewicht; jede einzelne Beobachtung mit einem Gewicht >50 % der Gesamtsumme entscheidet über das Urteil.
2. Namespaces
Fünf Namespaces werden via clone() erstellt:
| Namespace | Flag | Zweck |
|---|---|---|
| Mount | CLONE_NEWNS | Isolierter Dateisystembaum |
| PID | CLONE_NEWPID | Prozess-ID-Raum (Kind ist pid 1) |
| Net | CLONE_NEWNET | Keine Netzwerkschnittstellen |
| IPC | CLONE_NEWIPC | Kein Shared Memory / Semaphore |
| UTS | CLONE_NEWUTS | Separater Hostname |
Erfordert CAP_SYS_ADMIN im ursprünglichen Namespace.
3. pivot_root
Ersetzt das Wurzelverzeichnis des Mount-Namespace durch das --root-Verzeichnis:
- Binde das Wurzelverzeichnis auf sich selbst ein (
MS_BIND|MS_REC) pivot_root(new_root, put_old)– tausche den Mount-Baumchdir("/")– in das neue Wurzelverzeichnis wechselnumount2("/.pivot_old", MNT_DETACH)– altes Root lösenrmdir("/.pivot_old")– aufräumen
Dies ist strikt stärker als chroot(2) – es gibt für den sandboxierten Prozess keine Möglichkeit, zum Host-Root zurückzukehren, selbst mit CLONE_NEWNS von innerhalb der Sandbox (was bereits durch seccomp blockiert ist).
4. Capabilities
Alle Capabilities werden entfernt via:
capset(hdr, data) // data = {0, 0, 0}
prctl(SECBIT_KEEP_CAPS_LOCKED | SECBIT_NO_SETUID_FIXUP | ...)
Der Prozess entfernt setuid/setgid vor capset, sodass die UID-Änderung wirksam wird, während CAP_SETUID noch gehalten wird. Nach capset sind alle Caps weg und die securebits sind gesperrt – eine erneute Aktivierung ist nicht möglich.
5. NO_NEW_PRIVS
prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
Verhindert, dass der Prozess oder seine Kinder neue Privilegien über setuid-Binärdateien, Datei-Capabilities oder LSM-Übergänge erlangen. Irreversibel.
6. seccomp-BPF (Whitelist-v1)
Erlaubnisliste von 15 Syscalls – alles, was nicht auf der Liste steht, bekommt SECCOMP_RET_KILL:
| Syscall | Nummer | Anmerkungen |
|---|---|---|
read | 0 | stdin |
write | 1 | stdout/stderr + Bericht-Pipe |
openat | 257 | Dateizugriff (nicht open) |
close | 3 | — |
lseek | 8 | — |
brk | 12 | Heap-Verwaltung |
mmap | 9 | arg-eingeschränkt: flags & 4 == 0 (kein MAP_SHARED), flags == 0x22 (MAP_PRIVATE|MAP_ANONYMOUS) |
munmap | 11 | — |
execve | 59 | einmaliges exec beim Start |
exit_group | 231 | sauberer Prozessbeendigung |
rt_sigaction | 13 | Signalhandler |
rt_sigprocmask | 14 | Signalmaskierung |
getrandom | 318 | Zufallszahlenquelle |
clock_gettime | 228 | Zeitmessung |
fstat | 5 | Dateimetadaten |
Der BPF-Filter wird dynamisch generiert: Für jeden Whitelist-Eintrag wird eine Sprungkette ausgegeben, die entweder erlaubt (wenn der Syscall übereinstimmt) oder zu KILL durchfällt. Die Architektur wird zuerst geprüft (AUDIT_ARCH_X86_64).
Der Filter wird unabhängig von einem eigenständigen Test (tests/seccomp_filter_test.c, 8/8 bestanden) verifiziert, der via fork+execve Testfälle gegen ein echtes prctl(PR_SET_SECCOMP) ohne Root-Rechte ausführt.
7. Audit
Jede Ausführung erzeugt einen JSON-Auditdatensatz:
{
"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..."
}
Geschrieben nach build/audits/<binary-name>.audit.json. Der content_fingerprint ist ein BLAKE2b-256-Hash der Zielbinärdatei, der vom Elternprozess berechnet wird, nachdem das Kind beendet ist.
Verwendung
z_jail --root=<verz> [--seccomp-enforce] [--self-hash=<hex>]
[--quiet] [--verbose] -- <programm> [argumente...]
| Flag | Beschreibung |
|---|---|
--root=<verz> | Sandbox-Wurzelverzeichnis (erforderlich) |
--seccomp-enforce | seccomp-BPF-Syscall-Whitelist aktivieren |
--self-hash=<hex> | Binärdatei mit erwartetem BLAKE2b-256-Hash verifizieren |
--quiet | Audit-Ausgabe unterdrücken |
--verbose | Debug-Logging aktivieren |
--version | Build-ID anzeigen (Z-Jail/v1+dev) |
--help | Verwendung anzeigen und beenden |
Beispiele
# Statische Binärdatei mit allen Schutzmaßnahmen ausführen
sudo z_jail --root=./roots --seccomp-enforce -- bin/hello_static
# Mit Integritätsprüfung der Binärdatei ausführen
sudo z_jail --root=./roots --seccomp-enforce \
--self-hash=$(sha256sum z_jail | cut -c1-64) -- bin/programm
# Stiller Modus (kein Audit-JSON)
sudo z_jail --root=./roots --quiet -- bin/programm
Exit-Codes
| Code | Bedeutung |
|---|---|
| 0 | Kind normal beendet (Urteil: DETERMINISTIC) |
| 1 | Kind durch Signal getötet (Urteil: REJECT) |
| 2 | Self-Hash: ungültiger Hex-String oder Datei nicht lesbar |
| 3 | Self-Hash: Nichtübereinstimmung (Binärdatei wurde manipuliert) |
| 101 | Fehler bei Kind-Einrichtung (rlimit, etc.) |
| 102 | Installation des seccomp-Filters beim Kind fehlgeschlagen |
| 103 | Kind-execve fehlgeschlagen (Binärdatei nicht gefunden, keine Ausführungsberechtigung) |
| 104 | Kind-pivot_root fehlgeschlagen |
| 105 | Kind-Capability-Entfernung fehlgeschlagen |
| 125 | Namespace-Erstellung fehlgeschlagen (als root ausführen? Kernel-Unterstützung?) |
Build & Installation
Voraussetzungen
- Linux-Kernel ≥ 5.4 (Namespaces, seccomp-BPF, pivot_root)
- GCC ≥ 11 (getestet mit 11.4, 13.2, 15.2)
- Keine externen Bibliotheken – nur die Standard-C-Toolchain
Befehle
make # z_jail bauen (~130 KiB PIE-Binärdatei)
make install # nach /usr/local/bin + Manpage installieren
make clean # Build-Artefakte entfernen
make dist # Release-Tarball erstellen
make check # Rauchtest (--version + --help)
Die Binärdatei wird als Positionsunabhängiges Executable mit -fstack-protector-strong, -D_FORTIFY_SOURCE=2, vollständigem RELRO und -z now gebaut.
Compile-Zeit-Optionen
make CC=clang CFLAGS="-O3 -march=native" # benutzerdefinierter Compiler/Flags
Tests
Schnelltest (kein Root)
# seccomp-Filter-Logik (8 Tests)
tests/build/seccomp_filter_test
# BLAKE2b Known-Answer-Test
tests/build/blake2b_known
Diese benötigen kein Root und laufen in unter 100 ms.
Vollständige Testsuite
make -C tests setup # Payloads + Test-Roots bauen
sudo bash tests/run_tests.sh # 17 Szenarien
Erfordert Root für die Namespace-Erstellung. Die Testsuite deckt ab:
| # | Szenario | Typ | Was wird getestet |
|---|---|---|---|
| 0 | blake2b_regress | Known-Answer | Korrektheit der BLAKE2b-Implementierung |
| 1 | seccomp_filter | eigenständiger BPF | 8 Untertests der BPF-Filterlogik |
| 2 | hello_static | ok | Grundlegende Ausführung statischer Binärdatei |
| 3 | hello_dynamic | ok | Dynamische Binärdatei mit ld-linux + libc |
| 4 | execve_replacement | ok | execve in der Sandbox (durch seccomp blockiert) |
| 5 | fd_inherited_read | ok | stdin/stdout korrekt vererbt |
| 6 | mmap_bad_flags | getötet | mmap mit MAP_SHARED blockiert |
| 7 | mmap_good_allowed | ok | mmap mit MAP_PRIVATE|ANONYMOUS erlaubt |
| 8 | mmap_prot_exec | getötet | mmap mit PROT_EXEC blockiert |
| 9 | mmap_self_modify | getötet | Selbstmodifizierender Code blockiert |
| 10 | ptrace | getötet | ptrace blockiert |
| 11 | socket | getötet | Socket-Erstellung blockiert |
| 12 | chroot_escape | getötet | chroot-Syscall blockiert |
| 13 | double_chroot | getötet | Doppelter chroot blockiert |
| 14 | mount_replay | getötet | Mount-Syscall blockiert |
| 15 | cpu_exhaust | getötet | RLIMIT_NPROC blockiert Fork-Bombe |
| 16 | signal_parent | getötet | Signal an Eltern blockiert |
| 17 | self_hash | ok | Integritätsprüfung der Binärdatei |
Leistung
Gemessen auf WSL2 (Kernel 6.18.x-microsoft-standard-WSL2, Kali Linux),
50 Proben pro Werkzeug, einheitliche Arbeitslast (eine freistehende statische Binärdatei, deren Körper exit_group(0) ist), zeitlich erfasst mit einem getrusage-Testrahmen. Siehe docs/BENCHMARKS.md für die Methodik.
| Metrik | Wert |
|---|---|
| Binärgröße | ~73 KiB ungestrippt (~28 KiB gestrippt) |
| Mittlere Sandbox-Latenz | 5,85 ± 1,45 ms (95 % KI [5,45, 6,25]) |
| Spitzen-RSS | 1,62 MiB |
| Codezeilen (Kern) | ~900 |
Direkter Vergleich (gleicher Host, gleiche Methodik)
| Werkzeug | Latenz Mittelwert ± Standardabw. | Spitzen-RSS | Standard-seccomp |
|---|---|---|---|
| Z-Jail | 5,85 ± 1,45 ms | 1,62 MiB | ja |
| bwrap | 3,56 ± 0,40 ms | 2,19 MiB | nein |
| nsjail | 8,98 ± 1,68 ms | 7,91 MiB | ja |
Unter den getesteten Bedingungen hat Z-Jail die kleinste residente Menge der drei prozessorientierten Sandboxen und eine Latenz zwischen bwrap und nsjail. Bubblewrap ist am schnellsten, führt aber standardmäßig keine seccomp-Filterung durch, macht also weniger Einrichtungsarbeit; Z-Jail installiert bei jedem Lauf eine seccomp-Whitelist, entfernt Capabilities und führt pivot_root aus. gVisor (runsc) stürzt auf diesem WSL2-Kernel ab und konnte nicht gemessen werden; Firecracker isoliert über eine MicroVM (VM-Kaltstart, eine andere Metrik) und ist von der Fork-zu-Exec-Tabelle ausgeschlossen. Dies sind Einzel-Host-Zahlen – behandeln Sie sie als relativ.
Hinweis: Die älteren dokumentierten Werte (~8 ms, ~4 MiB, ~130 KiB) stimmen nicht mit einem aktuellen
make-Build (~73 KiB, ~5,9 ms) überein und scheinen ungenau gewesen zu sein; die obigen Zahlen wurden an dieser Codebasis neu gemessen. Truthimatics ist immer noch Teil des Codes und wurde nicht entfernt (die altenaxiom_jail-Berichte verwendeten einfach einen anderen Binärnamen und Testrahmen). Ein während des Benchmarkings gefundener Mount-Propagation-Fehler wurde insrc/sandbox.cbehoben (MS_REC|MS_PRIVATEvor dem Bind-Mount); das rekursive Remount trägt zur gemessenen Latenz bei.
Bedrohungsmodell
Im Scope
- Beliebige native Codeausführung durch eine nicht vertrauenswürdige Nutzlast
- Ausbruch via
chroot,mount,ptrace,socket,process_vm_writev - Fork-Bomben, CPU-Auslastung (
RLIMIT_CPU), Speicherüberlastung (RLIMIT_AS) - Dateideskriptor-Leaks über
execve setuid/ dynamischer Linker /LD_PRELOAD-Eskalation- Entfernung des seccomp-Filters oder erneute Aktivierung von Capabilities
Nicht im Scope
- Kernel-Zero-Days außerhalb der erlaubten Syscall-Oberfläche
- Hardware-Seitenkanäle (Spectre, Meltdown)
- Ausbruch aus ko-lokalisierter VM via gemeinsame
/proc,/sys-Mounts - Netzwerkausgang über das hinaus, was
CLONE_NEWNET+ blockiertessocketbietet - Ressourcenaushungerung von Geschwister-Sandboxen (benötigt cgroup-Unterstützung)
Annahmen
- Host-Kernel ist unveränderter Linux ≥ 5.4
clone(CLONE_NEWNS|CLONE_NEWPID|...)ist erfolgreich (erfordertCAP_SYS_ADMIN)- Zielbinärdatei ist statisch gelinkt (oder dynamische Bibliotheken sind in
--rootverfügbar) --self-hash=<hex>ist in Produktionsumgebungen konfiguriert
Dokumentation
| Datei | Beschreibung |
|---|---|
README.md | Diese Datei |
docs/ARCHITECTURE.md | Architekturübersicht |
docs/SANDBOX.md | Schicht-für-Schicht-Sandbox-Interna |
docs/SECCOMP.md | seccomp-BPF-Whitelist-Design |
docs/AUDIT_SCHEMA.md | Audit-JSON-Schema-Referenz |
docs/THREAT_MODEL.md | Sicherheitsannahmen und -umfang |
docs/BLAKE2B.md | BLAKE2b-Implementierungsdetails |
docs/BENCHMARKS.md | Leistungs-Benchmarks |
docs/BUILD.md | Bauanleitung |
docs/adr/ | Architekturentscheidungsdokumente (4 Dokumente) |
man/z_jail.1 | Manpage |
SECURITY.md | Sicherheitsrichtlinie und Meldung |
CONTRIBUTING.md | Wie man beiträgt |
CHANGELOG.md | Versionsgeschichte |
ROADMAP.md | Zukünftige Pläne |
TODO.md | Bekannte Lücken und geplante Arbeiten |
Roadmap
v1 (aktuell)
- 7-schichtige abgestufte Verteidigungssandbox
- BLAKE2b-256-Inhalts-Fingerprinting
- Audit-JSON-Ausgabe
- 17 Testszenarien
- Manpage, Vervollständigungen (bash, zsh, fish)
v2 (geplant)
- Externe seccomp-Richtliniendatei (JSON oder BPF-Quelle)
- Anpassbare Namespace-Flags pro Sandbox-Instanz
- Konfigurierbare Syscall-Whitelist über CLI
- Performance-Profiling-Hooks für CI-Integration
- Release-Signierung (minisign/signify)
Status
Lizenz
MIT – siehe LICENSE für den vollständigen Text.
Z-Jail wurde auf WSL2 (Kali Linux, GCC 15.2.0) erstellt, Ziel ist Linux 5.4+. Gepflegt von Division-36. Fehler bitte im Issue-Tracker melden.