Security — Intensive

Every other masterclass in this book takes the isolation boundary for granted. The KVM masterclass runs guest code on a real CPU and trusts that VT-x keeps it contained; the virtio masterclass emulates devices and trusts that a malformed descriptor chain only crashes the microVM, not the host. This intensive is about why you are allowed to trust those things — and, more importantly, about everything Firecracker layers on top of the bare KVM boundary so that you can run a hostile, untrusted guest kernel next to thousands of other hostile, untrusted guest kernels on the same machine and still sleep at night.

Firecracker's security argument is not "we use hardware virtualization." Hardware virtualization is necessary but not sufficient: the VMM is privileged host code that the guest talks to constantly (every MMIO write, every virtqueue kick, every metadata request), and a bug in that code is a bug in the host. The real argument is defense in depth — four overlapping layers, each of which assumes the layer above it has already failed:

┌─────────────────────────────────────────────────────────────────────────┐
│ 1. KVM boundary       guest runs in non-root mode; can't touch host RAM  │  ← hardware
│ 2. Minimal VMM        ~5 emulated devices in memory-safe Rust            │  ← small attack surface
│ 3. The jailer         chroot + cgroups + namespaces + drop privileges    │  ← process sandbox
│ 4. seccomp-BPF        ~40-syscall allow-list on every thread             │  ← syscall sandbox
└─────────────────────────────────────────────────────────────────────────┘
   A guest that defeats layer 1 hits 2; that defeats 2 hits 3; that defeats 3 hits 4.

This intensive makes all four layers concrete and runnable. You will run Firecracker under the real jailer and watch it construct each piece of the process sandbox, observing the namespaces and cgroup from the outside. You will read the shipped seccomp filters, compile them, deny a syscall on purpose, and watch the VMM die with SIGSYS the instant it tries to make it. And you will do a structured threat-model audit of the whole attack surface — thinking like the attacker who wants out and the defender who has to stop them — anchored on a real, recently-fixed vulnerability in the virtio-PCI transport (CVE-2026-5747).

Note: This curriculum will not hold your hand here either. Security is the one area where a contributor who "mostly understands" is a liability — a seccomp filter that is one syscall too loose or a jailer step done in the wrong order is a silent hole. Every claim in this intensive comes with the rg/find/lsns command that proves it on your checkout and your running process. Run them. Do not trust the prose.


What you will be able to do

By the end of this intensive you will be able to:

  1. Run a microVM in production posture — under the jailer, in a dedicated network namespace, with cgroup limits, a chroot, and dropped privileges — and explain every step in terms of the exact Linux mechanism (unshare, setns, pivot_root, mknod, setgid/setuid, cgroup writes) it maps to.
  2. Observe the sandbox from outside — find the jailed process, list its namespaces with lsns and /proc/<pid>/ns, read its cgroup, and confirm it cannot see the host filesystem or host processes.
  3. Break the barrier deliberately to see what each layer actually stops — remove a device node, skip the netns, loosen a cgroup — and read the failure each one produces.
  4. Read and modify the seccomp filters in resources/seccomp/<arch>.json: the three thread categories (vmm/api/vcpu), the SyscallRule model with argument operators, compile them with seccompiler-bin, and add a syscall to a filter and rebuild.
  5. Trigger and diagnose a SIGSYS — deny a syscall the VMM needs, watch it die, and read the evidence three ways (the FC log, strace, dmesg/auditd) — and explain why --no-seccomp is a debugging tool and never a production posture.
  6. Enumerate Firecracker's full attack surface — the emulated devices, the API socket, and the two trust boundaries (guest↔VMM and VMM↔host) — and reason about how a device-emulation bug becomes (or fails to become) a host compromise.
  7. Audit a real host against docs/prod-host-setup.md — SMT/KSM off, per-instance UID/GID, cgroup limits, the 169.254.169.254 egress block, ECC+TRR RAM — and produce a defensible audit checklist you could hand a security reviewer.

The threat model, stated precisely

Internalize this before the labs, because every design decision in the codebase falls out of it. The canonical statement lives in SECURITY.md and the NSDI '20 paper; the operational consequences live in docs/prod-host-setup.md.

# Read the project's own threat-model statement and the production hardening guide.
sed -n '1,80p' SECURITY.md
sed -n '1,120p' docs/prod-host-setup.md

