← Back to Feed CACHED · 2026-09-26 13:44:49 · CACHE_KEY CVE-2026-31413
CVE-2026-31413 · CWE-125 · Disclosed 2026-04-12

Linux Kernel BPF Verifier Unsound Scalar Forking in maybe_fork_scalars() for BPF_OR Leading to OOB Map Access

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

The kernel's BPF referee calls a foul that never happened, and the attacker walks off the field with the trophy

CVE-2026-31413 is a soundness bug in the Linux kernel's BPF verifier — specifically in maybe_fork_scalars() — that ships a wrong instruction pointer to the forked execution path when processing BPF_OR with a constant operand. The verifier believes a register holds 0 while the CPU actually computed K (the constant), creating a divergence that allows a crafted BPF program to perform out-of-bounds reads and writes against BPF map memory. From there, an attacker can hijack the map's vtable, overwrite modprobe_path, and escalate to full kernel code execution. Affected kernels: 6.12.75–6.12.79, 6.18.16–6.18.20, 6.19.6–6.19.10, and 7.0-rc1 through rc4. The vulnerable code existed for roughly two months (introduced January 14, 2026; fixed in mainline March 22, 2026).

The vendor's HIGH / 7.8 rating is accurate and does not need adjustment. The CVSS vector (AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H) correctly captures the local-only, low-complexity, low-privilege nature of the attack and its full CIA impact. Where CVSS slightly understates reality is the container-escape angle: in Kubernetes environments, infrastructure DaemonSet pods (Cilium, Falco, Datadog Agent, Tetragon) routinely hold CAP_BPF + CAP_PERFMON or CAP_SYS_ADMIN, meaning a compromised observability or CNI pod can chain this bug to escape to the host — a scenario the PoC by Rat5ak explicitly demonstrates. However, the capability gate prevents unprivileged exploitation, and the narrow affected-version window limits the exposed population, keeping the overall assessment at HIGH rather than tipping to CRITICAL.

"BPF verifier lies about a register; infrastructure pods can ride that lie to host root."
02 · The Attack Path

5 steps from start to impact.

STEP 01

Attain local execution with BPF capabilities

The attacker needs code execution in a context that holds CAP_BPF + CAP_PERFMON (or the superset CAP_SYS_ADMIN). In bare-metal or VM environments this means a compromised service account or an already-privileged shell. In Kubernetes, the canonical entry point is a compromised infrastructure DaemonSet pod — Cilium, Falco, Datadog Agent, or Tetragon — all of which run with BPF capabilities by design.
Conditions required:
  • Local code execution on a host running a vulnerable kernel
  • Process holds CAP_BPF + CAP_PERFMON or CAP_SYS_ADMIN
Where this breaks in practice:
  • Unprivileged users and standard application containers do NOT have CAP_BPF — kernel.unprivileged_bpf_disabled defaults to 2 since kernel 5.16
  • Attacker must first compromise an infra pod or privileged service to reach this step
Detection/coverage: Runtime security tools (Falco, Tetragon) can detect anomalous bpf() syscalls from unexpected processes. Audit rules on bpf() syscall (auditd -S bpf) provide host-level visibility.
STEP 02

Load crafted BPF program triggering verifier divergence

The attacker loads a BPF program containing the five-instruction trigger pattern: ARSH (arithmetic right shift) followed by BPF_OR with a controlled constant, plus conditional branching. The verifier's maybe_fork_scalars() pushes the forked path with insn_idx + 1, skipping the OR instruction. The verifier now tracks the register as 0 while the CPU holds K. This passes verification despite being unsound.
Conditions required:
  • Vulnerable kernel version (6.12.75–6.12.79, 6.18.16–6.18.20, 6.19.6–6.19.10, 7.0-rc1–rc4)
  • Ability to call bpf(BPF_PROG_LOAD)
Where this breaks in practice:
  • The vulnerable code was present for only ~2 months in mainline before the fix landed
  • Kernel live-patch vendors may have backported the fix before distro kernel releases
Detection/coverage: BPF program load events are logged when BPF audit is enabled (CONFIG_BPF_AUDIT). Some EDR agents flag unusual BPF program types.
STEP 03

Achieve out-of-bounds map read/write

