
Z-Jail v1.1.0
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.
Z-Jail
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) │
└──────────────────────────────────────────────────────┘
Índice
- Início Rápido
- Porquê Z-Jail?
- Arquitetura
- Camadas
- Utilização
- Compilação e Instalação
- Testes
- Desempenho
- Modelo de Ameaças
- Documentação
- Roadmap
- Licença
Início Rápido
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).
Porquê Z-Jail?
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.
Arquitetura
Fluxo de Dados
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]
Ordem das Camadas
Cada camada está ordenada de modo a que uma camada posterior não possa ser desfeita por uma anterior:
- setrlimit — limitar CPU, espaço de endereçamento, número de ficheiros e processos antes de qualquer outra coisa
- limpeza de fds — fechar todos os fds herdados, exceto o pipe de relatório
- PR_SET_DUMPABLE=0 — core dumps desativados, /proc/self/mem bloqueado
- pivot_root — desanexar do sistema de ficheiros do host; a raiz antiga é desmontada em modo lazy
- PR_SET_NO_NEW_PRIVS — sem setuid, sem escalonamento via
capseta partir deste ponto - drop_caps — zerar todas as capabilities, bloquear os securebits
- seccomp-BPF — restringir syscalls apenas à lista de permissões
- sinalizar o pai — informar o processo pai de que a sandbox está pronta
- execve — substituir o processo pelo binário alvo
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
Camadas
1. Versão Pública Truthimatics
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.
2. Namespaces
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.
3. pivot_root
Substitui a raiz do namespace de montagem pelo diretório --root:
- Faz bind-mount do diretório raiz sobre si próprio (
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")— limpeza
Isto é 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).
4. Capabilities
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.
5. NO_NEW_PRIVS
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.
6. seccomp-BPF (whitelist-v1)
Lista de permissões de 15 syscalls — tudo o que não estiver na lista recebe SECCOMP_RET_KILL:
| Syscall | Número | Notas |
|---|---|---|
read | 0 | stdin |
write | 1 | stdout/stderr + pipe de relatório |
openat | 257 | acesso a ficheiros (não open) |
close | 3 | — |
lseek | 8 | — |
brk | 12 | gestão do heap |
mmap | 9 | restrição de argumentos: flags & 4 == 0 (sem MAP_SHARED), flags == 0x22 (MAP_PRIVATE|MAP_ANONYMOUS) |
munmap | 11 | — |
execve | 59 | execução única no arranque |
exit_group | 231 | saída limpa do processo |
rt_sigaction | 13 | tratadores de sinais |
rt_sigprocmask | 14 | mascaramento de sinais |
getrandom | 318 | fonte de números aleatórios |
clock_gettime | 228 | medição de tempo |
fstat | 5 | metadados de ficheiro |
O filtro BPF é gerado dinamicamente: para cada entrada da lista de permissões, é emitida uma cadeia de saltos que permite (se a syscall corresponder) ou cai no KILL. A arquitetura é verificada primeiro (AUDIT_ARCH_X86_64).
O filtro é verificado de forma independente por um teste autónomo (tests/seccomp_filter_test.c, 8/8 passam) que faz fork+execve de casos de teste contra um prctl(PR_SET_SECCOMP) real, sem precisar de root.
7. Auditoria
Cada execução produz um registo de auditoria 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..."
}
Escrito em build/audits/<binary-name>.audit.json. O content_fingerprint é um hash BLAKE2b-256 do binário alvo, calculado pelo processo pai depois de o filho terminar.
Utilização
z_jail --root=<dir> [--seccomp-enforce] [--self-hash=<hex>]
[--quiet] [--verbose] -- <program> [args...]
| Flag | Descrição |
|---|---|
--root=<dir> | Diretório raiz da sandbox (obrigatório) |
--seccomp-enforce | Ativa a lista de permissões de syscalls seccomp-BPF |
--self-hash=<hex> | Verifica se o binário corresponde ao hash BLAKE2b-256 esperado |
--quiet | Suprime a saída de auditoria |
--verbose | Ativa o registo de depuração |
--version | Mostra o ID de compilação (Z-Jail/v1+dev) |
--help | Mostra a ajuda e sai |
Exemplos
# 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
Códigos de Saída
| Código | Significado |
|---|---|
| 0 | O filho terminou normalmente (veredito: DETERMINISTIC) |
| 1 | O filho foi morto por um sinal (veredito: REJECT) |
| 2 | Self-hash: string hex inválida ou ficheiro ilegível |
| 3 | Self-hash: não corresponde (o binário foi adulterado) |
| 101 | Erro de configuração do filho (rlimit, etc.) |
| 102 | Falha na instalação do filtro seccomp no filho |
| 103 | Falha no execve do filho (binário não encontrado, sem permissão de execução) |
| 104 | Falha no pivot_root do filho |
| 105 | Falha na remoção de capabilities do filho |
| 125 | Falha na criação de namespaces (correu como root? suporte do kernel?) |
Compilação e Instalação
Requisitos
- Kernel Linux ≥ 5.4 (namespaces, seccomp-BPF, pivot_root)
- GCC ≥ 11 (testado em 11.4, 13.2, 15.2)
- Sem bibliotecas externas — apenas o toolchain C padrão
Comandos
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)
O binário é compilado como um Executável Independente de Posição (PIE) com -fstack-protector-strong, -D_FORTIFY_SOURCE=2, RELRO completo e -z now.
Opções de Compilação
make CC=clang CFLAGS="-O3 -march=native" # custom compiler/flags
Testes
Teste Rápido (sem root)
# seccomp filter logic (8 tests)
tests/build/seccomp_filter_test
# BLAKE2b known-answer test
tests/build/blake2b_known
Estes não precisam de root e são executados em menos de 100 ms.
Suite de Testes Completa
make -C tests setup # build payloads + test roots
sudo bash tests/run_tests.sh # 17 scenarios
Requer root para a criação de namespaces. A suite de testes cobre:
| # | Cenário | Tipo | O que testa |
|---|---|---|---|
| 0 | blake2b_regress | known-answer | Correção da implementação BLAKE2b |
| 1 | seccomp_filter | standalone BPF | 8 sub-testes da lógica do filtro BPF |
| 2 | hello_static | ok | Execução básica de binário estático |
| 3 | hello_dynamic | ok | Binário dinâmico com ld-linux + libc |
| 4 | execve_replacement | ok | execve na sandbox (bloqueado pelo seccomp) |
| 5 | fd_inherited_read | ok | stdin/stdout herdados corretamente |
| 6 | mmap_bad_flags | killed | mmap com MAP_SHARED bloqueado |
| 7 | mmap_good_allowed | ok | mmap com MAP_PRIVATE|ANONYMOUS permitido |
| 8 | mmap_prot_exec | killed | mmap com PROT_EXEC bloqueado |
| 9 | mmap_self_modify | killed | Código automodificável bloqueado |
| 10 | ptrace | killed | ptrace bloqueado |
| 11 | socket | killed | Criação de socket bloqueada |
| 12 | chroot_escape | killed | Syscall chroot bloqueada |
| 13 | double_chroot | killed | Chroot duplo bloqueado |
| 14 | mount_replay | killed | Syscall mount bloqueada |
| 15 | cpu_exhaust | killed | RLIMIT_NPROC bloqueia fork bomb |
| 16 | signal_parent | killed | Sinal para o pai bloqueado |
| 17 | self_hash | ok | Verificação de integridade do binário |
Desempenho
Medido em WSL2 (kernel 6.18.x-microsoft-standard-WSL2, Kali Linux),
50 amostras por ferramenta, carga de trabalho uniforme (um binário estático
freestanding cujo corpo é exit_group(0)), cronometrado com um harness
getrusage. Consulte docs/BENCHMARKS.md para a
metodologia.
| Métrica | Valor |
|---|---|
| Tamanho do binário | ~73 KiB sem strip (~28 KiB com strip) |
| Latência média da sandbox | 5.85 ± 1.45 ms (IC 95% [5.45, 6.25]) |
| RSS máximo | 1.62 MiB |
| Linhas de código (núcleo) | ~900 |
Comparação Direta (mesmo host, mesma metodologia)
| Ferramenta | Latência média ± dp | RSS máximo | seccomp por defeito |
|---|---|---|---|
| Z-Jail | 5.85 ± 1.45 ms | 1.62 MiB | sim |
| bwrap | 3.56 ± 0.40 ms | 2.19 MiB | não |
| nsjail | 8.98 ± 1.68 ms | 7.91 MiB | sim |
Nas condições testadas, o Z-Jail apresenta o menor conjunto residente das
três sandboxes a nível de processo e uma latência entre o bwrap e o nsjail.
O Bubblewrap é o mais rápido, mas não efetua qualquer filtragem seccomp por
defeito, pelo que faz menos trabalho de configuração; o Z-Jail instala uma
lista de permissões seccomp, remove capabilities e executa pivot_root em
cada execução. O gVisor (runsc) sofre um segfault neste kernel WSL2 e não
pôde ser medido; o Firecracker isola através de uma microVM (cold-boot de VM,
uma métrica diferente) e fica excluído da tabela fork-to-exec. Estes números
provêm de um único host — trate-os como relativos.
Nota: os valores documentados anteriormente (~8 ms, ~4 MiB, ~130 KiB) não correspondem a uma compilação
makeatual (~73 KiB, ~5.9 ms) e parecem ter sido imprecisos; os números acima foram re-medidos neste codebase. O Truthimatics continua a fazer parte do código e não foi removido (os relatórios antigos doaxiom_jailsimplesmente usavam um nome de binário e um harness diferentes). Um bug de propagação de montagens encontrado durante os benchmarks foi corrigido emsrc/sandbox.c(MS_REC|MS_PRIVATEantes do bind mount); o remount recursivo contribui com parte da latência medida.
Modelo de Ameaças
No Âmbito
- Execução de código nativo arbitrário por um payload não confiável
- Fuga através de
chroot,mount,ptrace,socket,process_vm_writev - Fork bombs, exaustão de CPU (
RLIMIT_CPU), exaustão de memória (RLIMIT_AS) - Fugas de file descriptors através de
execve - Escalonamento via
setuid/ dynamic linker /LD_PRELOAD - Remoção do filtro seccomp ou reativação de capabilities
Fora do Âmbito
- Zero-days do kernel fora da superfície de syscalls permitida
- Canais laterais de hardware (Spectre, Meltdown)
- Fuga de VM co-localizada através de montagens partilhadas
/proc,/sys - Saída de rede para além do que
CLONE_NEWNET+socketbloqueado proporciona - Escassez de recursos de sandboxes irmãs (requer suporte a cgroups)
Premissas
- O kernel do host é Linux ≥ 5.4 não modificado
clone(CLONE_NEWNS|CLONE_NEWPID|...)é bem-sucedido (requerCAP_SYS_ADMIN)- O binário alvo está ligado estaticamente (ou as bibliotecas dinâmicas estão disponíveis em
--root) --self-hash=<hex>está configurado em implementações de produção
Documentação
| Ficheiro | Descrição |
|---|---|
README.md | Este ficheiro |
docs/ARCHITECTURE.md | Visão geral da arquitetura |
docs/SANDBOX.md | Interiores da sandbox, camada por camada |
docs/SECCOMP.md | Conceção da lista de permissões seccomp-BPF |
docs/AUDIT_SCHEMA.md | Referência do schema JSON de auditoria |
docs/THREAT_MODEL.md | Premissas e âmbito de segurança |
docs/BLAKE2B.md | Detalhes da implementação BLAKE2b |
docs/BENCHMARKS.md | Benchmarks de desempenho |
docs/BUILD.md | Instruções de compilação |
docs/adr/ | Registos de Decisões de Arquitetura (4 documentos) |
man/z_jail.1 | Página de manual |
SECURITY.md | Política de segurança e comunicação de problemas |
CONTRIBUTING.md | Como contribuir |
CHANGELOG.md | Histórico de versões |
ROADMAP.md | Planos futuros |
TODO.md | Lacunas conhecidas e trabalho planeado |
Roadmap
v1 (atual)
- Sandbox de defesa em profundidade com 7 camadas
- Fingerprinting de conteúdo BLAKE2b-256
- Saída de auditoria JSON
- 17 cenários de teste
- Página de manual, completions (bash, zsh, fish)
v2 (planeado)
- Ficheiro de política seccomp externo (JSON ou fonte BPF)
- Flags de namespace personalizadas por instância de sandbox
- Lista de permissões de syscalls configurável via CLI
- Hooks de profiling de desempenho para integração em CI
- Assinatura de releases (minisign/signify)
Status
Licença
MIT — consulte LICENSE para o texto completo.
O Z-Jail foi desenvolvido em WSL2 (Kali Linux, GCC 15.2.0), com o alvo Linux 5.4+. Mantido pela Division-36. Comunique problemas no rastreador de issues.