← Back to Feed CACHED · 2026-10-02 20:10:03 · CACHE_KEY CVE-2026-46242
CVE-2026-46242 · CWE-416 · Disclosed 2026-05-30

Linux Kernel epoll Use-After-Free Local Privilege Escalation (Bad Epoll)

ASSESSED — NOISGATE
Vendor
—
—
—
Reassessed
—
—
—
Verdict: —
Do you agree?
01 · The Real Story

A six-instruction crack in the wall that lets any tenant grab the landlord's master key

CVE-2026-46242 — dubbed Bad Epoll — is a use-after-free race condition in the Linux kernel's epoll subsystem, specifically in ep_remove(). When two chained epoll file descriptors are closed concurrently, ep_remove_file() clears file->f_ep under file->f_lock but continues using the same struct file inside the critical section. A concurrent __fput() observes the transient NULL, runs ep_eventpoll_release() → ep_free(), and kfree()s the watched struct eventpoll. The subsequent hlist_del_rcu() then writes into freed kmalloc-192 slab memory. Affected kernels span 6.4 through 6.18.32, 6.19 through 7.0.9, and certain stable-backport branches (5.15.209–5.16, 6.1.175–6.2). The fix — pinning @file via epi_fget() at the top of ep_remove() — landed upstream on April 24, 2026 (commit a6dc643c6931), five weeks before public disclosure on May 30.

The vendor's HIGH / 7.8 score accurately captures the base-score math — local access, low privileges, full C/I/A — but dramatically undersells the real-world risk. Researcher Jaeyoung Chung (Seoul National University) published a weaponized exploit through Google's kernelCTF program that achieves 99% reliability on LTS 6.12 and 98% on Container-Optimized OS, despite a race window only six instructions wide. The exploit chains four linked epoll FDs, leaks kernel addresses through /proc/self/fdinfo, and pivots into a ROP chain that drops a root shell. Critically, it works from inside Chrome's renderer sandbox and on Android 6.4+ devices. There is no workaround — epoll is compiled into the kernel and cannot be disabled. Every Linux 6.4+ host running without the patch is one ./bad_epoll invocation from root.

"99% reliable root exploit on any Linux 6.4+ host, no workaround, patch or burn."
02 · The Attack Path

5 steps from start to impact.

STEP 01

Obtain unprivileged local code execution

The attacker needs any unprivileged shell, container process, CI/CD build job, or even a compromised Chrome renderer tab on the target host. No special capabilities or group memberships are required — only the ability to call epoll_create, epoll_ctl, and close, which are available to all userspace processes including sandboxed ones.
Conditions required:
  • Local or sandboxed code execution on a Linux 6.4+ host
Where this breaks in practice:
  • Requires initial foothold — SSH, container tenant, web shell, or browser RCE chain
STEP 02

Chain four epoll file descriptors

The exploit creates four epoll instances where one watches another, forming a chain. It then triggers concurrent close() calls on the linked FDs to race ep_remove() against __fput(). The six-instruction race window is widened via scheduling manipulation and a retry loop that never crashes the kernel, yielding ~99% hit rate.
Conditions required:
  • Ability to call epoll_create/epoll_ctl/close (unprivileged)
  • Kernel 6.4+ with vulnerable ep_remove() code path
Where this breaks in practice:
  • None — epoll cannot be disabled or restricted; seccomp profiles rarely block epoll syscalls
Detection/coverage: Runtime detection is extremely difficult; no known EDR signature reliably catches epoll-fd chaining patterns prior to exploitation
STEP 03

Leak kernel addresses via /proc/self/fdinfo

Once the race is won, the freed struct eventpoll slab is reclaimed with attacker-controlled data. The exploit reads back kernel pointers through /proc/self/fdinfo on the surviving epoll FD, defeating KASLR without any separate info-leak primitive. This gives the attacker the kernel text base and slab addresses needed for the ROP chain.
Conditions required:
  • Successful UAF trigger from step 2
  • Access to /proc/self/fdinfo (available to all processes)