The guest is entirely untrusted — including the guest kernel. Firecracker does not assume a cooperative or even a non-malicious guest. The guest may be running attacker-controlled code in ring 0 of its own virtual machine, deliberately sending malformed virtio descriptors, hammering MMIO registers, and probing for VMM bugs. The asset being protected is the host and, transitively, every other microVM on it. A successful attack is one where guest code influences host state outside its own microVM — reads another tenant's memory, executes host code, escalates to root, crashes the host, or exfiltrates data through a side channel.

        UNTRUSTED                    │  TRUSTED (must be protected)
  ┌──────────────────┐              │   ┌──────────────────────────────┐
  │  guest kernel +  │  boundary A  │   │  Firecracker VMM (host code) │  boundary B   ┌──────────┐
  │  guest userspace │◄────────────►│   │  jailed + seccomp'd          │◄─────────────►│   host   │
  │  (attacker)      │  virtio/MMIO │   │                              │  syscalls     │  kernel  │
  └──────────────────┘   KVM exits  │   └──────────────────────────────┘   to /dev/kvm └──────────┘
                                    │            other microVMs ── also trusted relative to this guest

There are two boundaries, and the labs treat them separately:

BoundaryWho is on each sideCrossed byDefended by
A — guest ↔ VMMattacker guest ↔ Firecracker device emulationvirtio descriptors, MMIO/PIO accesses, MMDS requestsKVM (non-root mode), Rust memory safety, bounds-checked guest-memory access, the minimal device model
B — VMM ↔ hostFirecracker (assume compromised) ↔ host kernelsyscalls, file access, device-node accessthe jailer (chroot/namespaces/cgroups/priv-drop), seccomp-BPF

The crucial design move is that boundary B assumes boundary A has already failed. Firecracker is engineered as if the guest will eventually find a device-emulation bug and achieve code execution inside the VMM process. The jailer and seccomp exist to make that hollow victory: a guest that owns the VMM finds itself in a near-empty chroot, in a private network namespace, capped by a cgroup, unprivileged, and allowed to make roughly forty syscalls — none of which let it touch another tenant. That is what "defense in depth" means here, and it is the single most important idea in this intensive.


The three labs

This intensive has three labs. Do them in order — the threat-model audit in Lab 3 only lands once you have felt the jailer and seccomp work (and break) in Labs 1 and 2.

  • Lab 1: The jailer in depth — trace-it / break-it. Set up /srv/jailer, run Firecracker under the jailer with cgroups, namespaces, chroot, privilege drop, and a dedicated network namespace. mknod the device nodes. Observe the process tree, the namespaces (lsns, /proc/<pid>/ns), and the cgroup from outside. Then break things on purpose — remove a device node, drop the netns — to see exactly which barrier each step is. Map every jailer action to the Linux mechanism that implements it, reading src/jailer/ with rg.

  • Lab 2: Seccomp filters — trace-it / build-it. Read resources/seccomp/<arch>.json: the vmm/api/vcpu categories, the SyscallRule model, the argument operators. Compile a filter with seccompiler-bin. Run Firecracker with a custom --seccomp-filter that denies a syscall it needs and watch it die with SIGSYS — observed via the FC log, strace, and dmesg/auditd. Then run with --no-seccomp and reason about why that is dangerous. Add a syscall to a filter and rebuild. Understand the build-time bake-in.

  • Lab 3: A threat-model audit — review-it. Enumerate the full attack surface: every emulated device, the API socket, and both trust boundaries. Walk the docs/prod-host-setup.md hardening line by line on a real (or simulated) host. Reason through a hypothetical device vulnerability anchored on the real CVE-2026-5747 in the virtio-PCI transport — how it would be reached, what each defense layer would (and would not) stop, and what the fix and disclosure looked like. Produce a reusable audit checklist. Learn to think like an attacker and a defender at the same time.


Prerequisites

This is a masterclass, not an introduction. You must have completed Level 9, Lab 9.1: Seccomp and the Jailer — that lab gives you a first, guided pass over both mechanisms; this intensive assumes you have it and goes far deeper, breaking things and reading source. You should also have internalized the two deep dives this intensive expands on:

Read firstWhy
The Jailer (deep dive)The barrier the jailer constructs, step by step, and the one thing it deliberately does not do.
Seccomp Filtering (deep dive)The three filter categories, the SyscallRule model, the build-time compile, and how a denied syscall manifests.
The threat model (introduction)The "do almost nothing" security argument and where the VMM sits in the trust diagram.

You also need a working Firecracker build and the ability to boot a microVM by hand, exactly as in Level 1, Lab 1.3. Confirm your environment now:

