Level 9: Advanced Maintainer — Security, Snapshots, Performance
This is the last level before the capstone, and it is the one that changes how you read every diff. Through Level 8 you were a contributor: you found an issue, reproduced it, fixed it, wrote an integration test, and opened a PR with a DCO sign-off. A Firecracker maintainer carries a heavier burden. The maintainer is the person who blocks a green, well-tested, genuinely useful PR — because it adds a syscall to the seccomp filter without justifying it in the threat model, because it changes a serialized snapshot field without a versioned migration, or because it adds 8 ms to boot or 200 KiB to the per-microVM memory floor on a host that runs thousands of microVMs. The contributor optimizes for "does my change work." The maintainer optimizes for "is the host still safe, can every snapshot still restore, and is the fleet still dense — after my change merges and a billion Lambda invocations run through it."
Firecracker's reason for existing is multi-tenant isolation at density: VM-grade security on the same host that packs containers' worth of workloads. Three properties encode that mission directly into the codebase, and they are the three you must learn to defend in this level: the security model (the jailer, the seccomp filter, and the threat model that justifies both), snapshot/restore (and the brutal compatibility and versioning constraints that come with persisting a live machine's state), and performance and density (boot time, the < 5 MiB memory overhead, > 20x oversubscription, and the I/O engines that make block I/O cheap). These are the concerns that separate "I can add a feature" from "I can be trusted not to break the fleet."
This curriculum will not hold your hand here. By Level 9 you can read the vCPU run loop,
trace an API request to a VmmAction, instrument a virtio-block I/O down to a host
pread, and reproduce a GitHub issue. What you build now is judgment — the reflexes a
maintainer applies to every PR they review, including their own: does this widen the
attack surface, can this snapshot still be loaded by the version that wrote it (and the
next one), and did I measure the density cost?
Learning Objectives
By the end of Level 9 you must be able to:
- Run Firecracker under the jailer from scratch — chroot/
pivot_root, cgroups, namespaces,mknodfor/dev/kvmand/dev/net/tun,--netns, and thesetuid/setgidprivilege drop — and explain why the jailer is a separate binary that does not itself apply seccomp. - Read and modify a seccomp filter: locate the per-thread-category JSON in
resources/seccomp/<arch>.json, understandSyscallRulewithargsoperators, compile it withseccompiler-bin, and reason about what--seccomp-filterand--no-seccompdo to the security boundary. - Articulate the threat model and defense in depth — the untrusted guest, the privileged VMM as part of the attack surface, and the four overlapping layers (KVM boundary, jailer, seccomp, Rust memory safety) — well enough to evaluate whether a PR widens the surface.
- Execute the full snapshot/restore workflow (
PATCH /vmPaused →PUT /snapshot/create→PATCH /vmResumed → load on a fresh process), inspect both the state file and the memory file withsnapshot-editor, and explain thePersisttrait that every device implements. - Reason about snapshot backward compatibility as a maintainer concern: why adding
a field to persisted device state is a versioning event, what Full vs Diff snapshots
cost, and why a careless
Persistchange is unshippable. - Measure boot time and memory overhead, run the performance test harness with
A/B comparison (
tools/devtool test --performance/--ab), profile a microVM, and attribute a regression to a specific commit by bisection. - Connect a change to the density and oversubscription goals — and to how performance is guarded in CI so that a regression is caught before it ships.
- Review a security-, snapshot-, or performance-touching PR the way a maintainer does: surface-area first, compatibility second, measured cost third — not just "the diff is clean and CI is green."
The Three Constant Maintainer Concerns
Strip away everything else and a Firecracker maintainer's job, at this tier, reduces to defending three invariants on every change. Internalize these; they are the lens for the whole level.
flowchart TB
PR[Incoming PR / your own change] --> SEC{Does it touch the<br/>attack surface?<br/>seccomp, jailer,<br/>a device, an ioctl}
PR --> SNAP{Does it change<br/>persisted state,<br/>a Persist impl,<br/>or the snapshot format?}
PR --> PERF{Does it touch a<br/>hot path? boot,<br/>per-microVM memory,<br/>the virtio fast path}
SEC -->|yes| G1[Justify in the threat model,<br/>minimize syscalls/devices,<br/>review seccomp diff]
SNAP -->|yes| G2[Version-gate it, prove<br/>old→new and new→old restore,<br/>add a snapshot compat test]
PERF -->|yes| G3[Measure with --ab,<br/>profile, check boot time<br/>and memory floor]
SEC -->|no| OK1[Standard review]
SNAP -->|no| OK2[Standard review]
PERF -->|no| OK3[Standard review]
Concern 1 — Security (the host stays protected while the VMM changes)
Firecracker's threat model is explicit and harsh: the guest, including the guest kernel, is untrusted, and the VMM is privileged host code that is part of the attack surface. A guest that compromises the VMM escapes onto the host and into other tenants. Defense in depth is therefore not optional decoration; it is four overlapping boundaries — the KVM hardware-virtualization boundary, the jailer (chroot/cgroups/namespaces/privilege-drop), the seccomp-BPF syscall whitelist, and Rust memory safety — each of which must hold even if another is bypassed. Every PR that adds a syscall to a filter, emulates a new device, or adds an ioctl widens the surface and must justify itself against this model. The mechanisms live in the jailer deep dive and the seccomp deep dive; the auditing discipline is drilled in the security masterclass. Re-read those before this level's first lab — the lab assumes them.
Concern 2 — Snapshot compatibility (the state stays restorable while it changes)
A snapshot is a serialized copy of a running machine: KVM state, every device's
registers and queues, and the guest's RAM. The moment you persist that state, its
layout becomes a contract. Firecracker supports loading a snapshot taken by a range of
versions; that promise breaks the instant someone reorders a field in a device's
Persist state, changes a default, or removes a register from the saved set without a
versioned migration. The failure is not a red unit test — it is a customer's saved
microVM that will not resume after they upgrade Firecracker, which is among the worst
bugs this project can ship. The full mechanism — the Persist trait, the state file vs
memory file split, Full vs Diff, and the UFFD restore path — is in the
snapshotting deep dive and drilled in the
snapshotting masterclass.
Concern 3 — Performance and density (the fleet stays dense while the code changes)
Firecracker exists so AWS can run thousands of mutually-untrusting microVMs per host:
boot to app code in < 125 ms, < 5 MiB of memory overhead per microVM, tested
oversubscription beyond 20x. Those are not marketing numbers; they are the budget. A
path that runs once per boot, once per microVM, or on the virtio fast path is hot: a
few hundred KiB added to the per-microVM floor, or a few milliseconds added to boot,
multiplies across the fleet into stranded capacity and slower cold starts. Maintainers
do not accept "it should be about the same" — they accept "here is the --ab run, here
is the boot-time and memory delta, here is why it is within budget." The discipline is
taught in oversubscription and density
and drilled in this level's third lab and the
performance-density masterclass.
These three concerns are why maintainer review of a Level 9 PR feels slow. It is not gatekeeping for its own sake; it is the cost of invariants that cannot be un-shipped once a release goes out to a fleet measured in millions of microVMs per second.
Required Reading
These are the documents and source areas you must read before the labs. Confirm they exist on your checkout — paths drift, so run the commands rather than trusting this table.
| Read | What to extract | Confirm it exists |
|---|---|---|
docs/seccomp.md | The three thread-category filters (vmm/api/vcpu); JSON rule format; how to override with --seccomp-filter; why --no-seccomp is dev-only | ls docs/seccomp.md |
docs/jailer.md | What the jailer does, in order: chroot/pivot_root, cgroups, namespaces, mknod, priv-drop, then exec firecracker; CLI flags (--netns, --cgroup, --resource-limit, --new-pid-ns) | ls docs/jailer.md |
docs/prod-host-setup.md | Host hardening: disable SMT + KSM, per-instance UID/GID, cgroup limits, ECC+TRR RAM, no swap, drop egress to the IMDS link-local address | ls docs/prod-host-setup.md |
docs/snapshotting/ (esp. snapshot-support.md) | The Paused→create→Resumed workflow; state vs memory file; Full vs Diff; track_dirty_pages; mem_backend File vs Uffd; compatibility notes | ls docs/snapshotting/ |
| The release/support policy | The supported-version and snapshot-compatibility window — search for it: | rg -li "release.?policy|support.*window|snapshot.*compat" docs/ README.md |
resources/seccomp/ | The actual default filters you will inspect and recompile | ls resources/seccomp/ |
CHANGELOG.md | How security fixes, snapshot-format changes, and perf-affecting changes are announced and version-gated | ls CHANGELOG.md |
# One sweep to confirm the security/snapshot docs are where this level expects them.
ls docs/seccomp.md docs/jailer.md docs/prod-host-setup.md
ls docs/snapshotting/
ls resources/seccomp/
Source Code Areas to Inspect
You will live in these directories this level. Locate them with find/rg — do not
trust the paths blindly, the big crate refactor moved several of these into vmm.
| Area | Path (verify) | Why it matters this level |
|---|---|---|
| The jailer binary | src/jailer/src/ | The isolation barrier: chroot, cgroups, namespaces, mknod, priv-drop |
| Seccomp glue in the VMM | src/vmm/src/seccomp.rs | How the compiled BPF is loaded and installed per thread category |
| The compiler | src/seccompiler/ | Turns the JSON filters into BPF (seccompiler-bin); the in-tree origin of rust-vmm's seccompiler |
| The default filters | resources/seccomp/<arch>.json | The allowed-syscall whitelist you will read and edit |
| Snapshot persistence | src/vmm/src/persist.rs | The top-level save/restore of Vmm state; the microVM state file |
| Snapshot machinery | src/vmm/src/snapshot/ | Serialization/versioning of the state file; snapshot-editor reads this format |
| Snapshot editor | src/snapshot-editor/ | Inspect/edit a state file out-of-band |
| Per-device persistence | src/vmm/src/devices/virtio/*/persist.rs | Each device's Persist impl — where snapshot-compat bugs are born |
| Build from snapshot | src/vmm/src/builder.rs (build_microvm_from_snapshot) | The restore-side builder |
| The performance tests | tests/integration_tests/performance/ | Boot-time and memory-overhead tests; the --ab comparison harness |
| Metrics | src/vmm/src/logger/metrics.rs (verify) | The structured metrics you read when profiling |
# Locate the security, snapshot, and performance surfaces on YOUR branch.
find src/jailer -name '*.rs' | head
rg -n "seccomp" src/vmm/src/seccomp.rs | head
fd -e json . resources/seccomp/ 2>/dev/null || find resources/seccomp -name '*.json'
rg -n "trait Persist" src/vmm/src/ # the persistence contract
rg -ln "impl .*Persist" src/vmm/src/devices/ # every device that persists state
find tests/integration_tests/performance -type f
Key Types Quick Reference
Do not memorize these; run the rg and read them. They are the types the labs touch.
| Type / item | Where (verify with rg) | Role |
|---|---|---|
Persist (trait) | rg -n "trait Persist" src/vmm/src/ | The save/restore contract every persisted component implements; defines the serialized State |
MicrovmState | rg -n "struct MicrovmState" src/vmm/src/persist.rs | The whole microVM's serialized state — what lands in the state file |
VmInfo / version field | rg -n "VmInfo|app_version|SNAPSHOT_VERSION|version" src/vmm/src/persist.rs src/vmm/src/snapshot/ | How the snapshot records the version that wrote it |
SeccompFilter / install fn | rg -n "SeccompFilter|apply_filter|install" src/vmm/src/seccomp.rs | Loads compiled BPF and installs it on the current thread |
SyscallRule | rg -n "SyscallRule|SeccompRule|SeccompCondition" src/seccompiler/ | The JSON rule shape: a syscall plus optional args operators |
Env (jailer) | rg -n "struct Env" src/jailer/src/ | The jailer's parsed configuration and the steps it runs before exec |
build_microvm_from_snapshot | rg -n "fn build_microvm_from_snapshot" src/vmm/src/builder.rs | The restore-side builder that reconstructs a Vmm from MicrovmState |
track_dirty_pages | rg -n "track_dirty_pages" src/vmm/src/ | Enables dirty-page tracking for Diff snapshots (was enable_diff_snapshots) |
GitHub Issue Categories for Level 9
These are the labels and issue shapes a Level 9 graduate can credibly take on. Find them
with gh:
gh issue list -R firecracker-microvm/firecracker \
--label "Type: Enhancement" --search "snapshot OR seccomp OR jailer OR performance" --limit 40
gh issue list -R firecracker-microvm/firecracker --label "Kani" --limit 20 # formal-verification work
| Category | Typical shape |
|---|---|
| Security hardening | Tighten a seccomp rule's args, document a jailer flag, audit a device's host I/O surface |
| Snapshot compatibility | Add a versioned field to persisted state with a migration; a snapshot-restore edge case across versions |
| Performance / density | A boot-time or memory-overhead regression; an I/O-engine improvement; a metrics-driven optimization |
| Diagnostics | Better error on a failed restore; surfacing a denied-syscall failure clearly |
Formal verification (Kani) | Proofs over the virtqueue/device parsing that feeds persisted or guest-controlled data |
Deliverables
Demonstrate all of the following before attempting the capstone:
-
Completed Lab 9.1: booted a microVM under the
jailer from a hand-built
/srv/jailerchroot, inspected the default seccomp JSON, recompiled a modified filter withseccompiler-bin, and observed a denied syscall under your custom filter. -
Completed Lab 9.2: created a Full snapshot,
restored it on a fresh Firecracker process, inspected both files with
snapshot-editor, and explained thePersisttrait and a snapshot-compat hazard. -
Completed Lab 9.3: measured boot time and memory
overhead, ran an A/B (
--ab) comparison, and attributed a representative regression to a commit by bisection. -
A written review (GitHub-review style) of one real Firecracker PR that touches
seccomp, the jailer, a
Persistimpl, or a performance path — explicitly assessing attack surface, snapshot compatibility, and measured cost. - From memory: explain the four defense-in-depth layers, the difference between Full and Diff snapshots, and one failure mode plus one mitigation for each of the three maintainer concerns.
Common Mistakes
| Mistake | Consequence | Fix |
|---|---|---|
Running production with --no-seccomp to "make it work" | The whole syscall whitelist is gone; a VMM compromise has the host's full syscall surface | Never in prod; if a real syscall is missing, add it to the filter with the tightest args and justify it |
Adding a syscall to a seccomp filter with no args constraint | A broad allow widens the attack surface more than necessary | Constrain with operators (eq/masked_eq on flags/fd) so only the intended call shape passes |
| Assuming the jailer applies seccomp | Misreasoning about the boundary; seccomp gaps go unnoticed | The jailer sets up isolation then execs firecracker; firecracker installs seccomp per thread |
Reordering or removing a field in a device's Persist state | A snapshot taken before the change cannot be restored after it — silent corruption or a restore failure | Append + version-gate; add a snapshot-compat test that loads an old snapshot |
Treating a Persist change as an internal refactor | The serialized layout is a public contract; you broke cross-version restore | Any change to persisted state is a versioning event — see the snapshotting deep dive |
Claiming "no perf impact" with no --ab run | Reviewer cannot verify; the change may have added ms to boot or KiB to the floor | Run tools/devtool test --performance --ab, report boot-time and memory deltas |
| Optimizing an un-profiled path | Effort on a cold path; the real hot path untouched | Profile (perf/metrics/tracing) first; attribute before you optimize |
Forgetting track_dirty_pages before a Diff snapshot | The Diff snapshot is wrong or refused | Set track_dirty_pages: true in /machine-config before boot; note Diff is dev-preview (verify on your branch) |
How to Verify Success
# 1. You can build the binaries this level uses.
tools/devtool build --release
ls build/cargo_target/$(uname -m)-unknown-linux-musl/release/{firecracker,jailer,seccompiler-bin,snapshot-editor}
# 2. You can locate every Level 9 surface without notes.
rg -n "trait Persist" src/vmm/src/
rg -n "SeccompFilter\|apply_filter" src/vmm/src/seccomp.rs
find src/jailer -name '*.rs' | head
find tests/integration_tests/performance -type f
# 3. You can run the security and performance suites.
tools/devtool test -- integration_tests/security/ 2>/dev/null || \
rg -l "seccomp\|jailer" tests/integration_tests/security/
tools/devtool test --performance --ab 2>/dev/null || echo "review the --ab harness in Lab 9.3"
You have succeeded when you can, from memory, state the threat model in two sentences,
name the three files that make a snapshot a compatibility contract, and quote the boot-
time and memory-overhead budgets — and when the rg commands above land on real code on
your branch.
Maintainer Profile: Level 9 Graduate
| You can now | Evidence |
|---|---|
| See the attack-surface implication of a one-line seccomp/jailer/device change | You can point to the unconstrained syscall or the new ioctl and propose the tighter form |
| Prove a snapshot stays restorable, not eyeball it | You loaded an old snapshot on a new build and ran a compat test |
| Quantify a density change | You produced --ab boot-time and memory-overhead deltas, before/after |
| Reason about the security boundary as layers | You can explain which layer catches what, and what a single-layer bypass still leaves standing |
| Review a Level 9 PR like a maintainer | You apply surface-first, compat-second, cost-third — not "CI is green" |
| Connect a change to the fleet | You know whether it moves the per-microVM floor or the boot budget, and whether it gates a snapshot version |
You are now ready for the capstone — an end-to-end contribution where you select a real issue, reproduce it, find the root cause, implement and test the fix (with security, snapshot, and performance discipline), open the PR with DCO sign-off and a CHANGELOG entry, and write it up. Start at the Capstone Overview. The three reflexes you build here — does this widen the attack surface, can this snapshot still restore, did I measure the density cost? — are the ones the capstone evaluates hardest.
Note: Most engineers never reach this tier on an open-source project, not because they can't, but because they stop at "my PR is green." The distance from contributor to maintainer is the distance from "it works" to "the host is still safe, every snapshot still restores, and the fleet is still dense — for everyone who upgrades into it." That is the entire content of Level 9.