Where this breaks in practice:
  • Kernel hardening (e.g., kptr_restrict=2) limits some /proc leaks, but the exploit reads through the epoll fdinfo handler which exposes internal struct fields regardless
Detection/coverage: Anomalous /proc/self/fdinfo read patterns are not monitored by standard EDR agents
STEP 04

Hijack control flow via ROP chain

With KASLR bypassed, the exploit overwrites a function pointer in the freed-and-reallocated slab object to redirect an indirect call. The ROP chain calls commit_creds(prepare_kernel_cred(0)) or equivalent, granting the attacker's task uid=0. The process then spawns a root shell.
Conditions required:
  • Kernel base address from step 3
  • Controlled data in freed slab from step 2
Where this breaks in practice:
  • SMEP/SMAP are not bypassed by ret2user — the ROP chain stays in kernel text, so these mitigations do not block the exploit
Detection/coverage: Some kernel-level integrity monitors (e.g., LKRG) may detect credential structure modification post-exploitation, but this is not standard
STEP 05

Pivot from root to lateral movement

With root on the host, the attacker can read secrets, dump credentials, escape containers (if on a Kubernetes node), access the host network, and pivot laterally. On a container host, this means access to all tenant workloads, kubelet credentials, and potentially the cluster API server service account token.
Conditions required:
  • Root shell from step 4
Where this breaks in practice:
  • Post-exploitation detection by EDR/HIDS is the last line of defense
Detection/coverage: EDR agents (CrowdStrike Falcon, SentinelOne, Microsoft Defender for Endpoint) should detect anomalous root process spawning and credential access patterns post-exploitation
03 · Compensating Control

1
CRITICAL 9.0→IGNORE 0.0
SEVERITY REDUCED
Patch the kernel immediately — Apply upstream fixes (6.12.95, 6.18.33, 7.0.10) or distro-provided kernel updates. There is no alternative mitigation — this is the only complete remediation. Under the noisgate mitigation SLA for CRITICAL, deploy within 3 days. If live-patching (kpatch, Ksplice, KernelCare) is available for your distro, apply the livepatch to avoid reboot scheduling delays. Coordinate reboot waves for hosts that cannot live-patch.
2
CRITICAL 9.0→HIGH 8.0
SEVERITY REDUCED
Restrict unprivileged user namespaces — Set kernel.unprivileged_userns_clone=0 (Debian/Ubuntu) or user.max_user_namespaces=0 (RHEL). While this does not block the core epoll race, it limits some exploit stabilization techniques that rely on namespace isolation for heap shaping. This is a partial friction increase, not a fix. Deploy within 3 days alongside patch scheduling.
3
CRITICAL 9.0→MEDIUM 5.0
SEVERITY REDUCED
Harden seccomp profiles to block epoll_create1 — In containerized environments (Kubernetes, Docker), deploy a custom seccomp profile that denies epoll_create1 and epoll_create. Warning: This will break most real applications — nginx, Node.js, Go, Java, Python asyncio all require epoll. Only viable for extremely constrained single-purpose containers that use poll/select instead. Test thoroughly before deploying.
4
CRITICAL 9.0→HIGH 7.5
SEVERITY REDUCED
Deploy kernel-level integrity monitoring — Enable LKRG (Linux Kernel Runtime Guard) or equivalent kernel integrity monitor to detect credential structure tampering post-exploitation. This does not prevent exploitation but provides a detection layer. Deploy within 3 days as a compensating detection control.
What doesn't work
  • Network segmentation / firewalls: This is a local privilege escalation — network controls are irrelevant to the attack path. The attacker is already on the host.
  • SELinux / AppArmor in default profiles: Default MAC policies do not restrict epoll syscalls or /proc/self/fdinfo access. A targeted custom policy could theoretically block fdinfo reads, but this would break application monitoring and health checks.
  • KASLR alone: The exploit defeats KASLR by reading kernel pointers through the epoll fdinfo handler on the corrupted FD. KASLR provides zero additional friction.
  • Container isolation (namespaces/cgroups): The exploit escapes container boundaries by escalating to root on the host kernel. Container isolation is the thing being bypassed, not a defense against this bug.