Using the verifier-divergent register as a map index, the attacker reads and writes beyond the allocated BPF map boundaries. Two initial OOB reads leak the kernel address of the map structure and the bpf_map_ops vtable pointer, defeating KASLR. This provides the attacker with a stable arbitrary-read primitive.
Conditions required:
  • Successful verifier divergence from Step 2
Where this breaks in practice:
  • SMAP/SMEP are standard on modern x86_64 and prevent trivial user-space pointer dereference; the PoC works entirely within kernel BPF map memory, so SMAP/SMEP do not help here
Detection/coverage: Kernel address-space layout randomization (KASLR) bypass may trigger alerts in hardened kernels with CONFIG_HARDENED_USERCOPY, but standard BPF map operations bypass this.
STEP 04

Hijack BPF map vtable for arbitrary kernel write

The attacker constructs a fake bpf_map_ops structure within a second BPF map, replacing slot 15 (map_push_elem) with array_map_get_next_key — a function that writes a 4-byte integer to an attacker-controlled address via its write-back parameter. The attacker then corrupts the original map's header to point at the fake vtable and changes the map type to BPF_MAP_TYPE_QUEUE, causing subsequent bpf(BPF_MAP_UPDATE_ELEM) calls to dispatch through the hijacked function pointer.
Conditions required:
  • OOB write primitive from Step 3
  • Knowledge of kernel addresses from KASLR bypass
Where this breaks in practice:
  • CFI (Control Flow Integrity) / kCFI can block vtable hijacking on kernels compiled with CONFIG_CFI_CLANG; adoption is growing but not yet default on most distro kernels
Detection/coverage: kCFI violations generate kernel panics or WARN splats that are detectable. Without kCFI, this step is silent.
STEP 05

Overwrite modprobe_path for container escape / root shell

The attacker uses the arbitrary 4-byte write primitive to overwrite the kernel's modprobe_path variable, pointing it to an attacker-controlled script. Triggering an unknown binary format (e.g., executing a file with an unrecognized magic number) causes the kernel to invoke the attacker's script as root on the host, escaping the container namespace entirely. The PoC by Rat5ak demonstrates this achieving full host root from within a Kubernetes pod.
Conditions required:
  • Arbitrary kernel write from Step 4
  • Knowledge of modprobe_path address (from KASLR bypass)
Where this breaks in practice:
  • SELinux in enforcing mode or AppArmor with a tight BPF profile can block the modprobe execution
  • Some hardened kernels make modprobe_path read-only (CONFIG_STATIC_USERMODEHELPER)
Detection/coverage: Host-level file integrity monitoring (FIM) on /proc/sys/kernel/modprobe and auditd rules on execve from kernel context can detect the overwrite and subsequent execution.
03 · Compensating Control

1
HIGH 7.8→LOW 3.3
SEVERITY REDUCED
Revoke unnecessary BPF capabilities from pods and services — Audit all pods and host services for CAP_BPF, CAP_PERFMON, and CAP_SYS_ADMIN. Where possible, use Kubernetes SecurityContext to drop these capabilities. For Cilium and Falco, check whether newer versions support reduced capability sets. This directly breaks Step 1 of the attack path. Deploy within 30 days per the noisgate mitigation SLA for HIGH severity.
2
HIGH 7.8→MEDIUM 5.5
SEVERITY REDUCED
Enable kCFI (Clang Control Flow Integrity) on kernel builds — Compile kernels with CONFIG_CFI_CLANG=y to block vtable hijacking at Step 4 of the attack path. This does not prevent the verifier divergence or OOB access but stops the attacker from redirecting function pointers. Requires a kernel rebuild, so this is a medium-term hardening action. Deploy within 30 days.
3
HIGH 7.8→HIGH 7.0
Set kernel.unprivileged_bpf_disabled=1 and enforce via sysctl hardening — Ensure /proc/sys/kernel/unprivileged_bpf_disabled is set to 1 (permanently disabled, not the reversible value 2). While this is the default on modern kernels, explicitly setting and monitoring it via configuration management (Ansible, Puppet, Chef) prevents accidental re-enablement. This hardens Step 1 against any process that might acquire BPF capabilities through misconfiguration. Deploy within 30 days.
4
HIGH 7.8→MEDIUM 5.0
SEVERITY REDUCED
Enable SELinux enforcing or AppArmor BPF confinement — SELinux in enforcing mode with a policy that restricts bpf() syscall access and modprobe_path modification blocks both the BPF program load (Step 2) and the modprobe escape (Step 5). AppArmor profiles can similarly restrict BPF operations. This provides defense-in-depth even if capabilities are present. Deploy within 30 days.
5
HIGH 7.8→IGNORE 0.0
SEVERITY REDUCED
Apply the kernel patch — Update to 6.12.80, 6.18.21, 6.19.11, or 7.0-rc5+. For Ubuntu 24.04, update to linux 6.8.0-136.136 or the equivalent cloud kernel. This is the definitive fix. Per the noisgate remediation SLA for HIGH severity, complete patching within 180 days — but prioritize Kubernetes nodes and hosts running BPF-capable workloads in the first patch cycle.
What doesn't work
  • SMAP/SMEP — These CPU features prevent the kernel from accessing user-space memory, but the entire exploit operates within kernel BPF map memory. SMAP/SMEP are irrelevant to this attack path.
  • KASLR alone — The PoC includes a KASLR bypass via OOB map reads that leak kernel pointers. KASLR adds a speed bump but does not prevent exploitation.
  • Network segmentation / firewall rules — This is a local privilege escalation, not a network attack. No amount of network-level controls affect the attack path.
  • Seccomp profiles that allow bpf() — Many infrastructure pods explicitly allow the bpf() syscall in their seccomp profiles because they need it to function. A seccomp profile that blocks bpf() would break Cilium, Falco, and similar tools entirely.
