GhostLock is not a vulnerability.
GhostLock (CVE-2026-43499) is a 15-year rtmutex stack-UAF in Linux turned into a 97% stable root exploit via prctl stack reclaim and a one-write DirtyMode trick.
CVE-2026-43499, which Nebu calls GhostLock, has sat in the Linux kernel since the rtmutex rework landed in 2.6.39 in 2011. The affected range runs from v2.6.39-rc1 to v7.1-rc1, which covers every major distribution that has not picked up the April 2026 fix. The trigger requirements are not exotic: CONFIG_FUTEX_PI=y is the only one, with no capabilities and no user namespaces needed. Nebu built it into a 97% stable local privilege escalation and container escape, and Google paid $92,337 for the kernelCTF submission.
The bug is a lifecycle mistake in remove_waiter() in kernel/locking/rtmutex.c. That helper was written for one case: a thread blocks on a lock, then cleans up after itself, so it clears current->pi_blocked_on on the assumption that current is the waiter it is tearing down. Requeue-PI breaks that assumption. Through rt_mutex_start_proxy_lock(), the same helper now rolls back a waiter on behalf of a different, sleeping thread, and current is the thread that issued FUTEX_CMP_REQUEUE_PI rather than the waiter. When __rt_mutex_start_proxy_lock() returns -EDEADLK and rolls back through remove_waiter(), it scrubs the requeuer’s pi_blocked_on and leaves the sleeping waiter’s untouched.
The waiter object lives on the stack of the task sleeping in FUTEX_WAIT_REQUEUE_PI. After the botched rollback, that task still has pi_blocked_on pointing at its own stack frame, which is popped the moment it returns to userspace. Any later PI chain walk through the task follows the dangling pointer into freed stack. lockdep does not catch it, because it only checks that a pi_lock is held, not whose.
Reaching the -EDEADLK path takes a PI dependency cycle built from three futex words. A waiter thread takes the PI futex f_pi_chain, then blocks in FUTEX_WAIT_REQUEUE_PI on a plain futex pointing at the requeue target f_pi_target. An owner thread takes f_pi_target, then blocks on f_pi_chain, which the waiter holds. A third thread issues FUTEX_CMP_REQUEUE_PI to proxy the waiter onto f_pi_target, whose owner is already blocked behind the waiter, so the chain walk closes the loop, returns -EDEADLK, and takes the buggy rollback. Only one CPU core is required, and once the cycle is staged there is no time pressure. The waiter sits in userspace with the dangling pointer, and the sched_setattr() that walks the chain can fire whenever it likes.
The reclaim is the clever part. The freed object is on the waiter’s own kernel stack, so the spray has to land controlled bytes back on that exact frame at the same depth. The waiter returns from the futex syscall and immediately calls prctl(PR_SET_MM, PR_SET_MM_MAP, ...), whose prctl_set_mm_map() copies a user-supplied auxv into a fixed-size user_auxv[AT_VECTOR_SIZE] stack buffer at roughly the same depth. That gives a large, aligned, namespace-free block of controlled qwords sitting on top of the old rt_mutex_waiter, which the auxv forges outright: an rb node crafted so the erase promotes a chosen child, task set to &init_task so the walk’s derefs are safe, and lock aimed at the write target. The auxv is backed by a memfd straddling a page boundary, and a sibling thread races fallocate(PUNCH_HOLE) on the trailing page to stretch the copy_from_user window while another CPU fires sched_setattr(). Nebu notes clone, setsockopt, pselect, keyctl and other syscalls with large controlled stack locals work the same way.
The write itself comes from the chain walk’s rb-tree erase (rt_mutex_dequeue()), which writes one child pointer into the root slot. Point lock at inet6_protos[IPPROTO_UDP] - 8 and the erase overwrites the UDP entry in the IPv6 protocol table with a pointer into the CPU entry area. The constraints are strict: the qword before the target must read as an unlocked spinlock or the trylock fails and nothing is written, and the qwords after it must not steer the walk into an unmapped owner, which faults and panics the box.
The CEA earns its place because it is controllable memory at a known kernel address. Before 6.2 it sat at a fixed virtual address; since 6.2 the virtual address is randomized, but the physical offset is fixed, so the direct-map alias follows from the physmap base, which a prefetch timing leak recovers. On the kernelCTF LTS 6.12.80 3.5G-boot environment the offset is 0x11c517000. Nebu stages the fake inet6_protocol, the pivot gadgets and the ROP stack in that one ~120-byte window. A loopback IPv6 UDP packet (connect then write to ::1) dereferences the overwritten handler and hands over PC.
The last stage is the cheapest possible write. Rather than run a long ROP chain, Nebu flips one permission bit. coredump_sysctls[1].mode gets any value with the write bit set, which makes /proc/sys/kernel/core_pattern world-writable. An unprivileged process then writes |/proc/%P/fd/666 %P and crashes a helper so the kernel runs its binary as root. Nebu calls this DirtyMode. On the lts-6.12.80 target the whole chain won the flag in about five seconds.
For anyone running these kernels, the fix is upstream (commit 3bfdc63936dd, April 2026) and backported, so get onto a patched LTS. Two config options matter for defence in depth. RANDOMIZE_KSTACK_OFFSET breaks the deterministic overlap between the freed waiter frame and the user_auxv buffer, dropping the reclaim to a roughly 1-in-32 stack-offset guess; both of Nebu’s submitted targets had it off, and the mitigation target that turned it on was not taken through this path. STATIC_USERMODE_HELPER closes the DirtyMode route, though the same ctl_table::mode trick generalizes to any /proc/sys knob whose table sits in predictable writable kernel data.
One caveat on the patch itself. The first fix introduced a NULL-pointer dereference (CVE-2026-53166) when a non-top requeue waiter that already owns the target futex hits -EDEADLK before waiter->task is set. That was patched on 2026-07-03 by commit 40a25d59e85b. If you pulled the GhostLock fix between May and early July, confirm you also have the follow-up.
Keep Reading
ubuntuUbuntu still has no patch for CVSS 9.8 kernel flaw
CVE-2026-98432 is a CVSS 9.8 remote root flaw in the Linux kernel's TCP code. Debian patched it October 16; Ubuntu still has no fix two weeks on.
linuxYour browser tab runs as root on Omarchy
Omarchy versions before 4.0.1 put the default user in the docker group, giving any process in the session a passwordless path to root. Update to 4.0.1.
linux-kernelKees Cook deleted strncpy from the Linux kernel
Linux 7.2 removed strncpy after six years and about 362 commits. Here are the strscpy, strtomem_pad and memcpy replacements and when to use each.
Latest on the Wire
Full wire →- Bending Spoons Destroys Harvest With 10x Price Hike and LLM SupportHacker News
- CISA Flags SonicWall MFA Bypass Amid Exploitation ReportsThe Hacker News
- Claude Opus Ports 3D Models from Three.js to Vanilla WebGLSimon Willison
- Collapsing AI PR descriptions with HTML details tagsSimon Willison
New signal daily · RSS
Stay in the loop
New writing delivered when it's ready. No schedule, no spam.