
Um sandbox Linux leve e multicamadas que combina namespaces, pivot_root, seccomp-bpf, remoção de capabilities e um mecanismo de veredito baseado em evidências (Versão Pública Truthimatics) para execução de código segura e auditável.
Sandbox de múltiplas camadas para execução de código nativo no Linux.
Sete camadas de defesa independentes — sem dependências externas, binário PIE de ~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
O diretório --root deve conter um sistema de ficheiros mínimo com o binário alvo e as suas dependências (para binários estáticos, basta o próprio binário).
As soluções de sandboxing existentes envolvem compromissos:
| Z-Jail | Firecracker | gVisor | bwrap | nsjail | |
|---|---|---|---|---|---|
| Dependências externas | zero | libc, seccomp | Go runtime | libc | libc, protobuf |
| Tamanho do binário | ~73 KiB | 20+ MiB | 40+ MiB | ~70 KiB | ~1 MiB |
| Isolamento de VM | não | sim (microVM) | não (sandbox) | não | não |
| Lista de permissões seccomp | sim | não | sim | opcional | sim |
| Hash de conteúdo | sim | não | não | não | não |
| Auditoria JSON | sim | não | sim | não | parcial |
| Complexidade de compilação | um único make | complexa | complexa | trivial | moderada |
O Z-Jail preenche o nicho entre o bwrap (mínimo, sem seccomp por defeito) e o nsjail (cheio de funcionalidades, dependências pesadas). Foi concebido para pipelines de CI, desafios de jail em CTF e avaliação leve de código, onde é necessária defesa em profundidade sem depender de um runtime de contentores.
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]
Cada camada está ordenada de modo a que uma camada posterior não possa ser desfeita por uma anterior:
capset a partir deste pontosequenceDiagram
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
Mecanismo de veredito baseado em evidências. Recolhe observações ponderadas sobre o binário executado e determina um veredito final (DETERMINISTIC, REJECT ou UNCERTAIN). Cada observação tem um peso; qualquer observação individual com peso >50% do total decide o veredito.
Cinco namespaces são criados através de clone():
| Namespace | Flag | Propósito |
|---|---|---|
| Mount | CLONE_NEWNS | Árvore de sistema de ficheiros isolada |
| PID | CLONE_NEWPID | Espaço de IDs de processo (o filho é o pid 1) |
| Net | CLONE_NEWNET | Sem interfaces de rede |
| IPC | CLONE_NEWIPC | Sem memória partilhada / semáforos |
| UTS | CLONE_NEWUTS | Hostname separado |
Requer CAP_SYS_ADMIN no namespace inicial.
Substitui a raiz do namespace de montagem pelo diretório --root:
MS_BIND|MS_REC)pivot_root(new_root, put_old) — troca a árvore de montagenschdir("/") — entra na nova raizumount2("/.pivot_old", MNT_DETACH) — desanexa a raiz antigarmdir("/.pivot_old") — limpezaIsto é estritamente mais forte do que chroot(2) — não há forma de o processo em sandbox escapar de volta para a raiz do host, mesmo com CLONE_NEWNS a partir do interior da sandbox (que já é bloqueado pelo seccomp).
Todas as capabilities são removidas através de:
capset(hdr, data) // data = {0, 0, 0}
prctl(SECBIT_KEEP_CAPS_LOCKED | SECBIT_NO_SETUID_FIXUP | ...)
O processo remove setuid/setgid antes do capset, para que a alteração de uid tenha efeito enquanto CAP_SETUID ainda está detida. Após o capset, todas as caps desaparecem e os securebits ficam bloqueados — não é possível reativá-las.
prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
Impede que o processo ou os seus filhos obtenham novos privilégios através de binários setuid, capabilities de ficheiro ou transições LSM. Irreversível.
Lista de permissões de 15 syscalls — tudo o que não estiver na lista recebe SECCOMP_RET_KILL: