By falco365 · Published April 24, 2026

CVE-2026-2091: io_uring race turns any local user into root

A TOCTOU race in the Linux io_uring fixed-file-table cleanup path lets any unprivileged user trigger a use-after-free on the task credentials struct, leading to root. Working PoC public. CVSS 7.8. Patched in 6.8.9, 6.7.11, 6.6.28 LTS.

CVE-2026-2091: io_uring race turns any local user into root
Analysis

CVE-2026-2091 is a time-of-check / time-of-use race in the Linux kernel's io_uring fixed-file-table registration path. An unprivileged local user can race io_uring_register(IORING_REGISTER_FILES_UPDATE) against ring-teardown to trigger a use-after-free on struct cred, then reuse the freed slot to gain effective UID 0. A working PoC has been public on Full Disclosure since April 19 and is weaponized in several container-escape toolkits.

Why io_uring is the worst place for this

io_uring is the fastest I/O surface in modern Linux and, therefore, enabled by default in every mainstream distro since 5.1. Container runtimes mount it inside containers unless explicitly seccomp-filtered. The fixed-file-table model — the exact surface this CVE hits — is a performance optimization that lets long-lived rings pre-register file descriptors, which means many workloads keep rings open across privilege boundaries.

Combine pre-registered tables with the race, and you get an exploit primitive that:

  • Does not require CAP_SYS_ADMIN or any capability at all.
  • Runs from inside a default Docker or containerd container.
  • Escapes to host root if the container runs with the host kernel (i.e. everywhere except gVisor / Kata).
  • Leaves minimal forensic trace — the race success rate is ~30%, so logs show "weird syscall sequence" not "obvious exploit."
This is the fourth io_uring LPE since 2022. The subsystem is not going away, and the sharp edges are not going away with it. Treat io_uring as a dangerous kernel surface and seccomp-filter it wherever the workload doesn't need it.
Who is exposed
  • Any Linux host running kernel 6.1 LTS through 6.7. The LTS cutoff is important — 5.15 LTS is not affected.
  • Container workloads using default seccomp profiles (Docker, Kubernetes with the default RuntimeDefault seccomp profile still allows io_uring_* syscalls).
  • Multi-tenant CI runners (GitHub Actions self-hosted, GitLab runner, Buildkite agents) — attacker PRs can run the PoC and root the runner host.
  • Shared Linux hosts (universities, bastion hosts, shell-access shared-hosting).
Mitigation

Upgrade the kernel. Patched versions:

  • Mainline: 6.8.9 or later.
  • 6.7 branch: 6.7.11.
  • 6.6 LTS: 6.6.28.
  • 6.1 LTS: 6.1.85.

If you can't reboot immediately, the cheapest mitigation is disabling io_uring entirely via sysctl:

  • sysctl -w kernel.io_uring_disabled=2 (requires kernel 6.6+). Set in /etc/sysctl.d/ to persist.
  • For container hosts, add io_uring_setup and io_uring_register to the seccomp deny list.
  • The artifact folder includes a Falco rule that alerts on IORING_REGISTER_FILES_UPDATE calls from unprivileged PIDs — decent catch-rate for the public PoC.
The broader pattern

io_uring LPEs are now routine. The kernel team has been patching them quarterly since 2022. If you're not explicitly getting performance from io_uring in a given workload, disable it. The default-on posture made sense for 5.15-era systems that didn't have kernel.io_uring_disabled; it no longer does. Production Linux fleets should treat io_uring the way they treat eBPF — a feature worth having, but locked down to the workloads that actually need it.

The same lesson applies more broadly. Linux kept adding LPE entries through 2026 — CVE-2026-31431 (CopyFail) in April was a different primitive (a four-byte page-cache write through algif_aead), but the same defensive posture catches both: deny-list-by-default for any kernel surface that isn't load-bearing for the workload. AF_ALG, io_uring, eBPF, user namespaces — none of them belong unconditionally enabled in containers running untrusted code.