DEV Community

Cover image for 9.3 to root in 5 hops: I audited the Firecracker escape
frank chu
frank chu

Posted on

9.3 to root in 5 hops: I audited the Firecracker escape

9.3 to root in 5 hops: I audited the Firecracker escape

A 9.3-rated kernel flaw plus five chain links turned a guest network packet into root code execution on the host. I did not re-run the escape — my sandbox has no /dev/kvm, and reproducing a guest-to-host exploit is not a thing you do casually. So I audited every link in the chain against primary sources instead: the CVE record, the kernel fix commit, the Firecracker v1.16.1 source, and the kernel's own pt_regs struct. Four of the five links check out exactly. The fifth is where the story gets interesting.

The flaw underneath it all

The chain starts with CVE-2026-99999, published October 15, 2026, CVSS 3.1 score 9.3, assigned by the Linux CNA. The description is short and ugly: a packet with bad checksum metadata reaches the IPv4 stack, skb_checksum_help corrupts the network header, "and cause a bunch of mischief." It was discovered by Paulos Yibelo of Pwn.ai while working on Vercel's $1 million sandbox challenge — Vercel's sandboxes run on KVM-based Firecracker microVMs, which is why the researcher had a Firecracker target at hand.

I pulled the fix commit, 6785011f8b16, written by Michael S. Tsirkin. It is 44 lines added, 12 removed, across exactly 6 files — the same 6 files the CVE record lists as affected:

include/net/ip.h
net/bridge/netfilter/nf_conntrack_bridge.c
net/ipv4/ip_output.c
net/netfilter/nfnetlink_queue.c
net/netfilter/xt_CHECKSUM.c
net/xfrm/xfrm_output.c
Enter fullscreen mode Exit fullscreen mode

The commit message is unusually honest for a security fix: "A more complete validation at input is forthcoming, but needs more work." The fix checks checksum_start before completing the checksum — defense in depth, not a root-cause repair. Fixed stable releases: 5.10.272, 5.15.223, 6.1.190, 6.6.159, 6.12.113, 6.18.57, and 7.2.11. AWS's bulletin (2026-137-AWS) says its managed EC2, Lambda, and Fargate are not affected — but self-managed KVM hosts, including Firecracker, on unpatched kernels are. That split is the whole story: the vulnerability is in the host kernel, so updating the guest does nothing.

The five hops, and what I could check

Pwn.ai's Part 2 post walks the chain from a host-memory read primitive (Part 1) to full takeover of the Firecracker process. Here is each hop, with what I verified:

1. Guest page to host page. The guest plants a nonce in a page, then walks candidate EPT roots to translate its guest physical address to a host physical address. Recorded result: GPA 0x08f03000 → HPA 0x165610000. I can't verify the addresses — they're run-specific — but the mechanism is standard EPT walking, and the CVE's scenario text confirms the same path: guest-crafted virtio-net checksum metadata reaches the host TAP, tun_get_user() feeds the skb to the IPv4 stack.

2. Host page to Firecracker process. A reverse page-table walk finds which host process maps that page, recovering its PGD (0x14afc2000). Then the ELF image is identified by a known byte sequence in Firecracker's write(2) wrapper, giving a load bias (0x7298c2400000) under ASLR. I checked the v1.16.1 release exists — published July 2, 2026 — and that the binary links libc (libc = "0.2.186" in the vmm crate), so a write wrapper address is a real thing to find.

3. The live vCPU frame. The frame finder searches host memory for a pt_regs frame matching an active KVM_RUN ioctl. I verified the struct in the kernel source — arch/x86/include/asm/ptrace.h has exactly the fields the finder checks:

struct pt_regs {
    unsigned long bx, cx, dx, si, di, bp, ax;
    /* ... segment registers ... */
    unsigned long orig_ax;
    unsigned long ip;
    unsigned short cs;
    unsigned long flags;
    unsigned long sp;
    unsigned short ss;
};
Enter fullscreen mode Exit fullscreen mode