04 · Intelligence Metadata

The supporting signals.

In-the-wild exploitationNot confirmed. Not listed in CISA KEV. No GreyNoise or Shadowserver sightings as of 2026-09-26. The local-only attack vector makes mass scanning irrelevant — exploitation would be targeted and post-initial-access.
PoC / exploit availabilityPublic PoC available. Rat5ak/CVE-2026-31413-BPF-Container-Escape demonstrates full container escape via vtable hijacking and modprobe_path overwrite. Weaponized quality — includes KASLR bypass and namespace escape.
EPSS score0.00175 (~18th percentile). Low predicted exploitation probability, consistent with the capability gate and narrow version window.
KEV statusNot listed as of 2026-09-26.
CVSS vectorCVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H — Local vector, low complexity, low privilege required (CAP_BPF counts as 'low' in CVSS terms), no user interaction. Full CIA impact. Scope is Unchanged, which *understates* the container escape scenario where impact crosses namespace boundaries.
Affected versionsKernel 6.12.75–6.12.79, 6.18.16–6.18.20, 6.19.6–6.19.10, 7.0-rc1 through rc4. Introduced by commit bffacdb ("bpf: Recognize special arithmetic shift in the verifier") on 2026-01-14.
Fixed versions6.12.80, 6.18.21, 6.19.11, 7.0-rc5. Ubuntu 24.04: 6.8.0-136.136 (linux), 6.8.0-1061.64 (linux-aws), 6.8.0-1064.72 (linux-gcp). RHEL: tracking via Bugzilla #2457626. Fix commits: 342aa1ee995e, 58bd87d0e692, d13281ae7ea8, c845894ebd6f.
Exposure / scanningNot applicable for internet scanning — local-only attack vector. Shodan/Censys/GreyNoise have no relevant tags. Exposure is determined by fleet kernel version inventory, not internet-facing surface.
Disclosure timelineIntroduced: 2026-01-14. Fixed in mainline: 2026-03-22. CVE published: 2026-04-12. Vulnerable window: ~67 days in mainline.
Reporting researcherFix authored by kernel BPF maintainers (upstream commit). Container escape PoC by Rat5ak (GitHub).

Sources.

  1. Ubuntu Security Advisory — CVE-2026-31413
  2. OSV — CVE-2026-31413 affected versions and commits
  3. Red Hat Security — CVE-2026-31413
  4. SentinelOne Vulnerability Database — CVE-2026-31413
  5. Rat5ak PoC — BPF Container Escape
  6. SUSE Security — CVE-2026-31413
  7. Linux kernel sysctl documentation — unprivileged_bpf_disabled
  8. Pentera — eBPF security aspects and capability requirements
05 · The Call

Final Verdict
= UNCHANGED to HIGH (7.8/10)

