GhostLock: Analyzing the 15-Year-Old Linux Kernel Stack Use-After-Free (CVE-2026-43499)
CVE-2026-43499 is a stack-based use-after-free vulnerability affecting the Linux kernel's rtmutex subsystem since version 2.6.39. The bug stems from an incorrect task reference cleanup during proxy lock rollback, allowing local unprivileged users to leave a dangling pointer on a thread's kernel stack.
Technical Overview and Impact
GhostLock, designated as CVE-2026-43499, is a high-severity kernel vulnerability introduced in 2011 with the rtmutex subsystem rework in commit 8161239a8bcc. The bug affects all major Linux distributions from kernel versions v2.6.39-rc1 to v7.1-rc1. Exploitation requires only `CONFIG_FUTEX_PI=y` to be enabled, needing no special privileges, user namespaces, or non-standard container capabilities. Security researchers demonstrated a highly stable privilege escalation and container escape using this flaw, earning a reward in Google's kernelCTF.
Root Cause in the rtmutex Subsystem
The core vulnerability lies in `remove_waiter()` within `kernel/locking/rtmutex.c`. This helper function was designed under the assumption that the thread calling it is always the owner of the waiter object. Consequently, it clears `current->pi_blocked_on` during cleanup.
This design assumption fails in the Requeue-PI proxy path. When `rt_mutex_start_proxy_lock()` is invoked, it enqueues an `rt_mutex_waiter` on behalf of a sleeping proxy task. If the proxy lock attempt fails and returns an error, such as `-EDEADLK`, the kernel executes a rollback by calling `remove_waiter()`.
Because `current` in this path is the requeueing thread (the caller of `FUTEX_CMP_REQUEUE_PI`) rather than the sleeping waiter thread, `remove_waiter()` clears `pi_blocked_on` for the wrong task. The actual sleeping waiter task retains its pointer to `rt_mutex_waiter`, which resides directly on its kernel stack. The patch addresses this by modifying the cleanup sequence to lock `waiter->task->pi_lock` and clear `waiter->task->pi_blocked_on` instead of modifying `current`.
Triggering the -EDEADLK Path
Reaching the buggy rollback path requires establishing a lock dependency cycle. This is constructed using three threads and three specific futexes:
- `f_pi_chain`: A PI futex initially locked by the waiter thread.
- `f_pi_target`: A PI futex initially locked by the owner thread, acting as the requeue target.
- `f_wait`: A traditional futex where the waiter thread blocks.
The execution flow to trigger the deadlock rollback proceeds in a specific sequence:
- The waiter thread acquires `f_pi_chain` and subsequently blocks on `f_wait` via a `FUTEX_WAIT_REQUEUE_PI` call, placing its `rt_mutex_waiter` on its kernel stack.
- The owner thread acquires `f_pi_target` and blocks on `f_pi_chain`, which is held by the waiter thread.
- The main thread executes `FUTEX_CMP_REQUEUE_PI` from `f_wait` to `f_pi_target`.
- The kernel attempts to proxy lock the waiter onto `f_pi_target`. Because the owner of `f_pi_target` is blocked behind the waiter, a dependency cycle is closed: waiter to target, to owner, to chain, and back to waiter.
- The deadlock detection engine identifies this cycle, returns `-EDEADLK`, and executes the flawed rollback.
The Stack Use-After-Free State
Once the rollback completes and the system resolves the deadlock, the waiter thread wakes up and returns to userspace. This return pops its kernel stack frame, effectively freeing the memory where the `rt_mutex_waiter` structure was located.
Because the cleanup cleared the requeuer's state instead of the waiter's state, the waiter's `pi_blocked_on` field still points to the now-deallocated stack frame. This creates a persistent stack use-after-free condition with no strict timing constraints. A subsequent system call that walks the priority inheritance chain, such as `sched_setattr()`, will traverse this dangling pointer. To control execution, an attacker must reclaim the popped stack frame with controlled data at the matching offset.