[PATCH RFC] net/sched: taprio: fix advance_sched() RCU stalls and rbtree corruption

"syzbot" <[email protected]>
Newsgroups dev.linux.lists.syzbot
Message-ID <[email protected]>
An analysis of crash reports reveals two distinct but related issues in
taprio's advance_sched() hrtimer callback:

1. hrtimer rbtree corruption:
The original code dropped q->current_entry_lock before calling
hrtimer_set_expires(). This allowed taprio_change() to concurrently acquire
the lock and call hrtimer_start(), enqueuing the timer. advance_sched()
would then modify the expiration time of an actively enqueued timer,
corrupting the hrtimer rbtree. This leads to an infinite loop in
rb_erase_linked() during hrtimer removal, causing an RCU stall:

rcu: INFO: rcu_preempt detected stalls on CPUs/tasks:
rcu: 	0-...!: (1 GPs behind) idle=b8e4/1/0x4000000000000000
softirq=17061/17077 fqs=0
rcu: 	(detected by 1, t=10505 jiffies, g=11761, q=1458 ncpus=2)
Sending NMI from CPU 1 to CPUs 0:
NMI backtrace for cpu 0
...
Call Trace:
 <IRQ>
 rb_erase_linked+0x159/0x190 lib/rbtree.c:460
 timerqueue_linked_del include/linux/timerqueue.h:66 [inline]
 __remove_hrtimer+0xf7/0x3e0 kernel/time/hrtimer.c:1155
 __run_hrtimer kernel/time/hrtimer.c:1910 [inline]
 __hrtimer_run_queues+0x299/0xa20 kernel/time/hrtimer.c:1994
 hrtimer_interrupt+0x44b/0x950 kernel/time/hrtimer.c:2113
 local_apic_timer_interrupt arch/x86/kernel/apic/apic.c:1050 [inline]
 __sysvec_apic_timer_interrupt+0x102/0x430 arch/x86/kernel/apic/apic.c:1067
 instr_sysvec_apic_timer_interrupt arch/x86/kernel/apic/apic.c:1061
 [inline]
 sysvec_apic_timer_interrupt+0xa1/0xc0 arch/x86/kernel/apic/apic.c:1061
 </IRQ>

2. Catch-up loop RCU stall:
If the timer fires late (e.g. due to system overload or a paused VM),
advance_sched() calculates an end_time that is still in the past. It
returns HRTIMER_RESTART, causing the hrtimer core to immediately dequeue
and re-run the callback. Because advance_sched() only advances time by one
interval per iteration, a large delay combined with a small interval causes
the callback to loop millions of times in hardirq context. This monopolizes
the CPU and triggers the RCU stall detector.

To fix the rbtree corruption, keep hrtimer_set_expires() inside the lock
and conditionally update it only if !hrtimer_is_queued().

To fix the catch-up loop, mathematically skip missed cycles. When
advance_sched() detects that end_time is in the past, it uses div64_s64()
to calculate how many full cycles were missed and advances
oper->cycle_end_time, end_time, next->end_time, and next->gate_close_time
accordingly. This ensures that advance_sched() catches up to the present
time in at most num_entries iterations, completely eliminating the RCU
stall while preserving the correct schedule state.

Fixes: a3d43c0d56f1 ("taprio: Add support adding an admin schedule")
Assisted-by: Gemini:gemini-3.1-pro-preview syzbot
Reported-by: [email protected]
Closes: https://syzkaller.appspot.com/bug?extid=f8ccdac0a7eede0e1ae5
Link: https://syzkaller.appspot.com/ai_job?id=cc1516be-6e82-4a99-9529-5491b7e7d037
To: "David S. Miller" <[email protected]>
To: "Eric Dumazet" <[email protected]>
To: "Jamal Hadi Salim" <[email protected]>
To: "Jiri Pirko" <[email protected]>
To: "Jakub Kicinski" <[email protected]>
To: <[email protected]>
To: "Paolo Abeni" <[email protected]>
To: "Vinicius Costa Gomes" <[email protected]>
Cc: "Simon Horman" <[email protected]>
Cc: <[email protected]>

---
diff --git a/net/sched/sch_taprio.c b/net/sched/sch_taprio.c
index 45245157e..26258b07f 100644
--- a/net/sched/sch_taprio.c
+++ b/net/sched/sch_taprio.c
@@ -984,10 +984,37 @@ static enum hrtimer_restart advance_sched(struct hrtimer *timer)
 	taprio_set_budgets(q, oper, next);
 
 first_run:
+	/* Skip missed cycles */
+	if (oper->cycle_time > 0) {
+		ktime_t now = taprio_get_time(q);
+		if (ktime_before(end_time, now)) {
+			s64 n = div64_s64(ktime_sub(now, end_time),
+					  oper->cycle_time);
+			if (n > 0) {
+				ktime_t add = n * oper->cycle_time;
+				oper->cycle_end_time =
+					ktime_add_ns(oper->cycle_end_time, add);
+				end_time = ktime_add_ns(end_time, add);
+				next->end_time =
+					ktime_add_ns(next->end_time, add);
+				for (tc = 0; tc < num_tc; tc++) {
+					if (next->gate_close_time[tc] !=
+					    KTIME_MAX)
+						next->gate_close_time
+							[tc] = ktime_add_ns(
+							next->gate_close_time[tc],
+							add);
+				}
+			}
+		}
+	}
+
 	rcu_assign_pointer(q->current_entry, next);
-	spin_unlock(&q->current_entry_lock);
 
-	hrtimer_set_expires(&q->advance_timer, end_time);
+	if (!hrtimer_is_queued(&q->advance_timer))
+		hrtimer_set_expires(&q->advance_timer, end_time);
+
+	spin_unlock(&q->current_entry_lock);
 
 	rcu_read_lock();
 	__netif_schedule(sch);


base-commit: 4549871118cf616eecdd2d939f78e3b9e1dddc48
-- 
This is an AI-generated patch subject to moderation.
Reply with '#syz upstream' to Sign-off the patch as a human author
and send it to the upstream kernel mailing lists.
Reply with '#syz reject' to reject it ('#syz unreject' to undo).

See https://goo.gle/syzbot-ai-patches for information about AI-generated patches.
You can comment on the patch as usual, syzbot will try to address
the comments and send a new version of the patch if necessary.
syzbot engineers can be reached at [email protected].
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.