[tip: core/urgent] entry/rseq: Fix hard lockup on granted time slice extension

From: tip-bot2 for Niels Pressel

Date: Thu Aug 06 2026 - 08:53:39 EST


The following commit has been merged into the core/urgent branch of tip:

Commit-ID: f81845889e128d2e5f8f2f38eb7339cc74640f4f
Gitweb: https://git.kernel.org/tip/f81845889e128d2e5f8f2f38eb7339cc74640f4f
Author: Niels Pressel <npressel@xxxxxxx>
AuthorDate: Thu, 06 Aug 2026 13:34:29 +02:00
Committer: Peter Zijlstra <peterz@xxxxxxxxxxxxx>
CommitterDate: Thu, 06 Aug 2026 14:42:22 +02:00

entry/rseq: Fix hard lockup on granted time slice extension

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:

WARNING: ./include/linux/hrtimer_rearm.h:17 at irqentry_exit, CPU#1: slice_test

Originally, the issue was discovered because of intermittent lockups
when heavily using rseq TSEs.

Following the suggestion from Peter Zijlstra, fix this potential lockup
by reflowing __exit_to_user_mode_loop() to only enable IRQs after the
TSE check.

Fixes: 15dd3a948855 ("hrtimer: Push reprogramming timers into the interrupt return path")
Signed-off-by: Niels Pressel <npressel@xxxxxxx>
Signed-off-by: Peter Zijlstra (Intel) <peterz@xxxxxxxxxxxxx>
Link: https://patch.msgid.link/20260806113429.38333-1-npressel@xxxxxxx
---
include/linux/rseq_entry.h | 8 ++++----
kernel/entry/common.c | 10 +++++-----
2 files changed, 9 insertions(+), 9 deletions(-)

diff --git a/include/linux/rseq_entry.h b/include/linux/rseq_entry.h
index ed9da6e..772e72d 100644
--- a/include/linux/rseq_entry.h
+++ b/include/linux/rseq_entry.h
@@ -132,6 +132,8 @@ static __always_inline bool __rseq_grant_slice_extension(bool work_pending)
union rseq_slice_state state;
struct rseq __user *rseq;

+ lockdep_assert_irqs_disabled();
+
if (!rseq_slice_extension_enabled())
return false;

@@ -219,10 +221,8 @@ static __always_inline bool __rseq_grant_slice_extension(bool work_pending)
*
* which would be inconsistent state.
*/
- scoped_guard(irq) {
- clear_tsk_need_resched(curr);
- clear_preempt_need_resched();
- }
+ clear_tsk_need_resched(curr);
+ clear_preempt_need_resched();
return true;

efault:
diff --git a/kernel/entry/common.c b/kernel/entry/common.c
index e3d381f..e7dae46 100644
--- a/kernel/entry/common.c
+++ b/kernel/entry/common.c
@@ -47,13 +47,13 @@ static __always_inline unsigned long __exit_to_user_mode_loop(struct pt_regs *re
* items have been completed.
*/
while (ti_work & EXIT_TO_USER_MODE_WORK_LOOP) {
-
+ /* Check rseq slice extensions with IRQs disabled */
+ bool sched = (ti_work & (_TIF_NEED_RESCHED | _TIF_NEED_RESCHED_LAZY)) &&
+ !rseq_grant_slice_extension(ti_work, TIF_SLICE_EXT_DENY);
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 (sched)
+ schedule();

if (ti_work & _TIF_UPROBE)
uprobe_notify_resume(regs);