[PATCH RFC] net/sched: taprio: prevent infinite loop in advance_sched()

"syzbot" <[email protected]> Sun, 2 Aug 2026 22:41:40 +0000 (UTC)
Newsgroups dev.linux.lists.syzbot
Message-ID <[email protected]>
When hardware offload is not used, the taprio qdisc uses a software hrtimer
(`q->advance_timer`) to advance its schedule. The timer callback,
`advance_sched()`, calculates the next expiration time by adding the
schedule entry's interval to the current entry's end time.

If a schedule is configured with an extremely small interval (e.g., 1
microsecond), the execution time of `advance_sched()` and the surrounding
hrtimer interrupt overhead can exceed the interval itself. When this
happens, the newly programmed expiration time is already in the past. The
hrtimer subsystem (`__hrtimer_run_queues()`) then attempts to catch up by
repeatedly invoking the timer callback. Because each invocation takes
longer than the tiny interval, the catch-up process diverges, leading to an
infinite loop that starves the CPU and triggers an RCU stall:

rcu: INFO: rcu_preempt detected stalls on CPUs/tasks:
rcu: 	1-...!: (1 GPs behind) idle=c50c/1/0x4000000000000000
softirq=21413/21531 fqs=770
Call Trace:
 <IRQ>
 advance_sched+0xe4/0xc80 net/sched/sch_taprio.c:932
 __run_hrtimer kernel/time/hrtimer.c:2032 [inline]
 __hrtimer_run_queues+0x3bc/0xa10 kernel/time/hrtimer.c:2096
 hrtimer_interrupt+0x448/0x910 kernel/time/hrtimer.c:2215
 local_apic_timer_interrupt arch/x86/kernel/apic/apic.c:1051 [inline]
 __sysvec_apic_timer_interrupt+0x102/0x430 arch/x86/kernel/apic/apic.c:1068
 instr_sysvec_apic_timer_interrupt arch/x86/kernel/apic/apic.c:1062
 [inline]
 sysvec_apic_timer_interrupt+0xa1/0xc0 arch/x86/kernel/apic/apic.c:1062
 </IRQ>

To fix this, implement two complementary solutions. First, enforce a
reasonable minimum interval for software scheduling. Introduce
`TAPRIO_MIN_SW_INTERVAL` (50 us) and enforce it in `fill_sched_entry()` and
`parse_taprio_schedule()` when hardware offload is not enabled. This
prevents configuring schedules that software hrtimers cannot reliably
maintain. Second, advance the schedule mathematically on overruns. If the
newly calculated `end_time` is still in the past relative to the current
time, calculate the number of missed cycles and advance the expiration
times mathematically. This breaks the infinite loop by skipping missed
cycles and scheduling the next timer expiration in the future, ensuring the
system can recover gracefully even if an overrun occurs due to heavy system
load.

Fixes: 5a781ccbd19e ("tc: Add support for configuring the taprio scheduler")
Assisted-by: Gemini:gemini-3.5-flash Gemini:gemini-3.1-pro-preview syzbot
Reported-by: [email protected]
Closes: https://syzkaller.appspot.com/bug?extid=8785aaf121cfb2141e0d
Link: https://syzkaller.appspot.com/ai_job?id=8f74a8d2-7a9d-4fb5-96f1-392245f3cf7f
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 299234a5f..64600f3bc 100644
--- a/net/sched/sch_taprio.c
+++ b/net/sched/sch_taprio.c
@@ -40,6 +40,7 @@ static struct static_key_false taprio_have_working_mqprio;
 
 #define TXTIME_ASSIST_IS_ENABLED(flags) ((flags) & TCA_TAPRIO_ATTR_FLAG_TXTIME_ASSIST)
 #define FULL_OFFLOAD_IS_ENABLED(flags) ((flags) & TCA_TAPRIO_ATTR_FLAG_FULL_OFFLOAD)
+#define TAPRIO_MIN_SW_INTERVAL 50000 /* 50 us */
 #define TAPRIO_SUPPORTED_FLAGS \
 	(TCA_TAPRIO_ATTR_FLAG_TXTIME_ASSIST | TCA_TAPRIO_ATTR_FLAG_FULL_OFFLOAD)
 #define TAPRIO_FLAGS_INVALID U32_MAX
@@ -980,10 +981,31 @@ static enum hrtimer_restart advance_sched(struct hrtimer *timer)
 		end_time = next->end_time;
 	}
 
+first_run:
+	/* Check if we fell behind and advance the schedule mathematically */
+	if (unlikely(ktime_after(taprio_get_time(q), end_time))) {
+		ktime_t now = taprio_get_time(q);
+		ktime_t cycle = oper->cycle_time;
+		s64 n;
+
+		if (likely(cycle)) {
+			n = div64_s64(ktime_sub_ns(now, end_time), cycle);
+			end_time = ktime_add_ns(end_time, (n + 1) * cycle);
+			oper->cycle_end_time = ktime_add_ns(oper->cycle_end_time,
+							    (n + 1) * cycle);
+
+			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],
+							     (n + 1) * cycle);
+			}
+		}
+	}
+
 	next->end_time = end_time;
 	taprio_set_budgets(q, oper, next);
 
-first_run:
 	rcu_assign_pointer(q->current_entry, next);
 	spin_unlock(&q->current_entry_lock);
 
@@ -1061,6 +1083,11 @@ static int fill_sched_entry(struct taprio_sched *q, struct nlattr **tb,
 		return -EINVAL;
 	}
 
+	if (!FULL_OFFLOAD_IS_ENABLED(q->flags) && interval < TAPRIO_MIN_SW_INTERVAL) {
+		NL_SET_ERR_MSG_MOD(extack, "Interval too small for software scheduling");
+		return -EINVAL;
+	}
+
 	entry->interval = interval;
 
 	return 0;
@@ -1171,6 +1198,12 @@ static int parse_taprio_schedule(struct taprio_sched *q, struct nlattr **tb,
 		return -EINVAL;
 	}
 
+	if (!FULL_OFFLOAD_IS_ENABLED(q->flags) &&
+	    new->cycle_time < new->num_entries * TAPRIO_MIN_SW_INTERVAL) {
+		NL_SET_ERR_MSG_MOD(extack, "'cycle_time' is too small for software scheduling");
+		return -EINVAL;
+	}
+
 	taprio_calculate_gate_durations(q, new);
 
 	return 0;


base-commit: f5098b6bae761e346ebcd9da7f95622c04733cff
-- 
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].