Why this verdict

  • Capability gate is real but porous in Kubernetes: The attack requires CAP_BPF + CAP_PERFMON, which blocks unprivileged users and standard containers. However, infrastructure DaemonSet pods (Cilium, Falco, Datadog Agent, Tetragon) hold these capabilities by design and are present on a majority of production Kubernetes nodes. This caps downward adjustment — the exploitable population is not 'all Linux users' but is far from negligible.
  • Narrow version window limits exposed hosts: The vulnerable code existed for ~67 days in mainline and only shipped in specific stable branch ranges (6.12.75–79, 6.18.16–20, 6.19.6–10). Enterprises on older LTS kernels (5.x, 6.1, 6.6) or already on fixed point releases are not affected. This reduces the fraction of fleet hosts that need emergency action.
  • Weaponized PoC raises practical risk: The Rat5ak PoC is not a crash-only demonstrator — it achieves full container-to-host escape with KASLR bypass, vtable hijacking, and namespace escape. Any red team or adversary with access to a privileged pod can adapt it with minimal effort. This eliminates the 'theoretical only' discount.
  • Role multiplier: (a) *Workstations/dev boxes* — most run older kernels or lack CAP_BPF processes; blast radius is local, severity MEDIUM at this tier. (b) *Standard servers/VMs* — if running affected kernel with a BPF-capable service, blast radius is host compromise; severity HIGH. (c) *Kubernetes nodes with Cilium/Falco/Datadog* — the canonical high-value role for this CVE. A compromised infra pod → container escape → node root → potential cluster-wide lateral movement via kubelet credentials. Blast radius is node → cluster. Cilium alone has ~40% adoption among Kubernetes CNI deployments per CNCF surveys, meaning a substantial fraction of k8s nodes have a pod with the required capabilities. This high-value role outcome floors the verdict at HIGH.
  • No remote vector prevents CRITICAL: Despite the devastating post-exploitation impact, the attack is strictly local (AV:L). An adversary must already have code execution in a privileged context on the target host. There is no network-reachable entry point, no drive-by, no spray-and-pray. This is the primary reason the verdict stays HIGH and does not escalate to CRITICAL.

Why not higher?

CRITICAL would require either a remote attack vector or unprivileged local exploitation. This CVE requires CAP_BPF + CAP_PERFMON — capabilities that are not granted by default to any user or standard container. Even in Kubernetes, the attacker must first compromise an infrastructure pod that holds these capabilities, which is itself a non-trivial prerequisite representing a prior compromise stage. The narrow affected-version window (67 days in mainline, only certain stable branches) further limits the population at risk.

Why not lower?

MEDIUM would undercount the blast radius in Kubernetes environments where infrastructure pods with CAP_BPF are ubiquitous. The PoC is weaponized and demonstrates a complete container-escape-to-host-root chain — not a theoretical crash or information leak. The Linux kernel is the highest-value component in any infrastructure stack, and a verifier soundness bug that yields arbitrary kernel read/write is categorically more dangerous than a typical local privilege escalation. Any Kubernetes fleet running Cilium, Falco, Datadog, or Tetragon on an affected kernel has a realistic, demonstrated path from pod compromise to node takeover.

06 · Verification

Crowdsourced verification payload.

Run this script on each target host as root (or any user with read access to /proc/version_signature and uname). No arguments needed: sudo bash check_cve_2026_31413.sh. It checks the running kernel version against known vulnerable ranges and reports VULNERABLE, PATCHED, or UNKNOWN.

noisgate-verify.sh
BASHREAD-ONLYSAFE
#!/usr/bin/env bash
# check_cve_2026_31413.sh — Detect CVE-2026-31413 (BPF verifier scalar forking bug)
# Affected: 6.12.75-6.12.79, 6.18.16-6.18.20, 6.19.6-6.19.10, 7.0-rc1 through 7.0-rc4
# Fixed:    6.12.80, 6.18.21, 6.19.11, 7.0-rc5+
# Exit codes: 1=VULNERABLE, 0=PATCHED, 2=UNKNOWN

set -euo pipefail

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

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

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

echo "[*] Parsed version: $MAJOR.$MINOR.$PATCH"