The saved ip says where the thread resumes, the saved sp says which stack it uses. That is the whole attack surface in one struct: whoever controls pt_regs controls the thread.

4. The KVM_RUN return. This is the hop I could check most concretely. In Firecracker v1.16.1, the vCPU thread's main loop is in src/vmm/src/vstate/vcpu.rs. run() applies the seccomp filter, then running() loops on run_emulation(), which calls self.kvm_vcpu.fd.run() — the KVM_RUN ioctl — and handles the exit:

match self.kvm_vcpu.fd.run() {
    Err(ref err) if err.errno() == libc::EINTR => { /* ... */ }
    emulation_result => handle_kvm_exit(&mut self.kvm_vcpu.peripherals, emulation_result),
}
Enter fullscreen mode Exit fullscreen mode

So the thread really does sit inside that ioctl while the guest runs, with its userspace registers saved in exactly the frame the researchers targeted. The exploit rewrites the frame to point at the write wrapper with fd 2 (stderr), a buffer holding the 8-byte marker N8V23M2\n, then a xor eax, eax; ret gadget to normalize the return value — write returns 8, KVM_RUN returns 0 — before jumping back to the ioctl continuation. A serial-port write from the guest triggers the return. The host captured exactly 8 bytes on Firecracker's stderr, PID 2936 still alive, guest SSH still responsive, uptime 1615.72 seconds, all three stack words restored.

5. Root. The target ran Firecracker v1.16.1 directly as root under systemd-run, so the controlled call executed as root. The researchers are explicit that the configuration had no jailer, and that production containment — dropping privileges, namespaces, seccomp — "requires a separate assessment." I checked the jailer source: it does chroot plus a new mount namespace with pivot_root. The jailer is a real second line of defense, and this demo ran without it.

What I could not verify

Three things, stated plainly. I did not verify the ROP gadget bytes or the frame address — those are run-specific and need a live Firecracker, which I don't have. The host kernel 7.0.0-1011-aws is the researchers' stated target config; the naming pattern matches AWS kernels, but I didn't independently confirm that build. And the guest kernel 6.18.44+ predates the 6.18.57 fix release, which is consistent with the escape working — but "consistent with" is not "confirmed."

One more scope note: secondary coverage says host memory disclosure was demonstrated against Vercel's production sandbox, but Pwn.ai's own post does not make that claim — it describes a controlled lab setup. Vercel confirmed the findings and said no reports showed access to customer files, secrets, or application data. I am not repeating the stronger claim as fact.

The takeaway

The escape needs two failures stacked: an unpatched host kernel and a Firecracker running without its jailer. Either one alone stops this exact chain — the CVE fix closes the memory primitive, and the jailer means a compromised VMM process is still contained. The uncomfortable part is how small the kernel fix is: 44 lines, explicitly described as partial, with the real input validation still to come. If you run your own KVM or Firecracker hosts, the patch-and-reboot is the action; the guest kernel version is irrelevant. And if you run Firecracker without the jailer because it's simpler, this is the post that prices that shortcut: the difference between "the guest escaped the VM" and "the guest owns the host" was a configuration flag.

Sources: CVE-2026-99999 record (cveawg.mitre.org, published 2026-10-15, CVSS 3.1 9.3); kernel fix commit 6785011f8b16 (44 insertions, 12 deletions, 6 files); Pwn.ai, "Firecracker VM Escape: PwnEscape Part 2" (2026-11-06) and Part 1; AWS Security Bulletin 2026-137-AWS; Firecracker v1.16.1 source (src/vmm/src/vstate/vcpu.rs, src/jailer/); Linux arch/x86/include/asm/ptrace.h. The pt_regs fields, the KVM_RUN call site, the jailer code, the fix diff stats, and the release date I checked myself on 2026-11-06; the escape's run-specific addresses and outputs are the researchers' recorded values.

Top comments (0)