
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 | 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 |
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():
| 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 |
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: