[PATCH] futex/requeue: Clean up requeue state on the self-deadlock path
From: Michael Bommarito
Date: Sun Jun 28 2026 - 22:01:09 EST
futex_requeue() can reach the self-deadlock check after get_pi_state()
has taken a reference for the waiter and futex_requeue_pi_prepare() has
set this->pi_state and moved the waiter's requeue_state to
Q_REQUEUE_PI_IN_PROGRESS. The check returns -EDEADLK and breaks out of
the requeue loop without undoing any of that, unlike the sibling
rt_mutex_start_proxy_lock() failure path which clears this->pi_state,
drops the reference with put_pi_state() and calls
futex_requeue_pi_complete().
The extra pi_state reference is therefore leaked (the post-loop
put_pi_state() only balances the initial reference taken in
futex_proxy_trylock_atomic()), and the waiter is left in
Q_REQUEUE_PI_IN_PROGRESS. When that waiter is next woken on the source
futex it enters futex_requeue_pi_wakeup_sync() and waits for a requeue
completion that never comes, since futex_requeue_pi_complete() was
skipped; on a !PREEMPT_RT kernel this is an unkillable busy-spin that
pins a CPU. The path is reachable by an unprivileged FUTEX_CMP_REQUEUE_PI
that requeues a non-top waiter which already owns the target PI futex.
Make the -EDEADLK path perform the same cleanup as the
rt_mutex_start_proxy_lock() deadlock path.
Fixes: 74e144274af3 ("futex/requeue: Prevent NULL pointer dereference in remove_waiter() on self-deadlock")
Cc: stable@xxxxxxxxxxxxxxx
Assisted-by: Claude:claude-opus-4-8
Signed-off-by: Michael Bommarito <michael.bommarito@xxxxxxxxx>
---
Built x86_64 with W=1, no new warnings; checkpatch --strict clean.
Reproduced with an unprivileged user program under QEMU/KVM: the
triggering FUTEX_CMP_REQUEUE_PI leaves the requeued owner spinning
permanently in futex_requeue_pi_wakeup_sync() (requeue_state stuck at
Q_REQUEUE_PI_WAIT) and kmemleak reports the leaked futex_pi_state. With
this patch the requeue still returns -EDEADLK, but the waiter no longer
wedges and kmemleak is clean. The futex selftests pass and are unchanged
from the unpatched kernel.
kernel/futex/requeue.c | 3 +++
1 file changed, 3 insertions(+)
diff --git a/kernel/futex/requeue.c b/kernel/futex/requeue.c
index 7384672916fb6..73ca06ec32454 100644
--- a/kernel/futex/requeue.c
+++ b/kernel/futex/requeue.c
@@ -648,6 +648,9 @@ int futex_requeue(u32 __user *uaddr1, unsigned int flags1,
/* Self-deadlock: non-top waiter already owns the PI futex. */
if (rt_mutex_owner(&pi_state->pi_mutex) == this->task) {
ret = -EDEADLK;
+ this->pi_state = NULL;
+ put_pi_state(pi_state);
+ futex_requeue_pi_complete(this, ret);
break;
}
base-commit: f24ca6729076623c9a0547ecc71e4fc1c4b65c3c
--
2.53.0