Article URL: https://nebusec.ai/research/ionstack-part-2/ Comments URL: https://news.ycombinator.com/item?id=48864969 Points: 26 # Comments: 6

GhostLock (CVE-2026-43499) is a Linux kernel vulnerability found by VEGA that exists in every major distribution since 2011. Triggering the bug does not require any special kernel config or privilege. By turning it into a 97% stable privilege escalation and container escape, Google has rewarded us $92,337 in kernelCTF. This writeup covers the technical details of the exploit. GhostLock was introduced in Linux 2.6.39 and fixed in Linux 7.1. It has existed in the Linux kernel for more than 15 years. Every Linux distribution without the patch is affected and should consider upgrading to the latest LTS version. GhostLock was introduced with the rtmutex rework in 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 (“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. 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 is the same shape as many other life-cycle bugs: a function reused by a caller it was never written for. The helper function remove_waiter() was originally written for exactly one scenario: a thread blocks on its own, then cleans up after itself. So it has always assumed that current (whichever thread happens to be running) is the waiter it needs to clean up, and clears current->pi_blocked_on accordingly. However, Requeue-PI breaks that assumption. Through rt_mutex_start_proxy_lock(), this helper is now used to clean up on behalf of a different, sleeping thread. In that path, current is the thread that issued FUTEX_CMP_REQUEUE_PI rather than the actual waiter.