
Un sandbox Linux leggero e multilivello che combina namespace, pivot_root, seccomp-bpf, riduzione delle capacità e un motore di verdetto basato su prove (Versione Pubblica di Truthimatics) per un'esecuzione sicura e verificabile del codice.
Sandbox multistrato per l'esecuzione di codice nativo su Linux.
Sette livelli di difesa indipendenti — nessuna dipendenza esterna, binario PIE di circa 73 KiB.
┌──────────────────────────────────────────────────────┐
│ Z-Jail │
├──────────────────────────────────────────────────────┤
│ Truthimatics PV (motore di verdetto basato su prove) │
│ Namespaces (mount, pid, net, ipc, uts) │
│ pivot_root (chroot potenziato) │
│ Capacità (elimina tutto, blocca securebits) │
│ NO_NEW_PRIVS (nessuna escalation di privilegi) │
│ seccomp-BPF (whitelist: solo 15 syscall) │
│ Audit (logging JSON + hashing BLAKE2b) │
└──────────────────────────────────────────────────────┘
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
La directory --root deve contenere un filesystem minimo con il binario target e le sue dipendenze (per binari statici è sufficiente il binario stesso).
Le soluzioni di sandboxing esistenti fanno compromessi:
Z-Jail colma il divario tra bwrap (minimale, nessun seccomp di default) e nsjail (ricco di funzionalità, dipendenze pesanti). È progettato per pipeline CI, sfide CTF jail e valutazione leggera di codice dove serve difesa in profondità senza tirare dentro un runtime container.
flowchart LR
CLI[argomenti CLI] --> P[parse_args]
P --> C{clone namespaces}
C -->|figlio| CR[child_run]
C -->|genitore| W[waitpid]
CR --> RL[setrlimit]
RL --> FD[chiudi fd >= 3]
FD --> DUMP[PR_SET_DUMPABLE=0]
DUMP --> PV[pivot_root]
PV --> NNP[PR_SET_NO_NEW_PRIVS]
NNP --> CAP[elimina capacità]
CAP --> SC[seccomp-BPF]
SC --> SIG[segnala genitore]
SIG --> EX[execve target]
W --> A[audit JSON]
A --> EXIT[exit]
Ogni livello è ordinato in modo che un livello successivo non possa essere annullato da uno precedente:
capset dopo questo puntosequenceDiagram
participant P as Genitore
participant C as Figlio
P->>C: clone (NEWNS|NEWPID|NEWNET|NEWIPC|NEWUTS)
Note over C: setrlimit(CPU, AS, NOFILE, NPROC)
Note over C: close(tutti i fd > 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(tutti 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: scrivi audit JSON
Motore di verdetto basato su prove. Raccoglie osservazioni pesate sul binario eseguito e determina un verdetto finale (DETERMINISTIC, REJECT o UNCERTAIN). Ogni osservazione ha un peso; ogni singola osservazione con peso >50% del totale decide il verdetto.
Vengono creati cinque namespace tramite clone():
Richiede CAP_SYS_ADMIN nel namespace iniziale.
Sostituisce la root del mount namespace con la directory --root:
MS_BIND|MS_REC)pivot_root(new_root, put_old) — scambia l'albero dei mountchdir("/") — si sposta nella nuova rootumount2("/.pivot_old", MNT_DETACH) — distacca la vecchia rootrmdir("/.pivot_old") — puliziaQuesto è strettamente più forte di chroot(2) — non c'è modo per il processo in sandbox di scappare di nuovo alla root host, nemmeno con CLONE_NEWNS dall'interno della sandbox (cosa già bloccata da seccomp).
Tutte le capacità vengono eliminate tramite:
capset(hdr, data) // data = {0, 0, 0}
prctl(SECBIT_KEEP_CAPS_LOCKED | SECBIT_NO_SETUID_FIXUP | ...)
Il processo elimina setuid/setgid prima di capset in modo che il cambio di uid avvenga mentre CAP_SETUID è ancora attiva. Dopo capset, tutte le capacità sono sparite e i securebits sono bloccati — nessuna riattivazione è possibile.
prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
Impedisce al processo o ai suoi figli di ottenere nuovi privilegi tramite binari setuid, capacità dei file o transizioni LSM. Irreversibile.
Lista di consenso di 15 syscall — tutto ciò che non è nella lista riceve SECCOMP_RET_KILL:
Il filtro BPF viene generato dinamicamente: per ogni voce della whitelist, viene emessa una catena di salti che o permette (se la syscall corrisponde) o cade in KILL. L'architettura viene controllata prima (AUDIT_ARCH_X86_64).
Il filtro è verificato indipendentemente da un test standalone (tests/seccomp_filter_test.c, 8/8 passati) che fork+exec dei casi di test contro un vero prctl(PR_SET_SECCOMP) senza bisogno di root.
Ogni esecuzione produce un record JSON di audit:
{
"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..."
}
Scritto in build/audits/<binary-name>.audit.json. Il content_fingerprint è un hash BLAKE2b-256 del binario target, calcolato dal genitore dopo che il figlio ha finito.
z_jail --root=<dir> [--seccomp-enforce] [--self-hash=<hex>]
[--quiet] [--verbose] -- <program> [args...]
# Esegue un binario statico con tutte le protezioni
sudo z_jail --root=./roots --seccomp-enforce -- bin/hello_static
# Esecuzione con verifica di integrità del binario
sudo z_jail --root=./roots --seccomp-enforce \
--self-hash=$(sha256sum z_jail | cut -c1-64) -- bin/program
# Modalità silenziosa (nessun JSON di audit)
sudo z_jail --root=./roots --quiet -- bin/program
make # builda z_jail (~130 KiB binario PIE)
make install # installa in /usr/local/bin + pagina man
make clean # rimuove artefatti di build
make dist # crea tarball di rilascio
make check # test fumogeno (--version + --help)
Il binario è compilato come Position Independent Executable con -fstack-protector-strong, -D_FORTIFY_SOURCE=2, RELRO completo e -z now.
make CC=clang CFLAGS="-O3 -march=native" # compilatore/flags personalizzati
# logica del filtro seccomp (8 test)
tests/build/seccomp_filter_test
# test a risposta nota BLAKE2b
tests/build/blake2b_known
Questi non necessitano di root e girano in meno di 100 ms.
make -C tests setup # builda payload + root di test
sudo bash tests/run_tests.sh # 17 scenari
Richiede root per la creazione dei namespace. La suite di test copre:
Misurate su WSL2 (kernel 6.18.x-microsoft-standard-WSL2, Kali Linux),
50 campioni per strumento, carico di lavoro uniforme (un binario statico standalone il cui
corpo è exit_group(0)), temporizzato con un harness getrusage. Vedi
docs/BENCHMARKS.md per la metodologia.
| Metrica | Valore |
|---|---|
| Dimensione binario | ~73 KiB non strippato (~28 KiB strippato) |
| Latenza media sandbox | 5.85 ± 1.45 ms (95% CI [5.45, 6.25]) |
| RSS di picco | 1.62 MiB |
Nelle condizioni testate, Z-Jail ha il resident set più piccolo dei tre
sandbox a livello di processo e una latenza tra bwrap e nsjail.
Bubblewrap è il più veloce ma non esegue alcun filtraggio seccomp per default, quindi
fa meno lavoro di setup; Z-Jail installa una whitelist seccomp, elimina
le capacità ed esegue pivot_root ad ogni esecuzione. gVisor (runsc)
va in segfault su questo kernel WSL2 e non è stato possibile misurarlo; Firecracker
isola tramite una microVM (cold-boot della VM, una metrica diversa) ed è escluso
dalla tabella fork-to-exec. Questi sono numeri su singolo host — trattateli come
relativi.
Nota: le cifre documentate in precedenza (~8 ms, ~4 MiB, ~130 KiB) non corrispondono a una build
makecorrente (~73 KiB, ~5.9 ms) e sembrano essere state inaccurate; i numeri sopra sono stati rimisurati su questo codebase. Truthimatics è ancora parte del codice e non è stata rimossa (i vecchi reportaxiom_jailusavano semplicemente un nome di binario diverso e un harness). Un bug di propagazione dei mount trovato durante il benchmarking è stato corretto insrc/sandbox.c(MS_REC|MS_PRIVATEprima del bind mount); il rimount ricorsivo contribuisce parte della latenza misurata.
chroot, mount, ptrace, socket, process_vm_writevRLIMIT_CPU), esaurimento memoria (RLIMIT_AS)execvesetuid / linker dinamico / LD_PRELOAD/proc, /sysCLONE_NEWNET + socket bloccatoclone(CLONE_NEWNS|CLONE_NEWPID|...) ha successo (richiede CAP_SYS_ADMIN)--root)--self-hash=<hex> è configurato nei deployment di produzioneMIT — vedi LICENSE per il testo completo.
Z-Jail è stato costruito su WSL2 (Kali Linux, GCC 15.2.0), con target Linux 5.4+. Mantenuto da Division-36. Segnala problemi al issue tracker.
| Z-Jail | Firecracker | gVisor | bwrap | nsjail |
|---|
| Dipendenze esterne | zero | libc, seccomp | runtime Go | libc | libc, protobuf |
| Dimensione binario | ~73 KiB | 20+ MiB | 40+ MiB | ~70 KiB | ~1 MiB |
| Isolamento VM | no | sì (microVM) | no (sandbox) | no | no |
| whitelist seccomp | sì | no | sì | opzionale | sì |
| Hashing contenuto | sì | no | no | no | no |
| Audit JSON | sì | no | sì | no | parziale |
| Complessità build | un make | complessa | complessa | banale | moderata |
| Namespace | Flag | Scopo |
|---|
| Mount | CLONE_NEWNS | Albero del filesystem isolato |
| PID | CLONE_NEWPID | Spazio degli ID dei processi (il figlio è pid 1) |
| Net | CLONE_NEWNET | Nessuna interfaccia di rete |
| IPC | CLONE_NEWIPC | Nessuna memoria condivisa / semafori |
| UTS | CLONE_NEWUTS | Hostname separato |
| Syscall | Numero | Note |
|---|
read | 0 | stdin |
write | 1 | stdout/stderr + pipe di report |
openat | 257 | accesso ai file (non open) |
close | 3 | — |
lseek | 8 | — |
brk | 12 | gestione heap |
mmap | 9 | argomenti limitati: flags & 4 == 0 (no MAP_SHARED), flags == 0x22 (MAP_PRIVATE|MAP_ANONYMOUS) |
munmap | 11 | — |
execve | 59 | singola exec all'avvio |
exit_group | 231 | uscita pulita del processo |
rt_sigaction | 13 | gestori di segnali |
rt_sigprocmask | 14 | mascheramento segnali |
getrandom | 318 | fonte di numeri casuali |
clock_gettime | 228 | temporizzazione |
fstat | 5 | metadati file |
| Flag | Descrizione |
|---|
--root=<dir> | Directory root della sandbox (obbligatorio) |
--seccomp-enforce | Abilita la whitelist di syscall seccomp-BPF |
--self-hash=<hex> | Verifica che il binario corrisponda all'hash BLAKE2b-256 atteso |
--quiet | Sopprime l'output di audit |
--verbose | Abilita il logging di debug |
--version | Mostra build ID (Z-Jail/v1+dev) |
--help | Mostra l'utilizzo ed esce |
| Codice | Significato |
|---|
| 0 | Figlio uscito normalmente (verdetto: DETERMINISTIC) |
| 1 | Figlio ucciso da segnale (verdetto: REJECT) |
| 2 | Self-hash: stringa esadecimale errata o file illeggibile |
| 3 | Self-hash: mismatch (binario manomesso) |
| 101 | Errore di setup del figlio (rlimit, ecc.) |
| 102 | Installazione del filtro seccomp del figlio fallita |
| 103 | execve del figlio fallita (binario non trovato, nessun permesso di exec) |
| 104 | pivot_root del figlio fallito |
| 105 | Eliminazione capacità del figlio fallita |
| 125 | Creazione namespace fallita (eseguito come root? supporto kernel?) |
| # | Scenario | Tipo | Cosa testa |
|---|
| 0 | blake2b_regress | risposta nota | Correttezza implementazione BLAKE2b |
| 1 | seccomp_filter | BPF standalone | 8 sotto-test della logica del filtro BPF |
| 2 | hello_static | ok | Esecuzione binario statico di base |
| 3 | hello_dynamic | ok | Binario dinamico con ld-linux + libc |
| 4 | execve_replacement | ok | execve nella sandbox (bloccata da seccomp) |
| 5 | fd_inherited_read | ok | stdin/stdout ereditati correttamente |
| 6 | mmap_bad_flags | ucciso | mmap con MAP_SHARED bloccato |
| 7 | mmap_good_allowed | ok | mmap con MAP_PRIVATE|ANONYMOUS permesso |
| 8 | mmap_prot_exec | ucciso | mmap con PROT_EXEC bloccato |
| 9 | mmap_self_modify | ucciso | Codice auto-modificante bloccato |
| 10 | ptrace | ucciso | ptrace bloccato |
| 11 | socket | ucciso | Creazione socket bloccata |
| 12 | chroot_escape | ucciso | syscall chroot bloccata |
| 13 | double_chroot | ucciso | Doppio chroot bloccato |
| 14 | mount_replay | ucciso | Syscall mount bloccata |
| 15 | cpu_exhaust | ucciso | RLIMIT_NPROC blocca fork bomb |
| 16 | signal_parent | ucciso | Segnale al genitore bloccato |
| 17 | self_hash | ok | Verifica integrità binario |
| Linee di codice (core) |
| ~900 |
| Strumento | Latenza media ± dev.st. | RSS di picco | seccomp predefinito |
|---|
| Z-Jail | 5.85 ± 1.45 ms | 1.62 MiB | sì |
| bwrap | 3.56 ± 0.40 ms | 2.19 MiB | no |
| nsjail | 8.98 ± 1.68 ms | 7.91 MiB | sì |
| File | Descrizione |
|---|
README.md | Questo file |
docs/ARCHITECTURE.md | Panoramica dell'architettura |
docs/SANDBOX.md | Interni della sandbox strato per strato |
docs/SECCOMP.md | Progettazione whitelist seccomp-BPF |
docs/AUDIT_SCHEMA.md | Riferimento schema JSON di audit |
docs/THREAT_MODEL.md | Assunzioni e ambito di sicurezza |
docs/BLAKE2B.md | Dettagli implementazione BLAKE2b |
docs/BENCHMARKS.md | Benchmark di prestazioni |
docs/BUILD.md | Istruzioni di build |
docs/adr/ | Decisioni di architettura (4 documenti) |
man/z_jail.1 | Pagina man |
SECURITY.md | Politica di sicurezza e segnalazione |
CONTRIBUTING.md | Come contribuire |
CHANGELOG.md | Cronologia dei rilasci |
ROADMAP.md | Piani futuri |
TODO.md | Lacune note e lavoro pianificato |