GhostLock ( CVE-2026-43499 ) is a use-after-free in the Linux kernel’s rtmutex/futex-PI code ( remove_waiter() in kernel/locking/rtmutex.c ). On the requeue-PI proxy path, remove_waiter() clears pi_blocked_on on the requeuer instead of the waiter , so the waiter task can return to userspace with pi_blocked_on still pointing at the rt_mutex_waiter on its own FUTEX_WAIT_REQUEUE_PI stack frame. That frame is freed the moment the syscall returns, and any later PI chain walk through the task follows the dangling pointer.
GhostLock was introduced by the 2011 rtmutex rework and sat untouched for fifteen years. Triggering it needs only ordinary threading and futex syscalls: no config beyond CONFIG_FUTEX_PI=y , and no capabilities or namespaces. We turned the stack-UAF into a 97% stable privilege escalation and container escape, and won Google’s kernelCTF.
Regular syscalls mint a dangling pointer into freed kernel stack, which the attacker reclaims with controlled bytes and dereferences on demand as a forged rt_mutex_waiter . The whole chain runs locally from an unprivileged process.
The vulnerable code is built in whenever PI futexes are configured. A target is vulnerable if:
No capabilities, user namespaces, or network access are needed: the trigger is a set of ordinary threading and futex syscalls available to any local process. In practice, every unpatched Linux distribution is affected.
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, so current is the requeuer, not the waiter. When the proxy enqueue hits -EDEADLK and rolls back, remove_waiter() dequeues the waiter from the lock but scrubs pi_blocked_on on the wrong task. lockdep does not catch it, because it only checks that a pi_lock is held, not whose.
The waiter task then returns to userspace with pi_blocked_on still pointing at its freed FUTEX_WAIT_REQUEUE_PI frame. Reaching the rollback needs a PI dependency cycle across three futex words and three threads ( waiter -> f_pi_target -> owner -> f_pi_chain -> waiter ), which closes on its own once staged. After that there is no time pressure: a follow-up sched_setattr() walks the chain through the task whenever it likes, and dereferences the dangling pointer.
The full write-up covers the three-futex deadlock, the stack reclaim with PR_SET_MM_MAP , forging the waiter past its structural checks, and the CEA overlay that pivots and flips core_pattern in a single write.
The upstream fix reads the owning task out of waiter->task instead of current , so the proxy rollback scrubs the right pi_blocked_on :
Update to a fixed kernel (the latest LTS), carrying both the fix and its follow-up. There is no clean site-level workaround, since the trigger is ordinary futex and threading syscalls that any local process can issue. Two config options raise the bar without removing the bug: RANDOMIZE_KSTACK_OFFSET turns the deterministic stack reuse into a roughly 1/64 (>5-bit) guess. But it is not a substitute for patching.
Found by VEGA at Nebula Security. We reported the bug with a draft patch to [email protected] on Apr 18, 2026; it was fixed two days later and backported on May 4. A regression introduced by the first fix (CVE-2026-53166) was patched on Jun 2. Google acknowledged the kernelCTF submission and rewarded us $92,337 on Jun 30.
For all bugs found by VEGA, we follow a 90+30 day disclosure policy.
The full story
This article is one source in a clustered incident — the cluster page carries the summary, timeline and every other outlet covering it.