# Check for 7.0-rcN pattern
if [[ $MAJOR -eq 7 && $MINOR -eq 0 ]]; then
  RC=$(echo "$KERNEL" | grep -oP 'rc\K[0-9]+' || echo "99")
  if [[ $RC -ge 1 && $RC -le 4 ]]; then
    echo "[!] VULNERABLE — kernel 7.0-rc$RC is in the affected range (7.0-rc1 through rc4)"
    exit 1
  elif [[ $RC -ge 5 ]] || [[ $PATCH -ge 0 && "$KERNEL" != *rc* ]]; then
    echo "[+] PATCHED — kernel 7.0-rc$RC / 7.0.x is at or above the fix (7.0-rc5)"
    exit 0
  fi
fi

# 6.12.x branch
if [[ $MAJOR -eq 6 && $MINOR -eq 12 ]]; then
  if [[ $PATCH -ge 75 && $PATCH -le 79 ]]; then
    echo "[!] VULNERABLE — kernel 6.12.$PATCH is in the affected range (6.12.75–6.12.79)"
    exit 1
  elif [[ $PATCH -ge 80 ]]; then
    echo "[+] PATCHED — kernel 6.12.$PATCH is at or above the fix (6.12.80)"
    exit 0
  else
    echo "[+] PATCHED — kernel 6.12.$PATCH predates the vulnerable code (introduced at 6.12.75)"
    exit 0
  fi
fi

# 6.18.x branch
if [[ $MAJOR -eq 6 && $MINOR -eq 18 ]]; then
  if [[ $PATCH -ge 16 && $PATCH -le 20 ]]; then
    echo "[!] VULNERABLE — kernel 6.18.$PATCH is in the affected range (6.18.16–6.18.20)"
    exit 1
  elif [[ $PATCH -ge 21 ]]; then
    echo "[+] PATCHED — kernel 6.18.$PATCH is at or above the fix (6.18.21)"
    exit 0
  else
    echo "[+] PATCHED — kernel 6.18.$PATCH predates the vulnerable code (introduced at 6.18.16)"
    exit 0
  fi
fi

# 6.19.x branch
if [[ $MAJOR -eq 6 && $MINOR -eq 19 ]]; then
  if [[ $PATCH -ge 6 && $PATCH -le 10 ]]; then
    echo "[!] VULNERABLE — kernel 6.19.$PATCH is in the affected range (6.19.6–6.19.10)"
    exit 1
  elif [[ $PATCH -ge 11 ]]; then
    echo "[+] PATCHED — kernel 6.19.$PATCH is at or above the fix (6.19.11)"
    exit 0
  else
    echo "[+] PATCHED — kernel 6.19.$PATCH predates the vulnerable code (introduced at 6.19.6)"
    exit 0
  fi
fi

# Check for Ubuntu-specific kernel (6.8.x on 24.04)
if [[ $MAJOR -eq 6 && $MINOR -eq 8 ]]; then
  # Ubuntu 24.04 backport — check dpkg version if available
  if command -v dpkg &>/dev/null; then
    PKG_VER=$(dpkg -l linux-image-"$KERNEL" 2>/dev/null | awk '/^ii/{print $3}' || echo "")
    if [[ -n "$PKG_VER" ]]; then
      echo "[*] Ubuntu package version: $PKG_VER"
      # Fixed in 6.8.0-136.136
      PKG_BUILD=$(echo "$PKG_VER" | grep -oP '[0-9]+\.[0-9]+\.[0-9]+-\K[0-9]+' || echo "0")
      if [[ $PKG_BUILD -ge 136 ]]; then
        echo "[+] PATCHED — Ubuntu package $PKG_VER includes the fix (>= 6.8.0-136)"
        exit 0
      else
        echo "[!] VULNERABLE — Ubuntu package $PKG_VER is below the fix (6.8.0-136.136)"
        exit 1
      fi
    fi
  fi
  echo "[?] UNKNOWN — kernel 6.8.x detected but could not determine distro patch level"
  exit 2
fi

# All other kernels — not in known affected ranges
if [[ $MAJOR -lt 6 ]]; then
  echo "[+] PATCHED — kernel $MAJOR.$MINOR.$PATCH predates the vulnerable code entirely"
  exit 0
fi

if [[ $MAJOR -eq 6 && $MINOR -lt 12 ]]; then
  echo "[+] PATCHED — kernel 6.$MINOR.$PATCH is not in any affected branch"
  exit 0
fi

echo "[?] UNKNOWN — kernel $MAJOR.$MINOR.$PATCH not in a recognized affected or fixed range. Check manually."
exit 2