DEV Community

Cover image for I built a Linux sandbox smaller than most PNG files
Ziad
Ziad

Posted on

I built a Linux sandbox smaller than most PNG files

130 KiB. No external dependencies. Seven independent layers standing between untrusted code and your host kernel.

That's Z-Jail.


I wanted a sandbox for running untrusted native binaries in CI pipelines and CTF challenges. The options I found all had the same problem: they either pulled in a Go runtime, a full container stack, or a handful of shared libraries I didn't want to trust. So I wrote my own in C99.

The result is a ~130 KiB PIE binary that you build with a single make. That's it. No apt installs, no Go toolchain, no protobuf.


What it actually does

When you run a binary under Z-Jail, seven things happen in a specific order before execve is ever called:

1. setrlimit — CPU time, address space, file count, and process count are all capped before anything else. A fork bomb can't do much when RLIMIT_NPROC is already set.

2. fd scrub — Every file descriptor above 2 gets closed. No inherited handles leaking into the sandbox.

3. PR_SET_DUMPABLE=0 — Core dumps disabled. /proc/self/mem locked down.

4. pivot_root — This is chroot, but it actually works. The child process swaps its entire mount namespace root to whatever --root directory you specify, then unmounts the old root lazily. There's no path back to the host filesystem, even if the sandboxed process tries to call clone(CLONE_NEWNS) from inside (seccomp blocks it anyway).

5. PR_SET_NO_NEW_PRIVS — Irreversible. No setuid binaries, no file capabilities, no LSM transitions can grant new privileges after this point.

6. capset to zero + locked securebits — Every capability gone. The securebits are locked so they can't come back.

7. seccomp-BPF whitelist — 15 syscalls allowed. Everything else gets SECCOMP_RET_KILL. The list is tight: read, write, openat, close, lseek, brk, mmap (with argument restrictions — no MAP_SHARED), munmap, execve, exit_group, rt_sigaction, rt_sigprocmask, getrandom, clock_gettime, fstat. That's the whole list. socket, ptrace, chroot, mount — all blocked.

The ordering matters. Each layer is placed so that a later layer can't be undone by an earlier one. After seccomp is installed, the sandboxed process literally cannot call anything that would weaken the other layers.


The part I'm most interested in: Truthimatics

The sandbox also ships with a verdict engine called Truthimatics. After the child exits, the parent collects weighted observations about what happened — exit code, signal, resource usage, content fingerprint — and produces one of three verdicts: DETERMINISTIC, REJECT, or UNCERTAIN.

The idea is that "the process exited 0" is weak evidence. A program that always exits 0 regardless of input isn't useful. Truthimatics tries to give you a stronger signal about whether the execution was meaningful.

Every run also produces a JSON audit record with a BLAKE2b-256 hash of the target binary. If the binary changes between runs, you'll know.


Compared to the alternatives

Z-Jail Firecracker gVisor bwrap nsjail
External deps zero libc, seccomp Go runtime libc libc, protobuf
Binary size ~130 KiB 20+ MiB 40+ MiB ~70 KiB ~1 MiB
seccomp whitelist yes no yes optional yes
Content hashing yes no no no no
Audit JSON yes no yes no partial
Build one make complex complex trivial moderate

bwrap is simpler but doesn't enforce seccomp by default. nsjail is more featureful but brings protobuf with it. Z-Jail sits in the gap between them.


Quick start

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
Enter fullscreen mode Exit fullscreen mode

For static binaries, the --root directory just needs to contain the binary itself. No shared libraries needed.

The test suite covers 17 scenarios — fork bombs, mmap with bad flags, ptrace attempts, chroot escapes, double chroot, socket creation. All blocked. The seccomp filter tests run without root in under 100ms.


There's a lot more to dig into — the architecture decision records, the threat model, the BPF filter generation. It's all in the docs folder if you want to go deeper.

Here is the Repo: github.com/Division-36/Z-Jail

Top comments (1)

Collapse
 
thetylern profile image
Tyler N

I think your project was an awesome idea! Where did you get the inspiration to create it?