<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Ziad</title>
    <description>The latest articles on DEV Community by Ziad (@zierax).</description>
    <link>https://dev.to/zierax</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3970857%2F107b86a5-6f9f-498a-96ef-87053d9173f2.jpeg</url>
      <title>DEV Community: Ziad</title>
      <link>https://dev.to/zierax</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/zierax"/>
    <language>en</language>
    <item>
      <title>I built a Linux sandbox smaller than most PNG files</title>
      <dc:creator>Ziad</dc:creator>
      <pubDate>Sat, 06 Jun 2026 06:58:56 +0000</pubDate>
      <link>https://dev.to/zierax/i-built-a-linux-sandbox-smaller-than-most-png-files-3am8</link>
      <guid>https://dev.to/zierax/i-built-a-linux-sandbox-smaller-than-most-png-files-3am8</guid>
      <description>&lt;p&gt;130 KiB. No external dependencies. Seven independent layers standing between untrusted code and your host kernel.&lt;/p&gt;

&lt;p&gt;That's Z-Jail.&lt;/p&gt;




&lt;p&gt;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.&lt;/p&gt;

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




&lt;h2&gt;
  
  
  What it actually does
&lt;/h2&gt;

&lt;p&gt;When you run a binary under Z-Jail, seven things happen in a specific order before &lt;code&gt;execve&lt;/code&gt; is ever called:&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;2. fd scrub&lt;/strong&gt; — Every file descriptor above 2 gets closed. No inherited handles leaking into the sandbox.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. PR_SET_DUMPABLE=0&lt;/strong&gt; — Core dumps disabled. &lt;code&gt;/proc/self/mem&lt;/code&gt; locked down.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. pivot_root&lt;/strong&gt; — This is chroot, but it actually works. The child process swaps its entire mount namespace root to whatever &lt;code&gt;--root&lt;/code&gt; 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 &lt;code&gt;clone(CLONE_NEWNS)&lt;/code&gt; from inside (seccomp blocks it anyway).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. PR_SET_NO_NEW_PRIVS&lt;/strong&gt; — Irreversible. No setuid binaries, no file capabilities, no LSM transitions can grant new privileges after this point.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. capset to zero + locked securebits&lt;/strong&gt; — Every capability gone. The securebits are locked so they can't come back.&lt;/p&gt;

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

&lt;p&gt;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.&lt;/p&gt;




&lt;h2&gt;
  
  
  The part I'm most interested in: Truthimatics
&lt;/h2&gt;

&lt;p&gt;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: &lt;code&gt;DETERMINISTIC&lt;/code&gt;, &lt;code&gt;REJECT&lt;/code&gt;, or &lt;code&gt;UNCERTAIN&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;




&lt;h2&gt;
  
  
  Compared to the alternatives
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Z-Jail&lt;/th&gt;
&lt;th&gt;Firecracker&lt;/th&gt;
&lt;th&gt;gVisor&lt;/th&gt;
&lt;th&gt;bwrap&lt;/th&gt;
&lt;th&gt;nsjail&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;External deps&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;zero&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;libc, seccomp&lt;/td&gt;
&lt;td&gt;Go runtime&lt;/td&gt;
&lt;td&gt;libc&lt;/td&gt;
&lt;td&gt;libc, protobuf&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Binary size&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~130 KiB&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;20+ MiB&lt;/td&gt;
&lt;td&gt;40+ MiB&lt;/td&gt;
&lt;td&gt;~70 KiB&lt;/td&gt;
&lt;td&gt;~1 MiB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;seccomp whitelist&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;yes&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;optional&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Content hashing&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;yes&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Audit JSON&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;yes&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;td&gt;partial&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Build&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;one &lt;code&gt;make&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;complex&lt;/td&gt;
&lt;td&gt;complex&lt;/td&gt;
&lt;td&gt;trivial&lt;/td&gt;
&lt;td&gt;moderate&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;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.&lt;/p&gt;




&lt;h2&gt;
  
  
  Quick start
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/Division-36/Z-Jail.git
&lt;span class="nb"&gt;cd &lt;/span&gt;Z-Jail
make
&lt;span class="nb"&gt;sudo&lt;/span&gt; ./z_jail &lt;span class="nt"&gt;--root&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/path/to/rootfs &lt;span class="nt"&gt;--seccomp-enforce&lt;/span&gt; &lt;span class="nt"&gt;--&lt;/span&gt; /bin/ls
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For static binaries, the &lt;code&gt;--root&lt;/code&gt; directory just needs to contain the binary itself. No shared libraries needed.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;




&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Here is the Repo: &lt;a href="https://github.com/Division-36/Z-Jail" rel="noopener noreferrer"&gt;github.com/Division-36/Z-Jail&lt;/a&gt;&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>architecture</category>
      <category>linux</category>
      <category>cybersecurity</category>
    </item>
  </channel>
</rss>
