Blog
Linux KernelPrivilege EscalationBad EpollGenerative Pentest

Bad Epoll (CVE-2026-46242): a 6-instruction race that hands any Linux user root

Bad Epoll (CVE-2026-46242) is a Linux kernel use-after-free that gives any unprivileged user root — on servers and Android. A patch existed since April but stayed silent for 70 days. Here is the fix runbook.

Zero Hunt Research··7 min read

A use-after-free in the Linux kernel's epoll subsystem, disclosed publicly on July 3, lets any unprivileged local process become root — on Linux servers, desktops, and Android phones running 6.6-series kernels and newer. The exploit is 99% reliable on the reference target despite a race window only six machine instructions wide. The upstream fix has been in the mainline tree since April 24, but it landed quietly, sat unannounced for roughly 70 days, and many distributions still had no backport shipped when the writeup dropped. There is no configuration toggle that turns epoll off. That combination — trivial local-to-root, enormous install base, and a patch that existed but nobody was told to apply — is why CVE-2026-46242, nicknamed "Bad Epoll," is worth stopping for.

What Bad Epoll actually does to the kernel

Epoll is the I/O event-notification interface that essentially every modern Linux network service leans on — web servers, databases, message brokers, container runtimes. The bug lives in ep_remove(). The function clears file->f_ep under file->f_lock, then keeps using the same file object inside the critical section through hlist_del_rcu() and spin_unlock(). A concurrent __fput() — the kernel's file-release path — can observe a transient NULL there, skip eventpoll_release_file(), and go straight to f_op->release. That frees a watched struct eventpoll while it is still in use. Classic use-after-free, in one of the most-exercised code paths in the kernel.

The offending logic traces back to a single commit from April 2023, which is why kernels before mainline 6.4 — and Android devices still on 6.1, like the Pixel 8 — predate the bug and are not affected. Everything from 6.4 onward is.

The researcher, Jaeyoung Chung of Seoul National University's CompSec Lab, turned that UAF into a weaponized exploit and submitted it to Google's kernelCTF program (bounty: $71,337). The public write-up and proof-of-concept chains four linked epoll file descriptors, loops the race until it wins, then uses the freed object to get an arbitrary kernel-memory read through /proc/self/fdinfo and pivots into a ROP chain. Reported reliability: 99% on the LTS-6.12.67 kernelCTF target, 98% on COS-121. A six-instruction window sounds unwinnable by hand; in a tight loop, statistics do the work.

Why AI static auditing missed the Bad Epoll race

Here is the part that should interest anyone betting their security program on automated code review. The same epoll code was audited by a frontier AI model — Anthropic's Mythos — which did surface a neighboring race, now tracked as CVE-2026-43074. It did not find Bad Epoll. Two reasons are given, and both are structural, not incidental:

  • The window is six instructions wide. Reasoning statically about which exact thread interleaving frees the object while another path still holds it is close to intractable by inspection. The vulnerable code looks fine unless you can imagine the precise schedule that breaks it.
  • There was almost no runtime signal. Once CVE-2026-43074 was fixed, Bad Epoll's use-after-free usually does not trip KASAN, the kernel's primary memory-error detector. A fuzzer or a runtime-assisted auditor gets no red flag on most runs.

"We found the loud one and shipped a patch. The quiet one, in the same twenty lines, needed a specific six-instruction interleaving that neither static reasoning nor KASAN reliably surfaces."

That is the honest lesson, and it cuts against a comforting story the industry keeps telling itself. Static analysis — human or AI — is pattern-matching against code that looks dangerous. A race this narrow does not look dangerous. What actually finds it is running the race, thousands of times, against the real scheduler, and measuring whether root falls out. Detection here is empirical, not analytical.

The patch that hid in plain sight for 70 days

The timeline is its own case study in why "patched upstream" is not "protected":

Date Event
April 2023 Commit introduces the two epoll races
2026-02-17 Chung reports the flaw
2026-04-02 First fix merged — incomplete, does not fully close it
2026-04-22 Remaining flaw re-reported
2026-04-24 Correct fix lands: mainline commit a6dc643c6931
~2026-07-03 Public write-up + PoC released; roughly 70 days after the real fix

For those 70 days the fix was in mainline, unlabelled as security-critical, while distribution kernels lagged and no advisory told operators to prioritize it. Canonical, Red Hat, and the Debian Security Team move fast once a CVE of this size is public — but the exposure window is defined by your fleet's patch cadence, not upstream's commit date. And because Bad Epoll is a local privilege escalation, not a remote RCE, it rarely arrives first. It is the second stage: a web-app bug, a leaked SSH key, or a poisoned dependency gets an attacker a shell as some low-privilege service account, and Bad Epoll turns that shell into root — quietly, with no crash, no network callback, no KASAN splat.

