War Stories: Locking and Synchronization Bugs
Three CVEs and one origin story, all from the same corner of the kernel — rt_mutex and PI-futexes, the priority-inheritance machinery that keeps a high-priority task from starving behind a low-priority lock holder, and the three separate ways getting that machinery's bookkeeping wrong has gone badly
All three CVEs here live in the same subsystem — priority-inheritance futexes and the rt_mutex they're built on. Two of the three are the mirror image of each other: rt_mutex has to keep several pieces of state (a userspace futex word, pi_state, the mutex's own owner field) consistent across code paths that don't all touch every piece at once, and one bug left that state inconsistent after an unhandled fault while the other missed a precondition check its sibling function already had. The third CVE is a different kind of mistake entirely — a lock-discipline violation, not a state-consistency one. The fourth page isn't a bug at all: it's why any of this machinery exists.
Incidents
Ordered reverse chronologically by when the fix (or, for the origin story, the original merge) landed in mainline — newest first.
The Deadlock Detector That Scheduled While Atomic
Linux 6.11 (September 2024) · CVE-2024-46829
rt_mutex's own cycle detector confirmed a genuine deadlock and responded by warning and then looping forever calling schedule() — while still holding the raw spinlock every other waiter on the same lock needed to make progress.
The PI-Futex Fixup That Had No Answer for a Permanent Fault
Linux 5.11 (February 2021) · CVE-2021-3347 When the kernel couldn't write a new owner's TID back into a PI futex word, it gave up without reconciling its own rt_mutex and pi_state — and a subsequent unlock on that mismatch corrupted a waiter structure still resident on another task's kernel stack.
Towelroot: The Missing Check on the Requeuer's Half of the Pair
Linux 3.15 (June 2014) · CVE-2014-3153
futex_requeue() never checked that a PI-requeue's source and destination were different futexes — the identical check on the sibling function, added two years earlier for an unrelated crash, hadn't been enough to catch it.
The PI-Mutex Origin Story
Linux 2.6.18 (September 2006) · not a CVE A low-priority lock holder can be starved by a medium-priority task that has nothing to do with the lock at all — priority-inheritance mutexes exist because a plain mutex has no way to stop this, and it took a blunt public objection from Linus Torvalds before the kernel carried a mainline implementation anyway.
Common threads
| Pattern | Deadlock detector | PI-futex fixup | Towelroot | PI-mutex origin |
|---|---|---|---|---|
| Involves rt_mutex/PI-futex bookkeeping specifically | Yes | Yes | Yes | Yes (defines it) |
| Root cause: a lock held across a call that shouldn't happen while holding it | Yes | No | No | — |
| Root cause: state left inconsistent after an unhandled failure path | No | Yes | No | — |
| Root cause: a fix applied to one function, needed on its untouched sibling | No | No | Yes | — |
| Years between the bug's introduction and its fix | 10 (2014→2024) | 13 (2008→2021) | ~5 (2009→2014) | — |
| CISA KEV-listed | No | No | Yes (May 2022) | — |
| Public exploit tool published | No | No | Yes (Towelroot) | — |
Two of the three CVEs are the same shape of mistake, from opposite directions. The PI-futex fixup bug is a fault-handling path that left cross-referencing state inconsistent because the "permanent failure" branch was never written. Towelroot is a missing precondition check that had been written — just on the wrong half of a two-function pair. Both are failures to fully generalize a fix: the 2008 retry logic generalized "the fault will eventually resolve" without a fallback; the 2012 trinity-driven fix generalized "check uaddr != uaddr2" to exactly one function instead of the pair that shared the invariant.
The deadlock-detector bug is the odd one out: a lock-discipline violation in error-handling code, not a state-consistency bug. Unlike the other two, nothing about what the code was tracking was wrong — the mistake was scheduling with a spinlock held, in a branch whose entire job was to report that no further progress was possible. It's also the only one of the three not tied to PI-futexes at all: CONFIG_DEBUG_RT_MUTEXES=y kernels can hit it through any rt_mutex, not just the futex path.
None of the three CVEs here have a public exploit tool except Towelroot — and Towelroot's is one of the most consequential local root exploits the kernel has ever shipped. Read alongside the origin story, there's a certain irony: the mechanism Torvalds worried was "your system is broken anyway" territory became, about eight years later, the exact subsystem whose bookkeeping mistakes produced the highest-impact bug on this page.
See also
- Locking Overview — spinlocks, mutexes, rt_mutex, and RCU
- VFS War Stories — a comparable set of local-privilege CVEs from a different subsystem, useful for contrasting root-cause shapes
- GPU/DRM War Stories — reliability-only incidents (no CVEs) from a different kind of shared kernel-internal scheduling problem