From: Uladzislau Zhauniarovich <[email protected]>

The interval validation only requires an entry to cover the
transmission of a minimum sized frame at link speed. Virtual devices
inflate that budget: veth advertises 10Gb/s and bonding sums the
speeds of its members, so length_to_duration(ETH_ZLEN) evaluates to a
few tens of nanoseconds and schedules with nanosecond intervals pass
validation. In software mode each entry expiry is an hrtimer callback
costing on the order of 10us on a debug configuration and about a
microsecond on a release build; intervals below that cost rearm the
timer with an expiry already in the past, storming the CPU with back
to back timer interrupts until RCU stalls.

Require 100us per entry in software mode, leaving margin above the
timer service cost. Offloaded and txtime-assist schedules never arm
the per-entry hrtimer and keep the frame-length based minimum only.

Fixes: b5b73b26b3ca ("taprio: Fix allowing too small intervals")
Reported-by: [email protected]
Closes: https://syzkaller.appspot.com/bug?extid=19d01f6082ec61dd45b2
Reported-by: [email protected]
Closes: https://syzkaller.appspot.com/bug?extid=8785aaf121cfb2141e0d
Reported-by: [email protected]
Closes: https://syzkaller.appspot.com/bug?extid=2642f347f7309b4880dc
Tested-by: [email protected]
Tested-by: [email protected]
Tested-by: [email protected]
Link: 
https://lore.kernel.org/all/[email protected]/
Signed-off-by: Uladzislau Zhauniarovich <[email protected]>
[jc: exempt txtime-assist, use s64 to keep rejecting negative
 cycle_time, rework commit message]
Signed-off-by: Junjie Cao <[email protected]>
---
 net/sched/sch_taprio.c | 24 ++++++++++++++++++++++--
 1 file changed, 22 insertions(+), 2 deletions(-)

diff --git a/net/sched/sch_taprio.c b/net/sched/sch_taprio.c
index f3f90c5d2dca..7519bc5c1aff 100644
--- a/net/sched/sch_taprio.c
+++ b/net/sched/sch_taprio.c
@@ -259,6 +259,26 @@ static int length_to_duration(struct taprio_sched *q, int 
len)
        return div_u64(len * atomic64_read(&q->picos_per_byte), PSEC_PER_NSEC);
 }
 
+/* Software schedules service one hrtimer expiry per entry; intervals
+ * shorter than the expiry service cost rearm the timer with an expiry
+ * already in the past and storm the CPU. 100us leaves margin above the
+ * measured cost on debug configurations.
+ */
+#define TAPRIO_MIN_SW_INTERVAL_NS      (100 * NSEC_PER_USEC)
+
+static s64 taprio_min_interval(struct taprio_sched *q)
+{
+       s64 min_interval = length_to_duration(q, ETH_ZLEN);
+
+       /* Only pure software schedules arm the per-entry hrtimer. */
+       if (!FULL_OFFLOAD_IS_ENABLED(q->flags) &&
+           !TXTIME_ASSIST_IS_ENABLED(q->flags))
+               min_interval = max_t(s64, min_interval,
+                                    TAPRIO_MIN_SW_INTERVAL_NS);
+
+       return min_interval;
+}
+
 static int duration_to_length(struct taprio_sched *q, u64 duration)
 {
        return div_u64(duration * PSEC_PER_NSEC, 
atomic64_read(&q->picos_per_byte));
@@ -1088,7 +1108,7 @@ static int fill_sched_entry(struct taprio_sched *q, 
struct nlattr **tb,
                            struct sched_entry *entry,
                            struct netlink_ext_ack *extack)
 {
-       int min_duration = length_to_duration(q, ETH_ZLEN);
+       s64 min_duration = taprio_min_interval(q);
        u32 interval = 0;
 
        if (tb[TCA_TAPRIO_SCHED_ENTRY_CMD])
@@ -1216,7 +1236,7 @@ static int parse_taprio_schedule(struct taprio_sched *q, 
struct nlattr **tb,
                new->cycle_time = cycle;
        }
 
-       if (new->cycle_time < new->num_entries * length_to_duration(q, 
ETH_ZLEN)) {
+       if (new->cycle_time < (s64)new->num_entries * taprio_min_interval(q)) {
                NL_SET_ERR_MSG(extack, "'cycle_time' is too small");
                return -EINVAL;
        }
-- 
2.43.0


Reply via email to