diff --git a/src/binary-exploitation/common-exploiting-problems.md b/src/binary-exploitation/common-exploiting-problems.md index dcb9db55bea..9de78348582 100644 --- a/src/binary-exploitation/common-exploiting-problems.md +++ b/src/binary-exploitation/common-exploiting-problems.md @@ -216,6 +216,97 @@ so the combined residue uniquely equals the 64-bit pointer to `kCFNull`. The Pro No memory safety bug is required—simply observing serialization order of pointer-keyed structures yields a remote ASLR bypass primitive. +## Kernel waiter cleanup confusion, stack-UAF reclaim & constrained tree-writes + +Some kernel bugs become exploitable because a cleanup helper assumes that **`current` owns the object being cleaned up**, but a later proxy path reuses that helper **on behalf of another task**. If rollback removes the waiter from a lock/tree but clears the blocked state on the wrong task, the real owner can keep a **dangling pointer to a stack object** that belonged to a previous syscall frame. + +### Proxy cleanup confusion in PI/futex style paths + +Look for code with this shape: + +- a slow-path helper was originally written for self-blocking tasks +- a proxy/requeue path later reuses the same helper for another sleeping task +- rollback dequeues the waiter/object but clears the blocked pointer/state on `current` +- later priority-chain / wait-chain / lock-owner walks dereference the stale pointer + +This is especially interesting when the stale object was allocated in a **kernel stack frame** (not heap), because the owner task can often reclaim the same stack region itself. + +### Deterministic rollback by creating a dependency cycle + +If exploitation needs a rare rollback path, try to force a **dependency cycle** instead of racing for a tiny window. In the futex PI case, a reliable pattern is: + +1. Thread A holds `f_pi_chain` and sleeps in `FUTEX_WAIT_REQUEUE_PI(f_wait -> f_pi_target)`. +2. Thread B holds `f_pi_target` and blocks on `f_pi_chain`. +3. Thread C calls `FUTEX_CMP_REQUEUE_PI(f_wait -> f_pi_target)`. + +The kernel observes: + +```text +waiter -> f_pi_target -> owner -> f_pi_chain -> waiter +``` + +and the chain walk returns `-EDEADLK`, driving cleanup/rollback code without needing privileges. + +### Reclaiming a freed kernel stack frame with same-thread syscall locals + +When a stale pointer targets a **previous syscall frame on a task's own kernel stack**, the same task can often reclaim it by immediately entering another syscall whose **controlled local buffer lands at a similar stack depth**. + +Useful reclaim candidates are syscalls that copy attacker-controlled data into large stack locals, such as: + +- `prctl(PR_SET_MM, PR_SET_MM_MAP, ...)` +- `clone` +- `setsockopt` +- `pselect` +- `keyctl` + +If the copy source is user-controlled, page-boundary placement plus concurrent invalidation (for example a backing file hole-punch race) may stretch the `copy_from_user()` window long enough for another thread to consume the forged frame. + +### Turning tree erase into a constrained pointer write + +If the forged stale object is later passed to a tree-removal primitive such as `rb_erase()`, shape it as a **single-child root** so removal promotes the chosen child into the root slot. If the surrounding memory is reinterpreted as: + +```text +target - 8 -> lock / metadata / spinlock fields +target -> tree root pointer +target + 8 -> sibling / leftmost metadata +target + 16 -> owner / state +``` + +the primitive often becomes: + +```c +*(uint64_t *)target = controlled_child; +``` + +This is usually **not** a fully arbitrary write. Typical constraints are: + +- the qword before the target must look like an unlocked lock +- metadata after the target must not force unsafe dereferences +- the written pointer must reference a self-consistent fake object that survives the remaining walk + +### Small controlled kernel staging areas + +If the constrained write only lets you redirect an existing function/object pointer, a **small but stable kernel buffer** is often enough. On x86, the **CPU Entry Area (CEA)** can be reused as a compact staging area for: + +- fake objects that survive sanity checks +- safe dereference targets +- pivot data +- a very short ROP/JOP chain + +If the virtual CEA mapping is randomized, the **direct-map alias** can still be useful once `physmap` is leaked. + +### One-write privilege flips (DirtyMode style) + +When the hijack gives only a **very short** control-flow window, do not spend it on a full credential overwrite. A more reliable pattern is a **single kernel write** that weakens a permissions gate and then completes privilege escalation from userspace. + +A representative target is a writable `ctl_table` entry such as `core_pattern`: + +- flip the mode field so `/proc/sys/kernel/core_pattern` becomes writable by an unprivileged user +- write a pipe handler such as `|/proc/%P/fd/666 %P` +- crash a helper process so the kernel executes the attacker-controlled handler as root + +This pattern is useful whenever the initial primitive can reach writable policy bits more easily than `cred` or a long in-kernel ROP chain. + ## Related pages {{#ref}} @@ -235,6 +326,7 @@ common-exploiting-problems-unsafe-relocation-fixups.md - [FD duplication exploit example](https://ir0nstone.gitbook.io/notes/types/stack/exploiting-over-sockets/exploit) - [Socat delete-character behaviour](https://ir0nstone.gitbook.io/hackthebox/challenges/pwn/dream-diary-chapter-1/unlink-exploit) - [FuzzMe – Reverse Engineering and Fuzzing an Android Shared Library](https://hackmd.io/@sal/fuzzme-mobilehackinglab-ctf-writeup) +- [IonStack Part II: GhostLock, a stack-UAF that has existed in ALL Linux distributions for 15 years](https://nebusec.ai/research/ionstack-part-2) - [Pointer leaks through pointer-keyed data structures (Project Zero)](https://projectzero.google/2025/09/pointer-leaks-through-pointer-keyed.html) {{#include ../banners/hacktricks-training.md}}