Overview

AttributeValue
CVE IDCVE-2026-43121
RelatedCVE-2026-23263 (page array leak, same subsystem)
CVSS Score~7.8 (est., NVD enrichment pending)
VendorLinux Kernel
Componentio_uring/ZCRX (zero-copy receive)
Vulnerability TypeMissing bounds check → OOB heap write; Race condition → double-free
AffectedLinux kernel 6.12–6.19 (CONFIG_IO_URING_ZCRX=y)
Patched✅ Commit 770594e (bounds check) + 003049b1c4fb (atomic race fix), backported to v6.18.16
Exploit Status⚠️ PoC demonstrated (kernel module + userspace harness)
KEV AddedNot yet

What Is ZCRX?

io_uring’s ZCRX (zero-copy receive) is a subsystem new to Linux 6.15 that lets userspace receive network packets directly into a registered memory region — no kernel copy needed. The NIC DMAs into the region, the kernel posts a completion event, and the user returns the slot when done via a refill queue.

The region is divided into 4KB slots. Each slot has a net_iov struct tracking its state. The kernel manages free slots with:

  • freelist[] — a stack of available slot indices, allocated as kvmalloc_array(nr_iovs, sizeof(u32))
  • free_count — the stack depth, also used as the write index for the next push

The bug is simple. There is no upper bound check on free_count.


The Vulnerability

Bug #1: Missing Bounds Check (Freelist OOB)

static void io_zcrx_return_niov_freelist(struct net_iov *niov)
{
    struct io_zcrx_area *area = io_zcrx_iov_to_area(niov);

    spin_lock_bh(&area->freelist_lock);
    area->freelist[area->free_count++] = net_iov_idx(niov);  // NO BOUNDS CHECK
    spin_unlock_bh(&area->freelist_lock);
}

Two separate kernel teardown paths both return niovs to the same freelist. When they overlap, free_count exceeds num_niovs — and the write at freelist[free_count] walks past the end of the allocated array.

Result: a 4-byte out-of-bounds write into adjacent slab memory.

Bug #2: Race Condition → Double-Free (CVE-2026-43121)

The io_zcrx_put_niov_uref() function uses a non-atomic check-then-decrement on user_refs. Concurrent scrub and refill paths can both claim the final reference:

CPU0 (refill, holds rq_lock)        CPU1 (scrub, no rq_lock)
─────────────────────               ─────────────────────
atomic_read(uref) → 1
                                    atomic_xchg(uref, 0) → 1
                                    return_niov_freelist(niov) [PUSH #1]
atomic_dec(uref) → wraps to -1
returns true
return_niov_freelist(niov) [PUSH #2: DOUBLE-FREE]

Same niov pushed twice → free_count > nr_iovs → subsequent pushes write past the array end.

The OOB value is a niov index — a small integer between 0 and N-1. “That sounds useless. It is not.”


Exploitation

Write Primitive

The freelist[] array is allocated via kvmalloc_array(nr_iovs, sizeof(u32)). For small N values, this lands in the kmalloc-64 slab cache. The 4-byte OOB write at offset num_niovs * 4 corrupts the header of the next slab object.

Confirmed effects:

  • Adjacent slab corruption: kmalloc-64 fields overwritten (e.g., 0xAABBCCDD → 0x00000007)
  • Credential manipulation: With controlled kernel context, UID transition from non-root to root demonstrated via prepare_creds() + credential zeroing + commit_creds()

Requirements

RequirementDetail
CapabilityCAP_NET_ADMIN (enforced at io_register_zcrx_ifq())
HardwareNIC supporting page pool-backed memory providers (mlx5, nfp)
Kernel configCONFIG_IO_URING_ZCRX=y
Container⚠️ CAP_NET_ADMIN commonly granted in Kubernetes networking plugins and Docker containers

This makes the issue particularly relevant in container environments where CAP_NET_ADMIN is routinely granted.


The Fix

Bounds Check (commit 770594e)

static void io_zcrx_return_niov_freelist(struct net_iov *niov)
{
    struct io_zcrx_area *area = io_zcrx_iov_to_area(niov);

    guard(spinlock_bh)(&area->freelist_lock);

    if (WARN_ON_ONCE(area->free_count >= area->nia.num_niovs))
        return;

    area->freelist[area->free_count++] = net_iov_idx(niov);
}

Atomic Race Fix (commit 003049b1c4fb)

// Before (vulnerable): non-atomic check-then-decrement
if (niov->user_refs) {
    niov->user_refs--;
    if (!niov->user_refs)
        io_zcrx_return_niov(niov);
}

// After (fixed): atomic compare-and-exchange loop
u32 refs = atomic_read(&niov->user_refs);
do {
    if (!refs)
        return;
} while (!atomic_try_cmpxchg(&niov->user_refs, &refs, refs - 1));

if (refs == 1)
    io_zcrx_return_niov(niov);  // only winner pushes

io_uring Attack Surface

Google’s security team reported that 60% of kCTF VRP Linux kernel exploits in 2022 targeted io_uring. Multiple container runtimes (gVisor, ChromeOS) disable io_uring entirely due to its attack surface.

ZCRX adds shared-memory buffer management between kernel and userspace — exactly the pattern where atomicity bugs hide. This is the third io_uring CVE in recent months, and it won’t be the last.


CVEBugPrimitiveRace?Bypasses Copy Fail?
CVE-2026-23263Page array leakMemory leak❌N/A
CVE-2026-43121Freelist OOB + double-free4-byte controlled OOB write✅❌ (different path)
CVE-2026-31431Copy Fail (authencesn)4-byte page cache write❌N/A
CVE-2026-43284Dirty Frag (ESP)4-byte arbitrary STORE❌✅ Bypasses algif_aead

Remediation

Check Vulnerability

# Check if ZCRX is enabled in your kernel config
grep CONFIG_IO_URING_ZCRX /boot/config-$(uname -r)

# Check kernel version (6.18.16+ has the fix)
uname -r

Immediate Mitigations

# Option 1: Restrict io_uring via seccomp (recommended for containers)
# Add to container runtime profile or seccomp policy:
# SCMP_SYS(io_uring_setup) → ENOSYS
# SCMP_SYS(io_uring_enter) → ENOSYS
# SCMP_SYS(io_uring_register) → ENOSYS

# Option 2: Restrict CAP_NET_ADMIN in container runtime profiles
# Remove CAP_NET_ADMIN from default Docker/K8s capabilities

# Option 3: Disable io_uring system-wide (aggressive, may break workloads)
sysctl -w kernel.io_uring_disabled=2

Proper Fix

  • Update kernel to 6.18.16+ or backport commits 770594e and 003049b1c4fb
  • All major distros should ship fixes in upcoming kernel updates

Disclosure Timeline

DateEvent
2026-03-18CVE-2026-23263 assigned (page array leak, related)
2026-04-21Commit 770594e adds WARN_ON_ONCE bounds check
2026-05-03Mohamed salem Eddah requests CVE for freelist OOB on oss-sec
2026-05-03Greg KH responds (standard CVE process)
2026-05-04Pavel Begunkov (io_uring maintainer) comments on thread
2026-05-07Extended oss-sec discussion (Jens Axboe, Solar Designer)
2026-05-08ze3tar publishes “Damned OOB” writeup
2026-05-10CVE-2026-43121 assigned (user_ref race → double-free → OOB)

Sources


See also: CVE-2026-31431 — Copy Fail · CVE-2026-43284 — Dirty Frag · CVE Database