Security1 publisher2 min readPublished
A guest escape can reach Firecracker's blocked syscalls through io_uring
Amazon patched a symlink-following chown in Firecracker's jailer that only affected aarch64. Behind it sits a seccomp policy that permits io_uring, and researcher antitree shows how that hands back file-system calls the filter denies.
The Watch · Security desk

What happened
- Amazon merged pull request #5956 into Firecracker to fix a jailer flaw that affected aarch64 only; the post that describes it dates the merge as a few months earlier.
- antitree's example shows io_uring_setup passing the filter, then RENAMEAT, MKDIRAT and SYMLINKAT opcodes submitted through the shared ring and executed by io_uring_enter.
- The async Block IO Engine that required io_uring arrived in v1.0.0 four years ago and is still labelled a developer preview.
Compiled by The WatchSomething wrong?How this is made
Why it matters
- exposure Every Firecracker host carries the io_uring allowance in its filter regardless of architecture or block engine choice, so a VMM-process compromise reaches file-system mutation on x86_64 boxes that were never touched by the jailer bug.
- constraint Testing a seccomp policy against the syscall table no longer tells you what the policy stops; the check has to submit the denied operations as io_uring opcodes, which most teams have never done.
- decision Teams weighing the async block engine for throughput are choosing to widen the filter for code that has sat in developer preview for four years, and antitree suggests the main beneficiary is AWS rather than the average operator.
- precedent antitree's description of the bug shape, a privileged file operation with an attacker-influenceable path, points at where the next one lands: any root supervisor that writes into a directory the unprivileged side can rearrange.
seccomp-bpf filters on syscall numbers. io_uring takes the operation as an opcode written into a shared memory ring, so the kernel performs it without the process ever issuing the number the filter watches [9].
In antitree's example, symlinkat is syscall 36 and the Firecracker policy blocks it, so the jailed VMM calling it directly takes SIGSYS [10]. Submitted as IORING_OP_SYMLINKAT after io_uring_setup(8) and an mmap of the ring with MAP_SHARED, the same operation runs, and IORING_OP_MKDIRAT and IORING_OP_RENAMEAT cover syscalls 34 and 38 by the same route [11]. Two permitted syscalls, io_uring_setup and io_uring_enter, carry three the policy denies [17]. The post presents this as an example sequence and does not report running it against a live jail [20].
Why io_uring is in the policy at all goes back to v1.0.0, four years before the post, when Firecracker shipped sync and async modes for its Block IO Engine. Async needs io_uring [12]. Four years on, async is still marked "Developer preview" [13]. "I have no inside knowledge but I would imagine that this feature is more likely used by AWS who would be more invested in its performance than a normal user," antitree wrote [14]. He calls the bypass risk one that "appears to be self inflicted" [15].
The async engine is not the default [8]. Operators who never turned it on are running a filter that permits the calls it needs [18].
The jailer bug that started this is narrower. The jailer runs as root to set up the chroot, namespaces, cgroups and UID/GID before it drops privileges and execs the Firecracker binary [2]. During that setup it wrote a value into a file inside the jail and then chowned the file to the unprivileged jail UID, two separate calls, both following symlinks, neither checking the path they landed on [3]. It is a TOCTOU [4]. On other architectures the aarch64-specific copy_cache_info() and copy_midr_el1_info() were compiled out, so the chown was never there [5]. "This bug isn't that interesting when you look at all the dependencies," antitree wrote [6].
Amazon's PR #5956 closes the aarch64 path [1]. The filter behind it is unchanged, which is where validation gets awkward: a test that calls symlinkat directly gets SIGSYS and records the policy as working, while the same three operations submitted as io_uring opcodes are never seen by the filter [21].
What to watch
- Whether Firecracker drops io_uring from the applied seccomp policy, or promotes the async Block IO Engine out of developer preview after four years.
- Any published proof-of-concept that chains control of the Firecracker VMM process to file-system writes through io_uring opcodes.
- Whether the same io_uring allowance turns up in other microVM sandboxes' shipped seccomp policies; antitree's post covers Firecracker only.