* [PATCH v2 0/2] futex/requeue: Fix requeue PI races
@ 2026-07-22 8:51 Yao Kai
2026-07-22 8:51 ` [PATCH v2 1/2] futex/requeue: Fix rtmutex schedule preparation for requeue PI Yao Kai
2026-07-22 8:51 ` [PATCH v2 2/2] futex/requeue: Prevent rcuwait use-after-free during " Yao Kai
0 siblings, 2 replies; 5+ messages in thread
From: Yao Kai @ 2026-07-22 8:51 UTC (permalink / raw)
To: linux-kernel
Cc: tglx, mingo, peterz, dvhart, dave, andrealmeid, bigeasy,
liuyongqiang13
Hi,
This series fixes two independent issues in the requeue PI wait path.
Patch 1 restores rtmutex scheduling preparation when the temporary
removal of futex_q during requeue causes futex_do_wait() to skip
schedule(), while the proxy waiter still has to block on the target
rtmutex.
Patch 2 prevents the requeue side from accessing the waiter's
stack-based futex_q after publishing Q_REQUEUE_PI_LOCKED. The saved-task
wakeup in requeue_pi_wake_futex() also covers a waiter blocked in the
rcuwait synchronization path.
Changes since v1:
- Replace the scheduler helper split in patch 1 with
rt_mutex_pre_schedule()/rt_mutex_post_schedule() directly around
rt_mutex_wait_proxy_lock().
- Expand patch 2's comment and changelog to explain why the saved-task
wakeup covers rcuwait without losing a wakeup.
Thanks to Sebastian for the review and for suggesting the simpler fix
for patch 1.
v1:
https://lore.kernel.org/r/20260717084922.4153317-1-yaokai34@huawei.com
Testing:
- Build-tested with non-RT and PREEMPT_RT configurations.
- The original rt_mutex_schedule() warning reproducer no longer
triggers the warning.
- The original PREEMPT_RT KASAN reproducer no longer reports an
invalid access.
- The futex requeue PI selftests and stress tests pass.
Yao
Yao Kai (2):
futex/requeue: Fix rtmutex schedule preparation for requeue PI
futex/requeue: Prevent rcuwait use-after-free during requeue PI
kernel/futex/requeue.c | 20 ++++++++++++++++++--
1 file changed, 18 insertions(+), 2 deletions(-)
--
2.43.0
^ permalink raw reply [flat|nested] 5+ messages in thread* [PATCH v2 1/2] futex/requeue: Fix rtmutex schedule preparation for requeue PI 2026-07-22 8:51 [PATCH v2 0/2] futex/requeue: Fix requeue PI races Yao Kai @ 2026-07-22 8:51 ` Yao Kai 2026-08-04 12:21 ` Peter Zijlstra 2026-07-22 8:51 ` [PATCH v2 2/2] futex/requeue: Prevent rcuwait use-after-free during " Yao Kai 1 sibling, 1 reply; 5+ messages in thread From: Yao Kai @ 2026-07-22 8:51 UTC (permalink / raw) To: linux-kernel Cc: tglx, mingo, peterz, dvhart, dave, andrealmeid, bigeasy, liuyongqiang13 A waiter requeued onto a PI futex can reach rt_mutex_wait_proxy_lock() without rtmutex schedule preparation: WARNING: CPU: 0 PID: 293 at kernel/sched/core.c:7606 RIP: rt_mutex_schedule+0x43/0x50 Call Trace: rt_mutex_slowlock_block.constprop.0+0x5b/0x320 rt_mutex_wait_proxy_lock+0x3e/0x80 futex_wait_requeue_pi+0x3ba/0x590 do_futex+0x171/0x1f0 rt_mutex_schedule() requires current->sched_rt_mutex to be set. Normally, rt_mutex_pre_schedule() sets it before an rtmutex waiter can schedule. With requeue PI, another task can enqueue the waiter after its futex_q becomes visible: waiter requeue task ------ ------------ futex_wait_requeue_pi() futex_wait_setup() futex_queue(&q) futex_requeue() rt_mutex_start_proxy_lock() enqueue rt_waiter install pi_blocked_on requeue_futex() plist_del(&q->list) futex_do_wait() plist_node_empty(&q->list) skip schedule() plist_add(&q->list) futex_requeue_pi_complete() IN_PROGRESS -> DONE futex_requeue_pi_wakeup_sync() // DONE rt_mutex_wait_proxy_lock() rt_mutex_schedule() futex_do_wait() mistakes the temporary removal for a wakeup and skips schedule(). The proxy waiter can nevertheless remain blocked on the target rtmutex and subsequently enter rt_mutex_schedule() with current->sched_rt_mutex clear. Call rt_mutex_pre_schedule() and rt_mutex_post_schedule() directly around rt_mutex_wait_proxy_lock() so this second blocking point has the required scheduler preparation. Fixes: d14f9e930b90 ("locking/rtmutex: Use rt_mutex specific scheduler helpers") Suggested-by: Sebastian Andrzej Siewior <bigeasy@linutronix.de> Cc: stable@vger.kernel.org Signed-off-by: Yao Kai <yaokai34@huawei.com> --- kernel/futex/requeue.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/kernel/futex/requeue.c b/kernel/futex/requeue.c index 79823ad13683..f7889fb2fce4 100644 --- a/kernel/futex/requeue.c +++ b/kernel/futex/requeue.c @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include <linux/plist.h> +#include <linux/sched/rt.h> #include <linux/sched/signal.h> #include "futex.h" @@ -865,7 +866,14 @@ int futex_wait_requeue_pi(u32 __user *uaddr, unsigned int flags, case Q_REQUEUE_PI_DONE: /* Requeue completed. Current is 'pi_blocked_on' the rtmutex */ pi_mutex = &q.pi_state->pi_mutex; + /* + * Requeue temporarily removes q from the hash bucket, so + * futex_do_wait() may skip schedule() even though the proxy + * waiter still has to block on the rtmutex. + */ + rt_mutex_pre_schedule(); ret = rt_mutex_wait_proxy_lock(pi_mutex, to, &rt_waiter); + rt_mutex_post_schedule(); /* * See futex_unlock_pi()'s cleanup: comment. -- 2.43.0 ^ permalink raw reply related [flat|nested] 5+ messages in thread
* Re: [PATCH v2 1/2] futex/requeue: Fix rtmutex schedule preparation for requeue PI 2026-07-22 8:51 ` [PATCH v2 1/2] futex/requeue: Fix rtmutex schedule preparation for requeue PI Yao Kai @ 2026-08-04 12:21 ` Peter Zijlstra 2026-08-05 8:00 ` Yao Kai 0 siblings, 1 reply; 5+ messages in thread From: Peter Zijlstra @ 2026-08-04 12:21 UTC (permalink / raw) To: Yao Kai Cc: linux-kernel, tglx, mingo, dvhart, dave, andrealmeid, bigeasy, liuyongqiang13 On Wed, Jul 22, 2026 at 04:51:39PM +0800, Yao Kai wrote: > A waiter requeued onto a PI futex can reach rt_mutex_wait_proxy_lock() > without rtmutex schedule preparation: > > WARNING: CPU: 0 PID: 293 at kernel/sched/core.c:7606 > RIP: rt_mutex_schedule+0x43/0x50 > Call Trace: > rt_mutex_slowlock_block.constprop.0+0x5b/0x320 > rt_mutex_wait_proxy_lock+0x3e/0x80 > futex_wait_requeue_pi+0x3ba/0x590 > do_futex+0x171/0x1f0 > > rt_mutex_schedule() requires current->sched_rt_mutex to be set. Normally, > rt_mutex_pre_schedule() sets it before an rtmutex waiter can schedule. With > requeue PI, another task can enqueue the waiter after its futex_q becomes > visible: > > waiter requeue task > ------ ------------ > futex_wait_requeue_pi() > futex_wait_setup() > futex_queue(&q) > futex_requeue() > rt_mutex_start_proxy_lock() > enqueue rt_waiter > install pi_blocked_on > requeue_futex() > plist_del(&q->list) > futex_do_wait() > plist_node_empty(&q->list) > skip schedule() > plist_add(&q->list) > futex_requeue_pi_complete() > IN_PROGRESS -> DONE > futex_requeue_pi_wakeup_sync() // DONE > rt_mutex_wait_proxy_lock() > rt_mutex_schedule() > > futex_do_wait() mistakes the temporary removal for a wakeup and skips > schedule(). The proxy waiter can nevertheless remain blocked on the target > rtmutex and subsequently enter rt_mutex_schedule() with > current->sched_rt_mutex clear. > > Call rt_mutex_pre_schedule() and rt_mutex_post_schedule() directly around > rt_mutex_wait_proxy_lock() so this second blocking point has the required > scheduler preparation. > > Fixes: d14f9e930b90 ("locking/rtmutex: Use rt_mutex specific scheduler helpers") > Suggested-by: Sebastian Andrzej Siewior <bigeasy@linutronix.de> > Cc: stable@vger.kernel.org > Signed-off-by: Yao Kai <yaokai34@huawei.com> > --- > kernel/futex/requeue.c | 8 ++++++++ > 1 file changed, 8 insertions(+) > > diff --git a/kernel/futex/requeue.c b/kernel/futex/requeue.c > index 79823ad13683..f7889fb2fce4 100644 > --- a/kernel/futex/requeue.c > +++ b/kernel/futex/requeue.c > @@ -1,6 +1,7 @@ > // SPDX-License-Identifier: GPL-2.0-or-later > > #include <linux/plist.h> > +#include <linux/sched/rt.h> > #include <linux/sched/signal.h> > > #include "futex.h" > @@ -865,7 +866,14 @@ int futex_wait_requeue_pi(u32 __user *uaddr, unsigned int flags, > case Q_REQUEUE_PI_DONE: > /* Requeue completed. Current is 'pi_blocked_on' the rtmutex */ > pi_mutex = &q.pi_state->pi_mutex; > + /* > + * Requeue temporarily removes q from the hash bucket, so > + * futex_do_wait() may skip schedule() even though the proxy > + * waiter still has to block on the rtmutex. > + */ > + rt_mutex_pre_schedule(); > ret = rt_mutex_wait_proxy_lock(pi_mutex, to, &rt_waiter); > + rt_mutex_post_schedule(); As per always, I'm totally confused about everything. I mean, futexes suck, but requeue sucks worse. So the purpose of rt_mutex_pre_schedule() was to avoid the double waiter enqueue for rt_mutex on RT, where sched_submit_work() will hit a spinlock-nee-rtlock. So rt_mutex_pre_schedule() must happen before the rt_mutex is added as a waiter. However, AFAICT we're already a waiter at the above spot, no? So this cannot be right. The changelogs doesn't at all explain why this is correct. Please help? ^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH v2 1/2] futex/requeue: Fix rtmutex schedule preparation for requeue PI 2026-08-04 12:21 ` Peter Zijlstra @ 2026-08-05 8:00 ` Yao Kai 0 siblings, 0 replies; 5+ messages in thread From: Yao Kai @ 2026-08-05 8:00 UTC (permalink / raw) To: Peter Zijlstra Cc: linux-kernel, tglx, mingo, dvhart, dave, andrealmeid, bigeasy, liuyongqiang13 On 8/4/2026 8:21 PM, Peter Zijlstra wrote: > On Wed, Jul 22, 2026 at 04:51:39PM +0800, Yao Kai wrote: >> A waiter requeued onto a PI futex can reach rt_mutex_wait_proxy_lock() >> without rtmutex schedule preparation: >> >> WARNING: CPU: 0 PID: 293 at kernel/sched/core.c:7606 >> RIP: rt_mutex_schedule+0x43/0x50 >> Call Trace: >> rt_mutex_slowlock_block.constprop.0+0x5b/0x320 >> rt_mutex_wait_proxy_lock+0x3e/0x80 >> futex_wait_requeue_pi+0x3ba/0x590 >> do_futex+0x171/0x1f0 >> >> rt_mutex_schedule() requires current->sched_rt_mutex to be set. Normally, >> rt_mutex_pre_schedule() sets it before an rtmutex waiter can schedule. With >> requeue PI, another task can enqueue the waiter after its futex_q becomes >> visible: >> >> waiter requeue task >> ------ ------------ >> futex_wait_requeue_pi() >> futex_wait_setup() >> futex_queue(&q) >> futex_requeue() >> rt_mutex_start_proxy_lock() >> enqueue rt_waiter >> install pi_blocked_on >> requeue_futex() >> plist_del(&q->list) >> futex_do_wait() >> plist_node_empty(&q->list) >> skip schedule() >> plist_add(&q->list) >> futex_requeue_pi_complete() >> IN_PROGRESS -> DONE >> futex_requeue_pi_wakeup_sync() // DONE >> rt_mutex_wait_proxy_lock() >> rt_mutex_schedule() >> >> futex_do_wait() mistakes the temporary removal for a wakeup and skips >> schedule(). The proxy waiter can nevertheless remain blocked on the target >> rtmutex and subsequently enter rt_mutex_schedule() with >> current->sched_rt_mutex clear. >> >> Call rt_mutex_pre_schedule() and rt_mutex_post_schedule() directly around >> rt_mutex_wait_proxy_lock() so this second blocking point has the required >> scheduler preparation. >> >> Fixes: d14f9e930b90 ("locking/rtmutex: Use rt_mutex specific scheduler helpers") >> Suggested-by: Sebastian Andrzej Siewior <bigeasy@linutronix.de> >> Cc: stable@vger.kernel.org >> Signed-off-by: Yao Kai <yaokai34@huawei.com> >> --- >> kernel/futex/requeue.c | 8 ++++++++ >> 1 file changed, 8 insertions(+) >> >> diff --git a/kernel/futex/requeue.c b/kernel/futex/requeue.c >> index 79823ad13683..f7889fb2fce4 100644 >> --- a/kernel/futex/requeue.c >> +++ b/kernel/futex/requeue.c >> @@ -1,6 +1,7 @@ >> // SPDX-License-Identifier: GPL-2.0-or-later >> >> #include <linux/plist.h> >> +#include <linux/sched/rt.h> >> #include <linux/sched/signal.h> >> >> #include "futex.h" >> @@ -865,7 +866,14 @@ int futex_wait_requeue_pi(u32 __user *uaddr, unsigned int flags, >> case Q_REQUEUE_PI_DONE: >> /* Requeue completed. Current is 'pi_blocked_on' the rtmutex */ >> pi_mutex = &q.pi_state->pi_mutex; >> + /* >> + * Requeue temporarily removes q from the hash bucket, so >> + * futex_do_wait() may skip schedule() even though the proxy >> + * waiter still has to block on the rtmutex. >> + */ >> + rt_mutex_pre_schedule(); >> ret = rt_mutex_wait_proxy_lock(pi_mutex, to, &rt_waiter); >> + rt_mutex_post_schedule(); > > As per always, I'm totally confused about everything. I mean, futexes > suck, but requeue sucks worse. > > So the purpose of rt_mutex_pre_schedule() was to avoid the double waiter > enqueue for rt_mutex on RT, where sched_submit_work() will hit a > spinlock-nee-rtlock. > > So rt_mutex_pre_schedule() must happen before the rt_mutex is added as a > waiter. However, AFAICT we're already a waiter at the above spot, no? So > this cannot be right. > > The changelogs doesn't at all explain why this is correct. Please help? You are right that rt_mutex_pre_schedule() is normally required before the rtmutex waiter is enqueued. The v2 placement relies on a property of this particular call path that I failed to explain in the changelog: futex_wait_requeue_pi() is reachable only via the FUTEX_WAIT_REQUEUE_PI syscall from userspace. Therefore, 'current' cannot be a workqueue/io-wq worker, nor can it carry a live block plug across the syscall boundary. Consequently, sched_submit_work() inside rt_mutex_pre_schedule() has no pending plugged I/O or worker notifications to process here, so it cannot recurse into an rtlock after the proxy waiter has been installed. In this specific path, calling rt_mutex_pre_schedule() here effectively only sets current->sched_rt_mutex before rt_mutex_wait_proxy_lock() calls rt_mutex_schedule(), without executing any blocking submit_work after enqueue. This was the rationale for adopting Sebastian's simpler suggestion from the v1 discussion, but this essential invariant was omitted from the v2 changelog and code comments. Thanks, Yao Kai ^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH v2 2/2] futex/requeue: Prevent rcuwait use-after-free during requeue PI 2026-07-22 8:51 [PATCH v2 0/2] futex/requeue: Fix requeue PI races Yao Kai 2026-07-22 8:51 ` [PATCH v2 1/2] futex/requeue: Fix rtmutex schedule preparation for requeue PI Yao Kai @ 2026-07-22 8:51 ` Yao Kai 1 sibling, 0 replies; 5+ messages in thread From: Yao Kai @ 2026-07-22 8:51 UTC (permalink / raw) To: linux-kernel Cc: tglx, mingo, peterz, dvhart, dave, andrealmeid, bigeasy, liuyongqiang13 On PREEMPT_RT, FUTEX_CMP_REQUEUE_PI can trigger a KASAN report: BUG: KASAN: slab-out-of-bounds in _raw_spin_lock_irqsave+0x76/0xe0 Call Trace: _raw_spin_lock_irqsave+0x76/0xe0 try_to_wake_up+0xab/0x1540 rcuwait_wake_up+0x39/0x60 futex_requeue+0x18c3/0x1e10 The futex_q used by futex_wait_requeue_pi() is allocated on the waiter's stack. An early wakeup can race with a PI requeue as follows: waiter requeue task ------ ------------ futex_wait_requeue_pi() futex_do_wait() schedule() * timeout/signal wakes waiter * futex_requeue_pi_wakeup_sync() IN_PROGRESS -> WAIT rcuwait_wait_event() requeue_pi_wake_futex() task = READ_ONCE(q->task) futex_requeue_pi_complete() WAIT -> LOCKED return LOCKED return // q lifetime ends rcuwait_wake_up() futex_requeue_pi_complete() publishes LOCKED before calling rcuwait_wake_up(). Once the waiter observes LOCKED, it can return from futex_wait_requeue_pi() and let q go out of scope before rcuwait_wake_up() reads q->requeue_wait.task and passes the stale pointer to try_to_wake_up(). Skip rcuwait_wake_up() for Q_REQUEUE_PI_LOCKED. This state is only published by requeue_pi_wake_futex(), which saves q->task before futex_requeue_pi_complete() and calls wake_up_state(task, TASK_NORMAL) afterwards. If the waiter is already blocked in rcuwait_wait_event(), TASK_NORMAL includes TASK_UNINTERRUPTIBLE and wakes it. If the wakeup runs before the waiter blocks, the waiter observes LOCKED and does not schedule. The other completion states retain their rcuwait wakeup. Fixes: 07d91ef510fb1 ("futex: Prevent requeue_pi() lock nesting issue on RT") Cc: stable@vger.kernel.org Signed-off-by: Yao Kai <yaokai34@huawei.com> --- kernel/futex/requeue.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/kernel/futex/requeue.c b/kernel/futex/requeue.c index f7889fb2fce4..9b32f19f320a 100644 --- a/kernel/futex/requeue.c +++ b/kernel/futex/requeue.c @@ -155,8 +155,16 @@ static inline void futex_requeue_pi_complete(struct futex_q *q, int locked) } while (!atomic_try_cmpxchg(&q->requeue_state, &old, new)); #ifdef CONFIG_PREEMPT_RT - /* If the waiter interleaved with the requeue let it know */ - if (unlikely(old == Q_REQUEUE_PI_WAIT)) + /* + * If the waiter interleaved with the requeue, let it know. For LOCKED, + * q may be invalid as soon as the state is published. Only + * requeue_pi_wake_futex() publishes LOCKED; it saves q->task before + * futex_requeue_pi_complete() and follows it with + * wake_up_state(TASK_NORMAL), which also wakes the TASK_UNINTERRUPTIBLE + * rcuwait waiter. + */ + if (unlikely(old == Q_REQUEUE_PI_WAIT) && + new != Q_REQUEUE_PI_LOCKED) rcuwait_wake_up(&q->requeue_wait); #endif } -- 2.43.0 ^ permalink raw reply related [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-08-05 8:00 UTC | newest] Thread overview: 5+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-07-22 8:51 [PATCH v2 0/2] futex/requeue: Fix requeue PI races Yao Kai 2026-07-22 8:51 ` [PATCH v2 1/2] futex/requeue: Fix rtmutex schedule preparation for requeue PI Yao Kai 2026-08-04 12:21 ` Peter Zijlstra 2026-08-05 8:00 ` Yao Kai 2026-07-22 8:51 ` [PATCH v2 2/2] futex/requeue: Prevent rcuwait use-after-free during " Yao Kai
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox