← All chapters

Chapter 4

microVMs, and the primitives you already know

The twist: Firecracker's jailer builds its second wall out of the very same namespaces, cgroups, and seccomp you just learned. The primitives were never 'container tech'.

Chapter 3 ended on the one door hardening cannot lock: the shared kernel. The fix is the own-kernel model from chapter 1, made fast enough to actually use, and then a twist that is the whole reason this tutorial exists.

Wall 1, the hardware boundary

Firecracker gives each workload its own kernel. It drives KVM, so the guest runs on virtual hardware behind a boundary the CPU itself enforces; the code inside makes its syscalls into a guest kernel, and a kernel LPE in there escalates to root of a throwaway VM, not your host. That is wall 1, the hardware boundary, the thing a container never had. And the "VMs are slow" tradeoff you were taught to fear has expired: a Firecracker microVM boots in about 125 ms with a few MiB of overhead. AWS runs Lambda and Fargate on it, one microVM per Lambda function, precisely because it is both a real boundary and cheap enough to spin up per request.

The turn: the VMM is just a process

Here is where it gets interesting. That hardware boundary sits between the guest and the host, but the thing running the VM is itself an ordinary userspace process on your host, the virtual machine monitor. And it does not sit idle. It emulates the guest's virtual devices: the network card (virtio-net), the disk (virtio-blk), the host/guest socket (virtio-vsock). Emulating a device means parsing the bytes the guest sends it, and the guest is the untrusted party. Every virtio-net packet, every virtio-blk request, every virtio-vsock message is hostile input handed to host userspace code. If a device backend has a memory-safety bug, an attacker who controls the guest can trigger it and get code execution as the VMM, a process running on your host, outside the guest, with whatever authority you launched it with. Wall 1 stopped the guest from touching the host directly. It does nothing about a bug in the VMM itself.

So the VMM sandboxes itself, and here is the payoff

Firecracker's answer is to build a second wall around its own process, and this is the sentence the whole tutorial has been walking toward: it builds that second wall out of the very same primitives you learned in chapter 2. Its jailer wraps the VMM in a chroot, a fresh set of Linux namespaces, a cgroup, and a privilege drop; and Firecracker installs a seccomp-BPF allowlist on its own threads, applied per-thread at the last safe moment, right before each vCPU thread starts running guest code, so that even a fully compromised VMM can make only the roughly fifty syscalls on the list. No execve, no arbitrary open, no ptrace. Look at that list again: namespaces, cgroups, chroot, a privilege drop, a seccomp filter. It is a container. Firecracker sandboxes its own hypervisor with a container.

That is the payoff, and it reframes everything that came before it. The primitives were never "container technology", they are kernel features, and a VM uses them to protect the host from itself. The five features you learned by taking a container apart return here as the second layer of a serious two-wall design, doing the job the hardware boundary structurally cannot. Chapter 2 told you to hold onto that. This is why.

guest code (untrusted)

Wall 1, the hardware boundary

the CPU itself enforces it

VMM process (host userspace)

Wall 2, the VMM's own confinement

the VMM sandboxes itself

seccomp-BPFLinux namespacescgroups v2chrootprivilege drop
host kernel
The second wall is built from the exact same primitives as a container. The VMM is just a userspace process parsing hostile guest input, so it sandboxes itself.

The counter-example that proves it matters

If both walls came for free with every microVM, this would be a nice piece of trivia. They do not, and the differences are exactly where real tools diverge, which is why "it's a microVM" is not enough to know on its own.

libkrun is an embeddable VMM: instead of a standalone binary like the firecracker process, it ships as a C library you link directly into your own program, and it installs no seccomp filter of its own. Because it runs in-process, there is no separate VMM binary for a jailer to wrap, and a VMM-level bug lands with your host process's full authority. So wall 2 is the embedder's job, and tools built on libkrun differ wildly in whether they do it, some build a full second wall, some none at all.

A libkrun-based tool keeps a real hardware boundary either way. The lesson is that wall 2 is a choice, made per tool and sometimes per launch path, the exact thing you cannot see from the label "microVM."

Ask which walls it has

Defense in depth, here, means two specific things, wall 1 and wall 2, and you should ask any microVM tool which walls it actually has. Wall 1, the hardware boundary, is what makes a microVM stronger than any container. Wall 2, the VMM's own confinement, is what contains the day the device-backend bug is found. A tool with both is doing the job Firecracker documented; a tool with only wall 1 is one memory-safety bug in host userspace away from your whole account. Now that you can see both walls, the last chapter is about choosing, matching the tier to the threat, and cutting the holes in the boundary as small as they will go.

Reference these in the dictionary