The Short Version
What: Bad Epoll, tracked as CVE-2026-46242, is a race-condition use-after-free in the Linux kernel’s epoll subsystem. In the ep_remove() cleanup path, one close path clears the file’s epoll link and keeps using it while a concurrent file release, seeing that cleared link, frees the backing object underneath it. An unprivileged user rides that stray write to root.
Impact: An unprivileged local user wins the race, turns the 8-byte use-after-free write into control of a struct file via a cross-cache attack, then escalates to arbitrary kernel read and control-flow hijack – full root. The public PoC (J-jaeyoung/bad-epoll, submitted to Google kernelCTF) reports 99% reliability on a 6.12 kernel, the same base as AlmaLinux 10.
Status: The bug is genuine and already fixed upstream. It was reported privately in February, corrected on April 24, and disclosed publicly on May 30 – the fix predates disclosure by five weeks, with no in-the-wild use. It began as a kernelCTF 0-day, but for defenders today it is an N-day with a public exploit.
Severity: kernel.org and NVD score it CVSS 7.8 High (AV:L/AC:L/PR:L); Red Hat rates it 7.0, treating the ~6-instruction race as hard to win. Either way the result is root, and the PoC’s ~99% reliability makes that narrow window a practical problem, not a theoretical one.
Why this matters: The exposed systems are ones where local isolation is the product: shared hosting, multi-tenant hosts, CI runners, and container nodes running untrusted code. epoll underpins nearly every service, so there is no surface to disable. A local LPE is the boundary-crossing event here.
Do this now: There is no clean config mitigation – epoll cannot be turned off. Isolate untrusted local execution, and confirm the kernel actually carries the fix rather than trusting a failed exploit run. A KernelCare rebootless patch is under analysis; nothing is on the feeds yet. Details below.
Tired of scheduling another emergency reboot?
KernelCare applies Linux kernel patches without downtime, disruption, or emergency maintenance windows.
KernelCare status (updated 2026-07-04 — under analysis): This is one shared kernel lineage: RHEL, AlmaLinux, Rocky, Oracle Linux, and CloudLinux of the same major carry the same ep_remove() code and exposure — read “EL10” and “EL9” as the whole family. Red Hat marks RHEL 9 and 10 Affected (7/8 Not affected). EL10 is affected; EL9 very likely inherits the backported introducing commit. We’re still verifying reproduction and the exact affected builds. No KernelCare patch has completed validation, and none is on any feed yet (tracked as KCARE-27500). There is no customer-mitigable config today — see the mitigation section.
The Long Version
Two things will tempt you to close this ticket early, and both are wrong.
The first is the PoC’s own README, which says the bug only affects kernels “based on v6.4 or newer” and calls v6.1-based kernels safe. Read that on a CloudLinux 9 box – 5.14-based – and you would reasonably file yourself out of scope. You would be wrong. The second is a clean exploit run. Point the public PoC at a kernel on a small VM, watch it lose the race a few hundred thousand times, and it is easy to write “not reproducible, not affected” and move on. Also wrong, and for this bug, dangerously so.
Bad Epoll is a race-condition use-after-free in the kernel’s eventpoll subsystem – the machinery behind the epoll syscalls that essentially every network service on the box depends on. The race lives in the ep_remove() cleanup path, and the window is roughly six CPU instructions wide. Miss it and nothing happens. Win it, as the public exploit does with high reliability on a 6.12 kernel, and an unprivileged user is on the road to root.
EL10 is confirmed exploitable and EL9 is very likely affected through the backported introducing commit, so this is not a cautious read of a vendor advisory. It is a real bug, with a real upstream fix, and a weaponized public exploit already sitting in a kernelCTF submission.
How Bad Epoll Works
epoll keeps a struct eventpoll object, backed by a struct file, for each epoll instance. Tearing that down cleanly matters, because two paths can end up closing the same thing at nearly the same instant: the epoll removal path and a concurrent release of the backing file.
The bug is a small ordering mistake in that teardown. Inside ep_remove(), the old code cleared the file’s epoll association (file->f_ep) while holding file->f_lock, then kept using that same file and its eventpoll data inside the critical section. A concurrent __fput() hitting the release fast-path in that window sees the transient NULL f_ep, concludes it has no epoll cleanup to do, and runs straight on to f_op->release. In an epoll-watches-epoll arrangement that path frees the watched struct eventpoll outright. The first path is still running, and its next hlist_del_rcu() writes through a pointer embedded in memory that was just freed.
[Unprivileged local process]
│ creates linked epoll objects backed by struct file
▼
[Two close paths race inside ep_remove()]
│ path A clears file->f_ep, keeps using the file
│ path B (__fput) sees NULL f_ep, skips epoll cleanup
▼
[Path B frees the watched struct eventpoll / recycles the file slot]
│ path A's hlist_del_rcu() writes into freed memory
▼
[Attacker-controlled 8-byte use-after-free write]
▼
[Cross-cache reclaim gives control of a struct file]
▼
[Arbitrary kernel read + control-flow hijack]
▼
[uid=0 root shell]
The write itself is eight bytes into freed memory, which sounds survivable until you follow what the exploit does with it. The public chain uses four epoll objects in two pairs: closing one pair drives the race, the other becomes the victim. It turns the 8-byte write into a use-after-free on a struct file, uses cross-cache heap reclamation to replace that slot with an attacker-controlled object, and then achieves arbitrary kernel read and control-flow hijacking.
The narrowness of the race is exactly why the bug is easy to misjudge – and why it went unnoticed for years. The same 2023 commit that introduced it also introduced a sibling race, CVE-2026-43074, that was found and fixed earlier in 2026. That fix did not close Bad Epoll. Taking one epoll fix this year is not evidence you have both.
What the Upstream Fix Changes
The fix is small and self-contained, which is why it ports cleanly. It stops ep_remove() from working with an unprotected reference to the file it is removing.
The corrected path pins the file up front, taking a reference through epi_fget() and holding a struct file *file __free(fput) guard before it touches file->f_lock. With that reference held, the file’s refcount cannot fall to zero, so __fput() cannot start and the concurrent release path cannot free the file or its eventpoll object out from under the cleanup. If the pin cannot be taken, ep_remove() bails before clearing f_ep, and the release path follows the normal, slower cleanup that synchronizes through the eventpoll mutex instead of racing.
Conceptually:
Before: clear epoll link → keep using file → concurrent release frees it → write into freed memory
After: pin file reference → do cleanup while pinned → concurrent release cannot free → release reference
That single change closes both halves of the bug: the freeing of the watched struct eventpoll, and the recycling of the struct file slot while the original path still believes it holds a valid lock.
Who Is Affected
The short answer for the CloudLinux and AlmaLinux family: 9 and 10 are affected and confirmed, 8 is expected to be clear.
Affected:
- CloudLinux 10 / AlmaLinux 10 (6.12-based): A crash-only variant of the public PoC reliably panics an unprivileged AlmaLinux 10 host on bare metal within seconds – a reachable, weaponized use-after-free, not a lab curiosity. This is the same 6.12 base the full exploit roots with 99% reliability upstream.
- CloudLinux 9 / AlmaLinux 9 (5.14-based): Likely affected via the backported introducing commit – Red Hat lists RHEL 9 as Affected because the introducing commit was backported into that line. Reproduction on the 5.14 line is still being verified, so treat it as affected pending confirmation rather than proven in-house.
Expected not affected, confirm before you rely on it:
- CloudLinux 8 / AlmaLinux 8 (4.18-based): Red Hat marks RHEL 7 and 8 as not affected, and CL8 is expected clear by inheritance. If an auditor or customer needs a hard yes, verify the source rather than asserting it.
The version window is the first trap. The PoC README describes a mainline range of v6.4 and newer, and calls v6.1-based kernels safe. That is true for mainline and useless for Enterprise Linux, because vendors backport the introducing change into much older lines. A 5.14-based EL9 kernel sits well below v6.4 and is still affected. Do not reason from the upstream version to your EL box.
A failed reproduction is the second trap. Race exploits are exquisitely sensitive to core count, scheduling, heap layout, and build config. An early test on a small two-vCPU VM won zero races and looked clean; a stronger reproducer on multi-core bare metal panicked the same kernel line in seconds. The negative was a bad test environment, not a patched kernel. Do not read a missed race as safety.
So verify against the source, not the exploit. Grepping for the upstream commit hash is misleading, because backports strip the hash out. Check for the fix pattern inside ep_remove() instead:
grep -n 'epi_fget\|__free(fput)' /usr/src/kernels/$(uname -r)/fs/eventpoll.c
If ep_remove() pins the file with epi_fget() and an __free(fput) guard before touching file->f_lock, the fix is present. If those are absent, the kernel is unpatched, and any negative exploit run on it was a false negative.
Mitigate Now
Here it is straight: there is no clean configuration mitigation.
epoll is core syscall surface. nginx, php-fpm, mysqld, systemd – nearly everything on a production host sits on it, and it is enabled on every general-purpose kernel. There is no sysctl to flip, no module to blacklist, and no capability to strip that closes the vulnerable path without breaking the workloads that pay for the server. It does not even need user namespaces or special capabilities; the researcher notes it can be reached from constrained contexts, including a browser renderer sandbox. Anyone offering a one-line epoll hardening knob for this is selling you a broken box.
Because the bug is local-only (AV:L, PR:L), the durable controls are the boring ones you already know:
- Reduce who can run code on affected hosts, as this bug needs a local unprivileged foothold; fewer footholds, less exposure.
- Isolate untrusted and semi-trusted local execution – customer code, CI jobs, build steps – off the hosts you most need to protect, or onto kernels you can fix first.
- Prioritize the hosts where local isolation is the actual security boundary: shared hosting, multi-tenant nodes, CI runners, container hosts.
- Confirm the kernel carries the fix with the source check above, rather than inferring safety from a quiet exploit run.
Decision moment. If a host runs no untrusted local code and has a small, trusted set of users, the residual risk is lower and you can patch on your normal cadence once errata or a rebootless patch lands. If a host runs code you do not control – shared hosting, CI, multi-tenant – treat it as exposed today and move a rebootless patch or a fixed kernel to the front of the queue the moment one is available.
Rebootless Patching
This is the kind of bug rebootless patching exists for. The upstream change is available, but that does not protect a running system: the vendor has to backport it, you have to install the package, and the machine has to boot into the new kernel. That last step is where exposure lingers, and it is hardest exactly where it matters most.
The fix is a small, self-contained cleanup correction, which makes it a good rebootless patch candidate. It ports as a straight backport of the upstream ep_remove() reference pin, applied into the running kernel so in-flight cleanup can no longer free the file underneath it – the same guarantee as the upstream fix, delivered without a reboot.
For CloudLinux and AlmaLinux, the fix is identified – it ports as a straight backport of the upstream ep_remove() reference pin (RHEL 10 backport, gerrit 257974). The KernelCare backport is still under analysis, with EL10 and EL9 reproduction being verified, so no patch has completed validation and none is on the testing, main, or ePortal feeds yet. LibCare is out of scope here – this is a kernel-only bug, so there is no userspace-library angle to patch.
Once a release becomes available, deploying it from the testing channel (feed) looks like this:
kcarectl --update --prefix test
To confirm that the running patch build is dated on or after the release:
kcarectl --info | grep kpatch-build-time
A build date alone proves nothing. Verify the CVE mapping, the vendor erratum, the source pattern, or the KernelCare patch status directly.
Detection
Bad Epoll leaves no tidy on-disk signature. The exploit works entirely in kernel memory – winning a race, corrupting a freed object, rewriting control flow – so file-integrity tooling has nothing to catch. The trigger is built from ordinary interfaces (creating epoll instances, registering descriptors, timerfds, closing them) that are normal on any Linux host. That pushes detection into behavioral territory, and each of the following is a lead, not proof:
- Unprivileged processes creating and tearing down large numbers of epoll instances and timerfds in tight loops, consistent with hammering a narrow race.
- Kernel oops or list-corruption BUGs in the eventpoll teardown path, with signatures around
ep_show_fdinfo,ep_clear_and_put,ep_eventpoll_release, or__fput. - Repeated kernel panics and reboots on a multi-core host with no hardware or config change to explain them – a crash-only trigger panics the box in seconds.
- A local user or service account gaining privileges shortly after a burst of the above, with no matching sudo or login event.
If untrusted or semi-trusted local code ran on an exposed EL9 or EL10 host and you see kernel instability clustered in the epoll teardown path, prioritize patching and treat the host as warranting a closer look – new accounts or SSH keys, altered auth config, unexpected services – rather than reading clean file hashes as the absence of compromise.
Why This One Matters
Bad Epoll is not remote code execution. Nobody roots your server from the internet with this. For the environments it actually lands in, that is thin comfort.
Shared hosting runs customer code. CI runners run whatever the last pull request told them to. Container and Kubernetes nodes run workloads at varying trust levels, and multi-tenant hosts exist specifically to keep tenants apart. In every one of those, a local privilege escalation is the event the whole design is meant to prevent. It converts code that was allowed to run into code running as root where it never should have. That the bug is also reachable from places most kernel bugs are not, like a browser renderer sandbox, only widens the surface.
The reason it is easy to get wrong is the same reason it is dangerous. Three plausible signals all point toward “safe” and all mislead: the “v6.4+” version line ignores backports into 5.14-based EL9, a failed exploit run ignores how hard a six-instruction race is to hit without effort, and having taken the sibling epoll fix earlier this year does nothing for this one. Trust any of them and you will file a vulnerable host as clear.
The risk concentrates where it always does: the boxes a few updates behind, the long-lived CI runners nobody reboots, the multi-tenant hosts where a maintenance window is a negotiation. Those are precisely the hosts where an N-day with a public PoC quietly becomes a same-day problem – and the ones where a rebootless patch is the difference between fixing it today and fixing it next quarter.
Prioritized Remediation Checklist
- Treat CloudLinux 9 and CloudLinux 10 as affected and confirmed. Do not exempt CL9 on the strength of the “v6.4+” note – the introducing commit was backported into the 5.14 line.
- Confirm each kernel by source, not by exploit outcome: check ep_remove() for the
epi_fget()+__free(fput)fix pattern, and treat a failed PoC run as inconclusive. - Do not assume “we patched an epoll bug this year” covers this one; the sibling CVE-2026-43074 fix does not close Bad Epoll.
- Prioritize hosts where local isolation is the security boundary: shared hosting, multi-tenant nodes, CI runners, container and dev hosts running untrusted code.
- Reduce and isolate untrusted local execution until a fix is in place. There is no config mitigation to lean on – epoll cannot be disabled.
- Verify CloudLinux 8 at the source before telling anyone it is clear.
- Hunt for unexplained kernel panics and unusual epoll/timerfd activity in the eventpoll teardown path, and treat clean file hashes as insufficient evidence.
- Apply the KernelCare rebootless patch as soon as one is validated and promotes to the feed, especially on the hosts hardest to reboot. The patch is still under analysis; K-tags will be posted here when releases land.
Patch every kernel, no reboot required. If you are not yet a KernelCare user, now is a good time to see how rebootless patching protects critical Linux systems without emergency maintenance windows or downtime.
References
- Red Hat CVE-2026-46242 – https://access.redhat.com/security/cve/cve-2026-46242
- NVD CVE-2026-46242 (kernel.org CNA, CVSS 3.1 7.8) – https://nvd.nist.gov/vuln/detail/CVE-2026-46242
- linux-cve-announce – https://lore.kernel.org/linux-cve-announce/
- Public PoC and root-cause writeup (Jaeyoung Chung, Google kernelCTF) – https://github.com/J-jaeyoung/bad-epoll
- Upstream fix commit a6dc643c6931 (2026-04-24); bug-introducing commit 58c9b016e128 (2023-04-08, mainline v6.4)
- Related sibling bug CVE-2026-43074 (same introducing commit, fixed separately)