04 · Intelligence Metadata

The supporting signals.

In-the-wild statusNo confirmed in-the-wild exploitation as of 2026-10-03. Not listed in CISA KEV. No GreyNoise tags or scanning activity observed (local-only attack vector limits network-visible telemetry).
PoC availabilityWeaponized public exploit by Jaeyoung Chung (Seoul National University), submitted to Google kernelCTF ($71,337 bounty). Published July 14, 2026. Achieves 99% reliability on LTS 6.12.67, 98% on COS-121. GitHub repos: 0xBlackash/CVE-2026-46242, Baba01hacker666/CVE-2026-46242. Additionally listed in SecureWithUmer/CVE-2026-PoCs collection.
EPSS score0.00176 (user-reported). Other sources report 3.15% / 87th percentile — discrepancy may reflect model revision date. Either way, EPSS significantly underweights local kernel LPE with public weaponized exploit.
CVSS vectorCVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H — 7.8 HIGH. The Scope:Unchanged is debatable given container/sandbox escape capability, which would push to Scope:Changed and ~8.8.
Affected versionsLinux kernel 6.4 – 6.18.32, 6.19 – 7.0.9. Backport-affected: 5.15.209 – 5.16, 6.1.175 – 6.2. Bug introduced April 2023. Android kernels 6.4+ affected; Pixel 8 (6.1) not affected.
Fixed versionsUpstream: 6.18.33, 7.0.10. Stable backports: 6.12.95, 6.6.144, 6.1.178. Debian: trixie 6.12.111-1, forky/sid 7.2.8-1 (bullseye/bookworm not affected). Ubuntu: 26.04 7.0.0-28.28, 24.04 6.8.0-137.137, 22.04 various HWE kernels.
Sandbox/container escapeConfirmed exploitable from Chrome renderer sandbox (seccomp + namespaces). Container escape on Kubernetes nodes gives attacker root on the node OS, access to kubelet creds and all co-located pods.
Scanning/exposure dataNot applicable for network scanning (AV:L). However, Shodan/Censys Linux kernel fingerprinting shows tens of millions of hosts running affected 6.4+ kernels. Any multi-tenant Linux environment is a target.
Disclosure timelineReported privately: February 2026. Fix merged: April 24, 2026 (commit a6dc643c6931). CVE published: May 30, 2026. Public exploit released: July 14, 2026.
ResearcherJaeyoung Chung, Seoul National University Computer Security Lab. Submitted as 0-day to Google kernelCTF, awarded $71,337 bounty.

Sources.

  1. NVD — CVE-2026-46242 Detail
  2. SecurityOnline — Public Exploit and Details Released for Bad Epoll Root Flaw
  3. Zero Hunt — Bad Epoll: A 6-instruction race that hands any Linux user root
  4. GitHub Advisory Database — GHSA-h4r8-4j67-99mx
  5. Ubuntu Security — CVE-2026-46242
  6. Debian Security Tracker — CVE-2026-46242
  7. SecurityWeek — Proof-of-Concept Exploit Released for Linux Bad Epoll Root Access Vulnerability
  8. TuxCare — Bad Epoll CVE Analysis
05 · The Call

Final Verdict
↑ UPGRADED to CRITICAL (9.0/10)

