# Firecracker VM escape runs code as root on the host via KVM_RUN

> PwnEscape Part 2 shows a guest taking over a live vCPU thread and executing a syscall as root on the host, against Firecracker v1.16.1 with no jailer.

- Published: 2026-11-07
- By: Samir Sengupta (https://www.samcodeman.com/about)
- Canonical: https://www.samcodeman.com/writing/firecracker-vm-escape-pwnescape-kvm-run-root
- How this was made: Drafted with Claude, Anthropic's model from 1 cited source, then checked against it in a second editing pass. Editorial policy: https://www.samcodeman.com/editorial-policy
- Disclosure: I build Wayne, an AI coding harness for VS Code, and Omnitron, an agent harness, so I work in this category.

## The short version

- The chain was demonstrated against Firecracker v1.16.1 on x86-64 with host kernel 7.0.0-1011-aws and guest kernel 6.18.44+, running as root under systemd-run with no jailer.
- The guest locates Firecracker in host memory through EPT and page-table walks, then rewrites the saved pt_regs frame of a thread sitting in KVM_RUN.
- The proof call was write(2, "N8V23M2\n", 8) to Firecracker's stderr, after which the VM stayed up with SSH responsive and the stack restored.
- The published proof reaches host root only in an unjailed, run-as-root setup; the authors say a jailed deployment with namespaces and seccomp needs a separate assessment they do not provide.

PwnEscape Part 2 is a working Firecracker VM escape: the researchers at pwn.ai broke out of a microVM and ran code as root on the host, demonstrating CVE-2026-99999. The target was Firecracker v1.16.1 on x86-64, and the proof was a single syscall the guest forced the host process to execute. Firecracker kept running throughout and the guest VM stayed reachable over SSH.

Firecracker is designed to keep untrusted code inside a microVM, which is why agent and code-execution sandboxes run on top of it; one Hacker News commenter runs hundreds of database engines on Firecracker in the ClickBench playground. This note works through what the exploit affects, how the chain reaches host code, and what teams running these sandboxes should patch or harden. It builds on a host memory primitive that Part 1 introduced; Part 2 is the step that turns that read/write access into control of a live Firecracker thread.

## Which versions and configurations the firecracker vm escape affects

The demonstrated target was Firecracker v1.16.1 on x86-64, with host kernel 7.0.0-1011-aws and guest kernel 6.18.44+. The researchers launched Firecracker directly as root under systemd-run and pinned it with taskset, so the controlled call executed as root on the host. The configuration used no jailer.

That last detail matters for scoping. Firecracker's production containment drops privileges and restricts the process through namespaces and seccomp. The authors are explicit that a deployment using those controls needs a separate assessment of the privileges and resources available after the VMM is compromised. In other words, the published proof reaches host root in an unjailed setup; it does not claim to defeat the jailer.

## How the guest finds Firecracker in host memory

The escape never guesses addresses; it discovers them. Four resolvers run in sequence, each handing a validated value to the next, all driven by the memory primitive from Part 1. The entry point is a single page inside the guest that the attacker controls.

1. An EPT resolver places a recognizable nonce in a guest page, then searches candidate Extended Page Table roots, walks each one for the page's guest physical address, and checks the result for the nonce. The search produced two candidates; nested page-table validation left one, yielding the host physical address of the guest page.
2. A reverse mapper searches the native host page tables for a mapping of that host physical address, recovers the virtual address and page global directory (PGD), and re-walks it both ways to confirm. It returned one validated address space, kept as host_pgd.
3. An image mapper identifies the Firecracker ELF in those mappings by a known byte sequence in its write(2) wrapper, then recovers the ASLR load bias so runtime addresses can be computed as load_bias + code_address_in_elf.
4. A frame finder locates the saved register frame of the vCPU thread currently in KVM_RUN.

The frame finder is the discriminating step. A matching return address alone would also pick up stale stack contents, so the candidate pt_regs frame had to pass several related checks at once: the syscall identity and in-progress state matching an active ioctl, the ioctl request being KVM_RUN, the file descriptor in range, the saved instruction pointer matching the validated ioctl continuation, the stack pointer translating through host_pgd, valid segment selectors and flags, and a caller return address inside the Firecracker text. One candidate survived, stored as frame_hpa.

## How the exploit redirects the return from KVM_RUN

While a thread is inside the KVM_RUN ioctl, the host kernel holds its saved userspace registers in a pt_regs structure. The instruction pointer says where execution resumes and the stack pointer says which stack to use, which makes the return from KVM_RUN the target. The payload was deliberately small: make Firecracker write a marker to its own stderr.

```c
write(2, "N8V23M2\n", 8);
```

The eight-byte string fits one stack word, and the buffer pointer had to reference the string inside Firecracker's address space, so it was stored on the process's private stack. The attacker saved three stack words, then rewrote the saved frame: RIP to Firecracker's write wrapper, RDI to 2, RSI to the marker's virtual address, RDX to 8, and RSP to a temporary return sequence.

Return values needed care too. A successful write of eight bytes returns 8, while a successful KVM_RUN returns zero, so the return sequence placed an xor eax,eax; ret gadget first to clear the value before resuming at the original ioctl continuation. The guest then wrote one byte to its serial port to force the vCPU thread out of KVM_RUN. The kernel restored the modified registers, Firecracker entered the write wrapper, emitted the marker, cleared eax, and rejoined its original path. The helper then restored all three stack words.

## What the recovery checks proved about a live thread

The host runner captured Firecracker's stderr separately from the guest and saw exactly eight bytes, including the newline. The process, PID 2936, was alive after the trigger; guest SSH was responsive with uptime 1615.72 seconds; the three temporary stack words were restored; executable text and shared libraries were unmodified; and no host kernel errors were observed. A fresh validation pass that made no memory changes repeated the register, code, and stack checks after the trigger and found no stale marker.

Those three records answer different questions. The guest log shows the trigger and restoration, the host stderr capture shows Firecracker executed the chosen call, and the post-restoration validation shows the state was clean. Extending the write wrapper to other syscalls would require choosing code and arguments compatible with the process's mappings and syscall policy, which the authors note but do not demonstrate.

## Does the jailer stop it, and what to harden

Commenters on Hacker News pushed on the jailer gap directly. One noted that a properly jailed Firecracker would greatly reduce the blast radius and that the article stops at the jailer boundary, so turning this into full host control would need a second local privilege escalation. Another disagreed, arguing the exploit compromises the host kernel and that the detour through Firecracker userspace was a choice, not a requirement; by that reading the kernel compromise is the real event and the VMM takeover is one way to show it. The sources themselves attribute root execution to the unjailed, run-as-root configuration and do not settle which reading holds once seccomp and namespaces are in place.

Several commenters framed the root cause as a flaw in the host kernel's networking stack rather than in Firecracker, with the comparison that running Firecracker without a jailer is like setting a password to "password". They also pointed to a separate recent Firecracker escape from Coinspect that popped the jailer through io_uring: Firecracker filtered the rings it initiated with seccomp, so the attacker triggered a new, unfiltered ring. The lesson commenters drew is that the jailer is a real boundary but not a kernel-attack-surface eraser. The pwn.ai source does not itself name a networking flaw as the root cause.

The concrete hardening steps that follow: run Firecracker under its jailer with privilege dropping, namespaces, and seccomp rather than directly as root under systemd-run; if the root cause is the host kernel networking flaw commenters describe, patch the host kernel once a fix ships; and treat every microVM as fronting a large kernel attack surface for local privilege escalation. The broader isolation debate in the thread, whether gVisor-style software isolation or a tiny verified microhypervisor like Nova beats relying on hardware isolation, is unresolved; one commenter's intuition was that gVisor is not stronger in a fair comparison because almost all the vulnerabilities live in I/O code that every sandbox shares.

The part the sources leave open is the one production teams most need: the exact privileges and resources an attacker retains after compromising a jailed VMM. The authors call for a separate assessment and do not provide one, and the PwnEscape proof as published assumes a configuration no hardened deployment should be running.

## Questions this raises

### Does the jailer stop this Firecracker escape?

The published proof reaches host root only in an unjailed, run-as-root configuration and does not claim to defeat the jailer. The authors say a jailed deployment, with dropped privileges, namespaces and seccomp, needs a separate assessment, and commenters disagree on whether the kernel compromise alone would still matter.

### How does the guest find Firecracker in host memory?

Four resolvers run in sequence off the Part 1 memory primitive: an EPT resolver finds the guest page's host physical address, a reverse mapper recovers the virtual address and PGD, an image mapper recovers the ASLR load bias, and a frame finder locates the vCPU thread's saved register frame while it is in KVM_RUN.

### What did the exploit actually make Firecracker do?

It rewrote the saved pt_regs frame to point RIP at Firecracker's write wrapper with RDI=2, RSI at a marker string and RDX=8, then forced the vCPU out of KVM_RUN. An xor eax,eax; ret gadget cleared the return value before resuming, and the host captured exactly eight bytes on stderr.

## Key facts

- Target was Firecracker v1.16.1 on x86-64, host kernel 7.0.0-1011-aws, guest kernel 6.18.44+
- Demonstrates CVE-2026-99999; Firecracker launched as root under systemd-run with no jailer
- Proof was one forced syscall: an 8-byte marker written to Firecracker's stderr via its write(2) wrapper
- Four resolvers (EPT, reverse mapper, image mapper, frame finder) locate the vCPU thread in KVM_RUN
- After the trigger PID 2936 stayed alive, guest SSH responded with uptime 1615.72s, stack and text unmodified

## Sources

1. [Firecracker VM Escape: PwnEscape Part 2](https://pwn.ai/blog/firecracker-escape) - pwn.ai

Discussion read (practitioners' reactions, not a source): https://news.ycombinator.com/item?id=50397219