# 1. You can build firecracker AND the jailer (the jailer is a separate binary).
tools/devtool build --release
ls build/cargo_target/$(uname -m)-unknown-linux-musl/release/{firecracker,jailer}

# 2. The seccompiler binary exists (you'll compile filters with it in Lab 2).
ls build/cargo_target/$(uname -m)-unknown-linux-musl/release/seccompiler-bin \
  || cargo build --release -p seccompiler 2>/dev/null \
  || echo "build seccompiler before Lab 2"

# 3. The seccomp JSON for your arch exists.
find resources/seccomp -name "*.json"

# 4. You are root or can sudo — the jailer must start as root to set up the barrier.
id -u    # the jailer needs to begin life as uid 0; it drops privileges itself

Warning: Run these labs on a disposable host or VM you own — a bare-metal instance, a nested KVM dev box, a throwaway cloud instance. You will create cgroups, network namespaces, TAP devices, and mknod device nodes as root, and you will deliberately break isolation to observe failures. Do not do this on a shared or production machine. None of it persists if you tear the box down.


How the four layers map to the rest of the curriculum

This intensive is the security capstone of the whole book; it pulls threads from everywhere. Keep these cross-links handy — when a lab says "the device is part of the attack surface," the deep dive is where you learn why:

Layer / topicDeep diveMasterclass
KVM boundaryKVM fundamentals, vCPU run loop & VM exitsKVM & vCPUs
Minimal device modelvirtio transport, virtio-net, virtio-blockvirtio devices
The jailerThe JailerLab 1 (this intensive)
seccomp-BPFSeccomp FilteringLab 2 (this intensive)
MMDS attack surfaceMMDSNetworking Lab 3
Rate limiting (DoS defense)Rate limitingNetworking Lab 2
Signals & fail-closed exitSignals, shutdown & resetLab 2 (this intensive)

The minimal-device-model philosophy — fewer devices means less host code a guest can attack — is the unifying thread. Firecracker emulates roughly five devices not out of laziness but because every emulated device is a parser that processes attacker-controlled bytes inside the host. When you audit the attack surface in Lab 3, you are really counting parsers.


Common mistakes contributors make in this area

MistakeConsequenceFix
Running firecracker directly and calling it "secure"No chroot, no cgroups, no namespaces, no priv-drop — only seccompProduction = jailer + seccomp + host hardening; bare firecracker is for dev only
Adding a device that needs a new syscall, forgetting the filterThe VMM dies with SIGSYS the first time the path runsAdd the syscall to resources/seccomp/<arch>.json and rebuild (Lab 2)
Hard-coding a syscall number in a filterBreaks on the other architecture (x86_64 ≠ aarch64 numbering)Use syscall names in the JSON; there is a file per arch
Loosening an ioctl arg condition "to make it work"Opens the whole ioctl multiplexer; defeats the point of the filterPin ioctl to the exact request numbers the path needs
setuid before setgid in jailer-like codePrivilege drop is incomplete — can't drop the gid after dropping the uidAlways gid, then uid (Lab 1)
Treating --no-seccomp as a config optionRemoves the entire syscall sandbox in production--no-seccomp is a debugging flag only; reviewers reject it in prod configs
Assuming MMDS is harmless because there's "no real server"dumbo parses guest packets — it is attack surfaceBlock guest egress to 169.254.169.254; keep dumbo minimal (Lab 3)

How to verify you are ready to start

# You should be able to answer each of these from the deep dives BEFORE Lab 1.
# 1. Which sandbox layer does the jailer own, and which does it NOT?
rg -ni "seccomp" src/jailer/src/ || echo "correct: the jailer does NOT do seccomp"
# 2. Where do the three seccomp categories live, and what are their names?
rg -n '"vmm"|"api"|"vcpu"' resources/seccomp/$(uname -m)*.json | head
# 3. Where does Firecracker install the filter (NOT the jailer)?
rg -n "install_filter|apply_filter|seccomp" src/vmm/src/seccomp.rs
# 4. Which two device nodes does the jailer mknod, and why exactly those two?
rg -n "/dev/kvm|/dev/net/tun|mknod" src/jailer/src/

If you cannot explain, from memory, the difference between what the jailer does and what seccomp does, re-read the jailer deep dive and the seccomp deep dive before continuing. The labs assume that division is second nature.


Next: Lab 1: The jailer in depth — run Firecracker under the real jailer, construct the process sandbox by hand, observe it from outside, and break it deliberately to see what each barrier actually stops.