Why this verdict

  • Public weaponized exploit with 99% reliability: The kernelCTF submission by Jaeyoung Chung is not a crash PoC — it is a fully weaponized root exploit with a retry loop that never panics the kernel. Weaponization cost for any attacker is zero; they download and compile. This alone elevates risk well above a theoretical 7.8.
  • No workaround exists: epoll is compiled into the kernel and used by virtually every userspace application (systemd, nginx, Node.js, containerd, Chrome). It cannot be disabled, module-blocked, or restricted via seccomp without breaking the host. The only fix is the patch.
  • Sandbox and container escape: The exploit is confirmed to work from inside Chrome's renderer sandbox and from unprivileged container processes. On a Kubernetes node, any compromised pod tenant can escalate to node root, dump kubelet credentials, and pivot across the cluster. This transforms a LOW-value container compromise into fleet-scale impact.
  • Role multiplier: The Linux kernel is the canonical component for every high-value role — KVM/QEMU hypervisor hosts (VM escape to hypervisor root), Kubernetes nodes (container escape to node root → cluster pivot), CI/CD runners (build job → root → supply-chain poisoning), production database servers (app compromise → root → data exfiltration), and backup infrastructure. ≥10% of Linux 6.4+ installs serve these roles by definition. The blast radius from a single compromised container host is fleet-scale (all co-located workloads, cluster credentials, lateral network access). This floors the verdict at CRITICAL per the high-value-role rule.
  • Universal attack surface: Every Linux 6.4+ host — server, desktop, Android device, cloud VM, container node — is vulnerable. The affected population numbers in the tens of millions. The prerequisite is merely 'be a local user,' which includes every container tenant, every CI job, every PHP/Node/Python application with an RCE, and every Chrome tab if chained with a renderer bug.

Why not higher?

A 9.0 already reflects the maximum practical severity for a local privilege escalation with no remote attack vector. A score above 9.0 would require unauthenticated remote exploitation (AV:N). While the exploit is devastating once local access is obtained, the initial foothold is still a prerequisite the attacker must solve independently. The absence of confirmed in-the-wild exploitation and KEV listing also argue against pushing to 9.5+.

Why not lower?

Downgrading below CRITICAL is not defensible. The Linux kernel is the canonical high-value-role component — it IS the hypervisor, the container runtime host, the CI runner OS. The chain succeeds in every one of these roles with fleet-scale blast radius. A 99% reliable public exploit with no kernel crash, no workaround, and sandbox escape capability means the exploitation barrier is effectively zero for any attacker with local access. The 'requires local access' friction does not break the CRITICAL floor because local access is the *default state* for container tenants, CI jobs, and multi-user servers — the very environments where this bug is most dangerous.

06 · Verification

Crowdsourced verification payload.

Run this script on each target Linux host as any user (no root needed). Invoke with bash check_bad_epoll.sh. It compares the running kernel version against known-fixed versions and reports VULNERABLE, PATCHED, or UNKNOWN.

noisgate-verify.sh
BASHREAD-ONLYSAFE
#!/bin/bash
# check_bad_epoll.sh — CVE-2026-46242 (Bad Epoll) version checker
# Run on target Linux host. No root required.
# Exit codes: 1=VULNERABLE, 0=PATCHED, 2=UNKNOWN

set -euo pipefail

KERNEL=$(uname -r)
echo "[*] Running kernel: $KERNEL"

# Extract major.minor.patch (strip distro suffixes)
VERSION=$(echo "$KERNEL" | grep -oP '^[0-9]+\.[0-9]+\.[0-9]+')
if [[ -z "$VERSION" ]]; then
  echo "UNKNOWN — could not parse kernel version from: $KERNEL"
  exit 2
fi

MAJOR=$(echo "$VERSION" | cut -d. -f1)
MINOR=$(echo "$VERSION" | cut -d. -f2)
PATCH=$(echo "$VERSION" | cut -d. -f3)

ver_gte() {
  # Returns 0 if $MAJOR.$MINOR.$PATCH >= $1.$2.$3
  local m1=$1 m2=$2 m3=$3
  if (( MAJOR > m1 )); then return 0; fi
  if (( MAJOR == m1 && MINOR > m2 )); then return 0; fi
  if (( MAJOR == m1 && MINOR == m2 && PATCH >= m3 )); then return 0; fi
  return 1
}

ver_lt() {
  ! ver_gte "$1" "$2" "$3"
}