Remediation

Treat this as a patch-priority event on any host where untrusted users, untrusted code, or internet-facing services can get a local shell — which is most of them.

1. Am I affected?

Check your running kernel version:

uname -r

Mainline 6.4 and newer are affected; 6.1 and older are not. On Android, 6.6-series kernels and newer (including current Pixel hardware) are vulnerable. Version strings on distributions are backport-mangled, so the reliable signal is your distro's advisory for CVE-2026-46242, not the raw number. Confirm whether the fix is present:

# Debian/Ubuntu — is the patched kernel installed and booted?
apt-changelog linux-image-$(uname -r) 2>/dev/null | grep -i CVE-2026-46242 || \
  echo "No CVE-2026-46242 fix recorded for the running kernel"

2. Patch — the exact fix

There is no single magic version number; the fix is upstream commit a6dc643c6931 (landed 2026-04-24). The practical action is to install your distribution's backported kernel and reboot — a live kernel keeps the vulnerable code resident until it is replaced. Track the vendor advisories directly: Ubuntu USN, Debian DSA, Red Hat RHSA, SUSE, and your Android vendor's monthly security bulletin. Do not accept "package updated" as done; verify the booted kernel carries the fix.

3. Can't reboot yet? Compensating controls

There is no workaround that disables epoll — the kernel cannot run without it. Reduce who can reach the primitive instead:

  • Shrink local attack surface: no untrusted shells, no untrusted containers sharing the host kernel, tightened SSH.
  • Harden the container boundary — a shared host kernel means one container's local root is the host's problem. Consider stronger isolation (gVisor, Kata, dedicated nodes) for untrusted workloads.
  • Enforce kernel.yama.ptrace_scope=2 and seccomp profiles to slow post-exploitation pivoting; they do not stop Bad Epoll but they raise the cost of what comes after.

4. Hunt for compromise

Bad Epoll is post-access privilege escalation — MITRE ATT&CK T1068: Exploitation for Privilege Escalation. Signals to hunt:

  • A process that was not setuid transitioning to euid 0 with no corresponding sudo/su/PAM event — the tell of an in-memory escalation. Correlate auditd execve/setresuid records with authentication logs.
  • Unusual, repeated open/read of /proc/self/fdinfo from a non-root process, alongside dense epoll_create1/epoll_ctl bursts — the exploit's fingerprint. eBPF (e.g. a bpftrace probe on sys_enter_epoll_ctl per PID) catches the abnormal fan-out.
  • Kernel logs: sporadic KASAN or general protection fault entries in epoll paths on hardened builds, or unexplained kernel oopses.
  • Post-escalation ATT&CK follow-through: new root cron/systemd units (T1053), account creation (T1136), or credential access (T1003) shortly after an unexplained privilege change.

5. Eradicate and verify

If you find evidence of exploitation: rebuild the host — an attacker who reached root can hide in kernel state you cannot reliably audit from userland. Rotate every credential, key, and token that touched the box (SSH keys, service-account tokens, cloud instance credentials, cached secrets). Patch the kernel, reboot, and only then confirm clean — verifying before the vulnerable kernel is gone verifies nothing.

Where this leaves detection — and Zero Hunt

Bad Epoll is a compact argument against a comfortable assumption: that reading code — even with a capable AI reading it — tells you whether it is exploitable. A six-instruction race that a frontier model audited and walked past is proof that some classes of bug only reveal themselves when you run the exploit against the real kernel and count the wins. Detection here is empirical.

That is the design premise of the Zero Hunt 10-agent generative pentest swarm. Its Post-Exploit and Pivot agents do not grep for CVE signatures; they write a fresh escalation attempt against the target environment, execute it inside an ephemeral, gVisor-hardened Docker sandbox — so the race runs against the actual kernel, never the appliance host — and record whether root actually fell out. Every candidate skill is backtested in the AI Gym (142+ self-evolving skills, validated against Vulhub, NYU CTF Bench, and Vulhub-Bench's 314 CVE-derived black-box tasks) before it ever touches a live engagement, so a probabilistic exploit like Bad Epoll is characterized by its real success rate, not a static "looks patched" banner read off a version string. When the swarm confirms an escalation path, the finding is ECDSA-signed at write time and mapped to ATT&CK T1068 — a defensible record that this host was root-reachable on this date, not an analyst's guess. Static review, human or machine, told you the code looked fine for 70 days. Running it told you the truth in one afternoon.

Continuously validating whether a low-privilege foothold can reach root — before an attacker does it for you — is the difference between knowing your patch cadence and knowing your exposure. See how the generative swarm runs it on the platform, or get in touch.