필사 모드: GhostLock: a stack use-after-free that hid in Linux rtmutex for 15 years (CVE-2026-43499)
English- Introduction — the cause was surprisingly small
- What a stack use-after-free is
- What GhostLock actually is
- Fifteen years in plain sight — and what exploiting it takes
- Closing — patch, and how to check exposure
- References
Introduction — the cause was surprisingly small
On 2026-07-07 a writeup titled "IonStack part II" appeared on nebusec.ai and made the rounds on Hacker News. It describes GhostLock, CVE-2026-43499: a stack use-after-free in the Linux kernel's real-time mutex code (kernel/locking/rtmutex.c). By the researchers' account the flaw had been in mainline since Linux 2.6.39-rc1 in 2011 and was only fixed in 7.1-rc1 in April 2026 — roughly fifteen years.
The interesting part is not the headline number. It is that the cause is almost embarrassingly small — a helper function that assumed the wrong thing about which thread was calling it — and yet it survived a decade and a half of review, fuzzing, and lockdep. This post covers three things: what a stack use-after-free is and why it is subtle, how a bug this small hides that long, and, kept deliberately measured, what GhostLock actually takes to exploit and what you should do about it.
One caveat up front: this is part II of a series, and it does not summarize part I. I only describe what part II covers, and I flag which figures rest on that single writeup rather than independent confirmation.
What a stack use-after-free is
A use-after-free is what it sounds like: memory is freed, and something keeps using a pointer to it. The heap version is familiar. The stack version is stranger. Local variables live in a function's stack frame; when the function returns, that frame is "freed" only in the sense that the space is immediately reusable by the next call. If a pointer to a local outlives the function that owns it, later code can read or write memory that now belongs to something else.
In the kernel this is nastier than in userspace. There is no allocator metadata to corrupt and no obvious crash — the stack is just RAM, and reusing it is the normal, fast path. A dangling pointer into a freed stack frame does nothing at all until someone else's data happens to land there.
That silence is the whole problem. The bug is invisible in ordinary testing precisely because, most of the time, nothing has overwritten the freed frame yet. It only becomes a weapon when an attacker deliberately arranges to reclaim that space with bytes they control — which is exactly what the GhostLock exploit does.
What GhostLock actually is
The affected code is the real-time mutex (rtmutex) subsystem, reached through PI-futex syscalls — the priority-inheritance machinery that keeps a low-priority thread holding a lock from starving a high-priority one. The specific helper is remove_waiter(). It was written for one situation: a thread blocks on a lock, then cleans up after itself. So it assumed the running thread (current) was always the waiter being removed, and cleared current->pi_blocked_on.
The requeue-PI feature quietly broke that assumption. With FUTEX_WAIT_REQUEUE_PI and FUTEX_CMP_REQUEUE_PI, one thread's rt_mutex_waiter — which lives on that thread's stack — gets proxied onto another futex by a different thread via rt_mutex_start_proxy_lock(). If that path detects a deadlock cycle and rolls back with -EDEADLK, it calls remove_waiter() from the wrong thread, clearing the requeuer's state instead of the real waiter's. The waiter returns to userspace still linked into a PI chain through a pointer into its own, now-reused stack frame, and the next chain walk dereferences freed memory.
/* rtmutex.c: the helper assumed the running thread WAS the waiter */
static void remove_waiter(struct rt_mutex *lock, struct rt_mutex_waiter *w)
{
/* ... unlink w from the lock's waiter tree ... */
current->pi_blocked_on = NULL; /* BUG: clears current, not w */
}
/* Fix, commit 3bfdc63936dd: operate on the waiter's own task */
raw_spin_lock(&w->task->pi_lock);
w->task->pi_blocked_on = NULL;
The fix, commit 3bfdc63936dd ("rtmutex: Use waiter::task instead of current in remove_waiter()"), is a one-liner in spirit: take the waiter's own task rather than current, lock waiter->task->pi_lock, and clear waiter->task->pi_blocked_on. Fifteen years of exposure closed by pointing at the right structure — reassuring and a little unsettling at once.
Fifteen years in plain sight — and what exploiting it takes
The writeup's explanation for the longevity is mundane and convincing: function reuse across an assumption boundary. remove_waiter() was correct for its original caller and stayed correct there. Requeue-PI added a second caller with a different notion of who current is, and nobody re-checked the assumption. Reaching the bug also needs a specific, non-random setup — a priority-inheritance dependency cycle across three futexes and three threads to force the -EDEADLK rollback — the kind of state fuzzers and everyday workloads rarely stage.
Now the honest part about impact. GhostLock is local privilege escalation, not remote code execution. You already need to run code on the machine — a shell, or a process inside a container — before any of this applies. The exploit is also not a one-liner: the public chain grooms the freed frame with prctl(PR_SET_MM_MAP) to forge a fake waiter, uses a constrained rb-tree write to overwrite inet6_protos[IPPROTO_UDP] in the CPU entry area, and finally flips permission bits on /proc/sys/kernel/core_pattern to gain root.
That is research-grade work. The authors report 97% stability under kernelCTF conditions, which left hardening options off, and Google's kernelCTF paid 92,337 USD for it. The fair reading: reliable and real, but bounded — it needs local access and an elaborate chain. Container escape, where a local privesc turns one compromised workload into control of the whole node, is the scenario worth worrying about most.
The disclosure was fast, which matters as much as the fix. Per the writeup the bug was reported to security@kernel.org on 2026-04-18, patched in mainline on 2026-04-20, and backported to stable on 2026-05-04; Google acknowledged the kernelCTF submission on 2026-06-30, and the public writeup followed on 2026-07-07. The gap between quietly fixed and publicly detailed was about two months — enough time for stable users to update before the mechanics were on the front page.
Closing — patch, and how to check exposure
The practical response is boring and correct: patch. The fix landed in mainline as commit 3bfdc63936dd ("rtmutex: Use waiter::task instead of current in remove_waiter()") and was backported to stable trees on 2026-05-04, so a current, updated distribution kernel already carries it. Exposure is broad in principle — the bug reaches any kernel from 2.6.39-rc1 onward with CONFIG_FUTEX_PI=y, effectively every distribution build — so the real question is not "am I affected" but "am I patched."
uname -r # your running kernel
# then check your distro advisory for CVE-2026-43499;
# patched if you are on a stable kernel updated after 2026-05-04
Two defense-in-depth notes from the writeup, neither a substitute for patching. RANDOMIZE_KSTACK_OFFSET randomizes the stack offset per syscall and breaks the deterministic frame overlap the exploit relies on — only about five bits, roughly a 1-in-32 chance, but not nothing. STATIC_USERMODE_HELPER closes the specific /proc/sys route to root used here, though not the underlying bug. And the honest caveat on the numbers: the fifteen-year lifetime and the version details come from a single vendor writeup that is part II of a series. The mechanism is specific and checkable against the kernel tree, so I trust it; treat the surrounding figures as that source's account rather than independently confirmed.
References
현재 단락 (1/28)
On 2026-07-07 a writeup titled "IonStack part II" appeared on nebusec.ai and made the rounds on Hack...