Re: [PATCH] rseq: fix hard lockup on granted time slice extension

From: Peter Zijlstra

Date: Mon Aug 03 2026 - 03:25:07 EST


On Sun, Aug 02, 2026 at 02:44:23PM +0200, Niels Pressel wrote:
> In __exit_to_user_mode_loop, TSE eligibility is checked while
> IRQs are enabled. Granting a TSE might involve rearming the
> hrtimers. However, hrtimer_rearm_deferred_tif is expected to be
> called with IRQs disabled (see include/linux/hrtimer_rearm.h:17).
>
> Calling the function with IRQs enabled can lead to a hard lockup
> because __hrtimer_rearm_deferred acquires a raw spinlock (without
> disabling IRQs) that is also acquired in hard IRQ context within
> hrtimer_run_queues.
>
> Lockdep flags the issue when running the rseq selftests on the
> 7.2-rc5 release:

>
> Originally, the issue was discovered because of intermittent lockups
> when heavily using rseq TSEs.
>
> Fix this potential lockup by disabling IRQs around the timer rearm function
> call. Tested the fix using the rseq selftests.
>
> Fixes: 15dd3a948855 ("hrtimer: Push reprogramming timers into the interrupt return path")
> Signed-off-by: Niels Pressel <npressel@xxxxxxx>
> ---
> include/linux/rseq_entry.h | 1 +
> 1 file changed, 1 insertion(+)
>
> diff --git a/include/linux/rseq_entry.h b/include/linux/rseq_entry.h
> index ed9da6e41a2a..31ce349ed42c 100644
> --- a/include/linux/rseq_entry.h
> +++ b/include/linux/rseq_entry.h
> @@ -233,6 +233,7 @@ static __always_inline bool __rseq_grant_slice_extension(bool work_pending)
> static __always_inline bool rseq_grant_slice_extension(unsigned long ti_work, unsigned long mask)
> {
> if (unlikely(__rseq_grant_slice_extension(ti_work & mask))) {
> + guard(irq)();
> hrtimer_rearm_deferred_tif(ti_work);
> return true;
> }

Argh!

Thomas, previously we would call hrtimer_rearm_deferred() before
re-enabling IRQs, but here it slipped past. And while disabling it will
cure the splat, I'm thinking it makes sense to reflow
__exit_to_user_mode_loop() to instead delay enabling IRQs.

Something a little like the below, but let me go find more wake-up juice
and double check.

---
kernel/entry/common.c | 11 ++++++-----
1 file changed, 6 insertions(+), 5 deletions(-)

diff --git a/kernel/entry/common.c b/kernel/entry/common.c
index e3d381fd3d25..f8c9216cbcea 100644
--- a/kernel/entry/common.c
+++ b/kernel/entry/common.c
@@ -48,11 +48,12 @@ static __always_inline unsigned long __exit_to_user_mode_loop(struct pt_regs *re
*/
while (ti_work & EXIT_TO_USER_MODE_WORK_LOOP) {

- local_irq_enable();
-
- if (ti_work & (_TIF_NEED_RESCHED | _TIF_NEED_RESCHED_LAZY)) {
- if (!rseq_grant_slice_extension(ti_work, TIF_SLICE_EXT_DENY))
- schedule();
+ if ((ti_work & (_TIF_NEED_RESCHED | _TIF_NEED_RESCHED_LAZY)) &&
+ (!rseq_grant_slice_extension(ti_work, TIF_SLICE_EXT_DENY))) {
+ local_irq_enable();
+ schedule();
+ } else {
+ local_irq_enable();
}

if (ti_work & _TIF_UPROBE)