# Kernels < 5.15.209: not affected (vulnerable code not present)
if ver_lt 5 15 209; then
  echo "PATCHED — kernel $VERSION predates vulnerable code (introduced ~5.15.209/6.4)"
  exit 0
fi

# 5.15.209 – 5.15.x: check if >= 5.15.x fixed (not affected per Debian, code not present in 5.10/6.1 base)
# 6.1.175 – 6.1.x: fixed in 6.1.178
if (( MAJOR == 6 && MINOR == 1 )); then
  if (( PATCH >= 175 && PATCH < 178 )); then
    echo "VULNERABLE — kernel $VERSION is in affected 6.1.175-6.1.177 range (fixed in 6.1.178)"
    exit 1
  elif (( PATCH >= 178 )); then
    echo "PATCHED — kernel $VERSION includes fix (6.1.178+)"
    exit 0
  else
    echo "PATCHED — kernel $VERSION predates vulnerable backport in 6.1 branch"
    exit 0
  fi
fi

# 6.2 – 6.3.x: affected, no stable fix (upgrade to 6.6.144+)
if (( MAJOR == 6 && (MINOR == 2 || MINOR == 3) )); then
  echo "VULNERABLE — kernel $VERSION is in affected 6.2-6.3 range (upgrade to 6.6.144+)"
  exit 1
fi

# 6.4 – 6.6.x: fixed in 6.6.144
if (( MAJOR == 6 && MINOR >= 4 && MINOR <= 6 )); then
  if (( MINOR == 6 && PATCH >= 144 )); then
    echo "PATCHED — kernel $VERSION includes fix (6.6.144+)"
    exit 0
  else
    echo "VULNERABLE — kernel $VERSION is in affected range (fixed in 6.6.144)"
    exit 1
  fi
fi

# 6.7 – 6.12.x: fixed in 6.12.95
if (( MAJOR == 6 && MINOR >= 7 && MINOR <= 12 )); then
  if (( MINOR == 12 && PATCH >= 95 )); then
    echo "PATCHED — kernel $VERSION includes fix (6.12.95+)"
    exit 0
  else
    echo "VULNERABLE — kernel $VERSION is in affected range (fixed in 6.12.95)"
    exit 1
  fi
fi

# 6.13 – 6.18.x: fixed in 6.18.33
if (( MAJOR == 6 && MINOR >= 13 )); then
  if (( MINOR == 18 && PATCH >= 33 )); then
    echo "PATCHED — kernel $VERSION includes fix (6.18.33+)"
    exit 0
  elif (( MINOR > 18 )); then
    echo "PATCHED — kernel $VERSION is past affected range"
    exit 0
  else
    echo "VULNERABLE — kernel $VERSION is in affected range (fixed in 6.18.33)"
    exit 1
  fi
fi

# 7.0.x: fixed in 7.0.10
if (( MAJOR == 7 && MINOR == 0 )); then
  if (( PATCH >= 10 )); then
    echo "PATCHED — kernel $VERSION includes fix (7.0.10+)"
    exit 0
  else
    echo "VULNERABLE — kernel $VERSION is in affected 7.0.0-7.0.9 range"
    exit 1
  fi
fi

# 7.1+: fixed
if ver_gte 7 1 0; then
  echo "PATCHED — kernel $VERSION is past all affected ranges"
  exit 0
fi

# 5.15.209 – 5.15.x / 5.16.x edge cases
if (( MAJOR == 5 && MINOR == 15 && PATCH >= 209 )); then
  echo "VULNERABLE — kernel $VERSION may contain backported vulnerable code (verify with distro advisory)"
  exit 1
fi

if (( MAJOR == 5 && MINOR == 16 )); then
  echo "VULNERABLE — kernel $VERSION is in affected 5.16 range"
  exit 1
fi

if (( MAJOR == 6 && MINOR == 0 )); then
  echo "PATCHED — kernel $VERSION predates vulnerable code in 6.x branch"
  exit 0
fi

echo "UNKNOWN — kernel $VERSION not in known affected/fixed ranges; verify manually"
exit 2