* [PATCH net] net/sched: taprio: fix infinite loop in taprio_dequeue_tc_priority()
@ 2026-09-11 3:37 李强
2026-09-11 5:53 ` Hangbin Liu
2026-09-11 6:07 ` Greg KH
0 siblings, 2 replies; 5+ messages in thread
From: 李强 @ 2026-09-11 3:37 UTC (permalink / raw)
To: security@kernel.org; +Cc: netdev@vger.kernel.org
Hi,
I found an infinite loop bug in taprio_dequeue_tc_priority() while fuzzing the Linux Ethernet networking stack on embedded devices.
taprio_dequeue_tc_priority() contains a do-while loop that iterates over TXQs belonging to a traffic class using a persistent cursor (q->cur_txq[tc]). The loop termination condition compares the current cursor position with its initial value (first_txq).
However, taprio_next_tc_txq() reads the TC-to-TXQ mapping from dev->tc_to_txq[tc], which is a per-device shared field. When another qdisc (e.g., mqprio) is grafted onto the same device with a different queue mapping, dev->tc_to_txq[] is updated but taprio's private cur_txq[] retains the stale value from the old mapping.
This creates a situation where first_txq (from old mapping A) falls outside the new [offset, offset+count) range (from new mapping B). The wrap logic in taprio_next_tc_txq() confines the cursor to the new range, so it can never reach first_txq, causing an infinite loop
in softirq context with interrupts disabled.
The loop manifests as a hard CPU lockup: the affected core spins indefinitely in taprio_dequeue_from_txq(), timer ticks stop, RCU
stalls, and the rtnl_lock is held forever -- blocking all network configuration (tc, ip, netlink) and even reboot. Only SysRq hard
reset can recover the system.
This is 100% deterministic with just 2 tc commands and zero network traffic:
tc qdisc replace dev eth0 root taprio \
num_tc 3 map 0 0 1 1 2 2 0 0 0 0 0 0 0 0 0 0 \
queues 1@0 1@1 1@2 base-time 0 \
sched-entry S ff 1000000 clockid CLOCK_TAI
tc qdisc replace dev eth0 root mqprio \
num_tc 3 map 0 0 1 1 2 2 0 0 0 0 0 0 0 0 0 0 \
queues 1@1 1@2 1@3
The second command triggers the graft path: mqprio writes dev->tc_to_txq[] with the new mapping, then dev_deactivate() fires
a pending TX softirq that calls taprio's dequeue with the stale cursor -- instant deadloop.
Tested on:
- aarch64 (6.12.73-rt15+, stmmac physical NIC): 6.5h lockup
- x86_64 (6.8.0-138-generic, veth virtual NIC): instant lockup
Fix by clamping first_txq into the current [offset, offset+count) range read from dev->tc_to_txq[tc] at the start of each TC iteration. If the cursor is outside the valid range, reset it to offset. This adds a single READ_ONCE + branch per TC per dequeue call with negligible overhead.
Fixes: 2f530df76c8c ("net/sched: taprio: give higher priority to higher TCs in software dequeue mode")
Signed-off-by: LiQiang <liqiang35@xiaomi.com>
---
net/sched/sch_taprio.c | 14 ++++++++++++++
1 file changed, 14 insertions(+)
diff --git a/net/sched/sch_taprio.c b/net/sched/sch_taprio.c
index 39ac5b97a..9ae0de007 100644
--- a/net/sched/sch_taprio.c
+++ b/net/sched/sch_taprio.c
@@ -781,6 +781,7 @@ static struct sk_buff *taprio_dequeue_tc_priority(struct Qdisc *sch,
struct taprio_sched *q = qdisc_priv(sch);
struct net_device *dev = qdisc_dev(sch);
int num_tc = netdev_get_num_tc(dev);
+ struct netdev_tc_txq tc_txq;
struct sk_buff *skb;
int tc;
@@ -790,6 +791,19 @@ static struct sk_buff *taprio_dequeue_tc_priority(struct Qdisc *sch,
if (!(gate_mask & BIT(tc)))
continue;
+ /* Clamp the persistent cursor into the current tc_to_txq
+ * range. Another qdisc (e.g. mqprio) grafted onto the same
+ * device may have rewritten dev->tc_to_txq[] since taprio
+ * last set cur_txq[], making first_txq fall outside the new
+ * [offset, offset+count) interval and the do-while exit
+ * condition unreachable -- an infinite loop.
+ */
+ tc_txq.combined = READ_ONCE(dev->tc_to_txq[tc].combined);
+ if (tc_txq.count == 0 ||
+ first_txq < tc_txq.offset ||
+ first_txq >= tc_txq.offset + tc_txq.count)
+ q->cur_txq[tc] = first_txq = tc_txq.offset;
+
do {
skb = taprio_dequeue_from_txq(sch, q->cur_txq[tc],
entry, gate_mask);
--
2.50.1
Best regards,
LiQiang
Xiaomi ShadowBlade Security Lab
#/******本邮件及其附件含有小米公司的保密信息,仅限于发送给上面地址中列出的个人或群组。禁止任何其他人以任何形式使用(包括但不限于全部或部分地泄露、复制、或散发)本邮件中的信息。如果您错收了本邮件,请您立即电话或邮件通知发件人并删除本邮件! This e-mail and its attachments contain confidential information from XIAOMI, which is intended only for the person or entity whose address is listed above. Any use of the information contained herein in any way (including, but not limited to, total or partial disclosure, reproduction, or dissemination) by persons other than the intended recipient(s) is prohibited. If you receive this e-mail in error, please notify the sender by phone or email immediately and delete it!******/#
^ permalink raw reply related [flat|nested] 5+ messages in thread
* Re: [PATCH net] net/sched: taprio: fix infinite loop in taprio_dequeue_tc_priority()
2026-09-11 3:37 [PATCH net] net/sched: taprio: fix infinite loop in taprio_dequeue_tc_priority() 李强
@ 2026-09-11 5:53 ` Hangbin Liu
2026-09-11 6:04 ` Willy Tarreau
2026-09-11 6:07 ` Greg KH
1 sibling, 1 reply; 5+ messages in thread
From: Hangbin Liu @ 2026-09-11 5:53 UTC (permalink / raw)
To: 李强; +Cc: security@kernel.org, netdev@vger.kernel.org
Hi Li Qiang,
Please use scripts/get_maintainer.pl to Cc all the needed maintainers.
On Fri, Sep 11, 2026 at 03:37:14AM +0000, 李强 wrote:
> Hi,
>
> I found an infinite loop bug in taprio_dequeue_tc_priority() while fuzzing the Linux Ethernet networking stack on embedded devices.
>
> taprio_dequeue_tc_priority() contains a do-while loop that iterates over TXQs belonging to a traffic class using a persistent cursor (q->cur_txq[tc]). The loop termination condition compares the current cursor position with its initial value (first_txq).
>
> However, taprio_next_tc_txq() reads the TC-to-TXQ mapping from dev->tc_to_txq[tc], which is a per-device shared field. When another qdisc (e.g., mqprio) is grafted onto the same device with a different queue mapping, dev->tc_to_txq[] is updated but taprio's private cur_txq[] retains the stale value from the old mapping.
>
> This creates a situation where first_txq (from old mapping A) falls outside the new [offset, offset+count) range (from new mapping B). The wrap logic in taprio_next_tc_txq() confines the cursor to the new range, so it can never reach first_txq, causing an infinite loop
> in softirq context with interrupts disabled.
>
> The loop manifests as a hard CPU lockup: the affected core spins indefinitely in taprio_dequeue_from_txq(), timer ticks stop, RCU
> stalls, and the rtnl_lock is held forever -- blocking all network configuration (tc, ip, netlink) and even reboot. Only SysRq hard
> reset can recover the system.
>
> This is 100% deterministic with just 2 tc commands and zero network traffic:
>
> tc qdisc replace dev eth0 root taprio \
> num_tc 3 map 0 0 1 1 2 2 0 0 0 0 0 0 0 0 0 0 \
> queues 1@0 1@1 1@2 base-time 0 \
> sched-entry S ff 1000000 clockid CLOCK_TAI
>
> tc qdisc replace dev eth0 root mqprio \
> num_tc 3 map 0 0 1 1 2 2 0 0 0 0 0 0 0 0 0 0 \
> queues 1@1 1@2 1@3
>
> The second command triggers the graft path: mqprio writes dev->tc_to_txq[] with the new mapping, then dev_deactivate() fires
> a pending TX softirq that calls taprio's dequeue with the stale cursor -- instant deadloop.
>
> Tested on:
> - aarch64 (6.12.73-rt15+, stmmac physical NIC): 6.5h lockup
> - x86_64 (6.8.0-138-generic, veth virtual NIC): instant lockup
>
> Fix by clamping first_txq into the current [offset, offset+count) range read from dev->tc_to_txq[tc] at the start of each TC iteration. If the cursor is outside the valid range, reset it to offset. This adds a single READ_ONCE + branch per TC per dequeue call with negligible overhead.
>
> Fixes: 2f530df76c8c ("net/sched: taprio: give higher priority to higher TCs in software dequeue mode")
> Signed-off-by: LiQiang <liqiang35@xiaomi.com>
The description is too long. It's prefer a maximum 75 chars per line.
You can check it with scripts/checkpatch.pl.
> ---
> net/sched/sch_taprio.c | 14 ++++++++++++++
> 1 file changed, 14 insertions(+)
>
> diff --git a/net/sched/sch_taprio.c b/net/sched/sch_taprio.c
> index 39ac5b97a..9ae0de007 100644
> --- a/net/sched/sch_taprio.c
> +++ b/net/sched/sch_taprio.c
> @@ -781,6 +781,7 @@ static struct sk_buff *taprio_dequeue_tc_priority(struct Qdisc *sch,
> struct taprio_sched *q = qdisc_priv(sch);
> struct net_device *dev = qdisc_dev(sch);
> int num_tc = netdev_get_num_tc(dev);
> + struct netdev_tc_txq tc_txq;
> struct sk_buff *skb;
> int tc;
>
> @@ -790,6 +791,19 @@ static struct sk_buff *taprio_dequeue_tc_priority(struct Qdisc *sch,
> if (!(gate_mask & BIT(tc)))
> continue;
>
> + /* Clamp the persistent cursor into the current tc_to_txq
> + * range. Another qdisc (e.g. mqprio) grafted onto the same
> + * device may have rewritten dev->tc_to_txq[] since taprio
> + * last set cur_txq[], making first_txq fall outside the new
> + * [offset, offset+count) interval and the do-while exit
> + * condition unreachable -- an infinite loop.
No need to comment it here since you have describe the problem in commit
description.
> + */
> + tc_txq.combined = READ_ONCE(dev->tc_to_txq[tc].combined);
> + if (tc_txq.count == 0 ||
> + first_txq < tc_txq.offset ||
> + first_txq >= tc_txq.offset + tc_txq.count)
> + q->cur_txq[tc] = first_txq = tc_txq.offset;
All the code format is broken. You can try git send-email to send
plain text patch.
> +
> do {
> skb = taprio_dequeue_from_txq(sch, q->cur_txq[tc],
> entry, gate_mask);
> --
> 2.50.1
>
>
> Best regards,
> LiQiang
> Xiaomi ShadowBlade Security Lab
> #/******本邮件及其附件含有小米公司的保密信息,仅限于发送给上面地址中列出的个人或群组。禁止任何其他人以任何形式使用(包括但不限于全部或部分地泄露、复制、或散发)本邮件中的信息。如果您错收了本邮件,请您立即电话或邮件通知发件人并删除本邮件! This e-mail and its attachments contain confidential information from XIAOMI, which is intended only for the person or entity whose address is listed above. Any use of the information contained herein in any way (including, but not limited to, total or partial disclosure, reproduction, or dissemination) by persons other than the intended recipient(s) is prohibited. If you receive this e-mail in error, please notify the sender by phone or email immediately and delete it!******/#
And you email client add these info.
Thanks
Hangbin
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH net] net/sched: taprio: fix infinite loop in taprio_dequeue_tc_priority()
2026-09-11 5:53 ` Hangbin Liu
@ 2026-09-11 6:04 ` Willy Tarreau
0 siblings, 0 replies; 5+ messages in thread
From: Willy Tarreau @ 2026-09-11 6:04 UTC (permalink / raw)
To: Hangbin Liu; +Cc: liqiang35, netdev@vger.kernel.org
On Fri, Sep 11, 2026 at 01:53:08PM +0800, Hangbin Liu wrote:
> Hi Li Qiang,
>
> Please use scripts/get_maintainer.pl to Cc all the needed maintainers.
Also, no need to Cc security@ when posting to public lists (now moved
to Bcc).
thanks,
willy
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH net] net/sched: taprio: fix infinite loop in taprio_dequeue_tc_priority()
2026-09-11 3:37 [PATCH net] net/sched: taprio: fix infinite loop in taprio_dequeue_tc_priority() 李强
2026-09-11 5:53 ` Hangbin Liu
@ 2026-09-11 6:07 ` Greg KH
1 sibling, 0 replies; 5+ messages in thread
From: Greg KH @ 2026-09-11 6:07 UTC (permalink / raw)
To: 李强; +Cc: security@kernel.org, netdev@vger.kernel.org
On Fri, Sep 11, 2026 at 03:37:14AM +0000, 李强 wrote:
> #/******本邮件及其附件含有小米公司的保密信息,仅限于发送给上面地址中列出的个人或群组。禁止任何其他人以任何形式使用(包括但不限于全部或部分地泄露、复制、或散发)本邮件中的信息。如果您错收了本邮件,请您立即电话或邮件通知发件人并删除本邮件! This e-mail and its attachments contain confidential information from XIAOMI, which is intended only for the person or entity whose address is listed above. Any use of the information contained herein in any way (including, but not limited to, total or partial disclosure, reproduction, or dissemination) by persons other than the intended recipient(s) is prohibited. If you receive this e-mail in error, please notify the sender by phone or email immediately and delete it!******/#
Now deleted.
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH net] net/sched: taprio: fix infinite loop in taprio_dequeue_tc_priority()
@ 2026-09-11 17:18 李强
0 siblings, 0 replies; 5+ messages in thread
From: 李强 @ 2026-09-11 17:18 UTC (permalink / raw)
To: vinicius.gomes@intel.com
Cc: jhs@mojatatu.com, jiri@resnulli.us, davem@davemloft.net,
edumazet@google.com, kuba@kernel.org, pabeni@redhat.com,
horms@kernel.org, netdev@vger.kernel.org,
linux-kernel@vger.kernel.org
Dear All,
I am writing this email to report an infinite loop bug in taprio_dequeue_tc_priority() while fuzzing the Linux Ethernet networking stack on embedded devices.
taprio_dequeue_tc_priority() contains a do-while loop that iterates over TXQs belonging to a traffic class using a persistent cursor (q->cur_txq[tc]). The loop termination condition compares the current cursor position with its initial value (first_txq).
However, taprio_next_tc_txq() reads the TC-to-TXQ mapping from dev->tc_to_txq[tc], which is a per-device shared field. When another qdisc (e.g., mqprio) is grafted onto the same device with a different queue mapping, dev->tc_to_txq[] is updated but taprio's private cur_txq[] retains the stale value from the old mapping.
This creates a situation where first_txq (from old mapping A) falls outside the new [offset, offset+count) range (from new mapping B). The wrap logic in taprio_next_tc_txq() confines the cursor to the new range, so it can never reach first_txq, causing an infinite loop
in softirq context with interrupts disabled.
The loop manifests as a hard CPU lockup: the affected core spins indefinitely in taprio_dequeue_from_txq(), timer ticks stop, RCU
stalls, and the rtnl_lock is held forever -- blocking all network configuration (tc, ip, netlink) and even reboot. Only SysRq hard
reset can recover the system.
This is 100% deterministic with just 2 tc commands and zero network traffic:
tc qdisc replace dev eth0 root taprio \
num_tc 3 map 0 0 1 1 2 2 0 0 0 0 0 0 0 0 0 0 \
queues 1@0 1@1 1@2 base-time 0 \
sched-entry S ff 1000000 clockid CLOCK_TAI
tc qdisc replace dev eth0 root mqprio \
num_tc 3 map 0 0 1 1 2 2 0 0 0 0 0 0 0 0 0 0 \
queues 1@1 1@2 1@3
The second command triggers the graft path: mqprio writes dev->tc_to_txq[] with the new mapping, then dev_deactivate() fires
a pending TX softirq that calls taprio's dequeue with the stale cursor -- instant deadloop.
Tested on:
- aarch64 (6.12.73-rt15+, stmmac physical NIC): 6.5h lockup
- x86_64 (6.8.0-138-generic, veth virtual NIC): instant lockup
Fix by clamping first_txq into the current [offset, offset+count) range read from dev->tc_to_txq[tc] at the start of each TC iteration. If the cursor is outside the valid range, reset it to offset. This adds a single READ_ONCE + branch per TC per dequeue call with negligible overhead.
Fixes: 2f530df76c8c ("net/sched: taprio: give higher priority to higher TCs in software dequeue mode")
Signed-off-by: LiQiang <liqiang35@xiaomi.com>
---
net/sched/sch_taprio.c | 14 ++++++++++++++
1 file changed, 14 insertions(+)
diff --git a/net/sched/sch_taprio.c b/net/sched/sch_taprio.c
index 39ac5b97a..9ae0de007 100644
--- a/net/sched/sch_taprio.c
+++ b/net/sched/sch_taprio.c
@@ -781,6 +781,7 @@ static struct sk_buff *taprio_dequeue_tc_priority(struct Qdisc *sch,
struct taprio_sched *q = qdisc_priv(sch);
struct net_device *dev = qdisc_dev(sch);
int num_tc = netdev_get_num_tc(dev);
+ struct netdev_tc_txq tc_txq;
struct sk_buff *skb;
int tc;
@@ -790,6 +791,19 @@ static struct sk_buff *taprio_dequeue_tc_priority(struct Qdisc *sch,
if (!(gate_mask & BIT(tc)))
continue;
+ /* Clamp the persistent cursor into the current tc_to_txq
+ * range. Another qdisc (e.g. mqprio) grafted onto the same
+ * device may have rewritten dev->tc_to_txq[] since taprio
+ * last set cur_txq[], making first_txq fall outside the new
+ * [offset, offset+count) interval and the do-while exit
+ * condition unreachable -- an infinite loop.
+ */
+ tc_txq.combined = READ_ONCE(dev->tc_to_txq[tc].combined);
+ if (tc_txq.count == 0 ||
+ first_txq < tc_txq.offset ||
+ first_txq >= tc_txq.offset + tc_txq.count)
+ q->cur_txq[tc] = first_txq = tc_txq.offset;
+
do {
skb = taprio_dequeue_from_txq(sch, q->cur_txq[tc],
entry, gate_mask);
--
2.50.1
Best regards,
LiQiang
Xiaomi ShadowBlade Security Lab
#/******本邮件及其附件含有小米公司的保密信息,仅限于发送给上面地址中列出的个人或群组。禁止任何其他人以任何形式使用(包括但不限于全部或部分地泄露、复制、或散发)本邮件中的信息。如果您错收了本邮件,请您立即电话或邮件通知发件人并删除本邮件! This e-mail and its attachments contain confidential information from XIAOMI, which is intended only for the person or entity whose address is listed above. Any use of the information contained herein in any way (including, but not limited to, total or partial disclosure, reproduction, or dissemination) by persons other than the intended recipient(s) is prohibited. If you receive this e-mail in error, please notify the sender by phone or email immediately and delete it!******/#
^ permalink raw reply related [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-09-11 17:18 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-11 3:37 [PATCH net] net/sched: taprio: fix infinite loop in taprio_dequeue_tc_priority() 李强
2026-09-11 5:53 ` Hangbin Liu
2026-09-11 6:04 ` Willy Tarreau
2026-09-11 6:07 ` Greg KH
-- strict thread matches above, loose matches on Subject: below --
2026-09-11 17:18 李强
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.