* [PATCH net repost 1/2] net/sched: codel: bound the dropping loop per dequeue call
@ 2026-09-12 18:08 Jamal Hadi Salim
2026-09-14 11:36 ` Toke Høiland-Jørgensen
2026-09-17 0:30 ` patchwork-bot+netdevbpf
0 siblings, 2 replies; 3+ messages in thread
From: Jamal Hadi Salim @ 2026-09-12 18:08 UTC (permalink / raw)
To: netdev
Cc: Jamal Hadi Salim, stable, Jiri Pirko, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
Victor Nogueira, Johannes Berg, linux-wireless, Vega
The CoDel control law schedules the next drop one interval/sqrt(count)
after the previous drop, using the configured interval
(codel_params.interval). For very small intervals the scheduled step
rounds down to zero, so the dropping loop in codel_dequeue() never
advances and drains the entire backlog under the qdisc lock in one
call - an unprivileged user can trigger a soft lockup this way.
Fix in the shared codel code used by both codel and fq_codel:
1. Make the control-law step at least 1 tick so the dropping loop
always moves forward.
2. Cap the dropping loop at CODEL_MAX_DROPS_PER_DEQUEUE (256) drops
per codel_dequeue() call, resyncing drop_next to now when the cap
is hit: the catch-up owed to the loop grows with the idle gap and
the backlog, which no interval threshold can bound. This is a
deliberate behaviour change after long idle gaps.
The cap applies to fq_codel (4b549a2ef4be) and the mac80211 TXQ path
(fixed interval, cap only).
The target sojourn delay (codel_params.target) is not validated: it
does not feed the control law, so a sub-tick value is aggressive
rather than deadlock-prone.
Conditions to recreate the bug:
- tc qdisc add dev lo root handle 1: tbf rate 1kbit burst 2kb limit 1000000
- tc qdisc add dev lo parent 1:1 handle 10: codel interval 2us target 1ms noecn limit 1000000 (same for fq_codel)
- unpatched kernel: tc accepts it; a UDP flood under the 1kbit tbf
soft-lockups (watchdog: BUG: soft lockup) while one
codel_dequeue() call drops the backlog under the qdisc lock
- patched kernel: same setup, at most 256 drops per dequeue call,
no soft lockup
Testing: claim reproducer and interval 2us/3us variants run clean;
tdc qdisc category passes (see the selftests patch).
Fixes: 76e3cc126bb2 ("codel: Controlled Delay AQM")
Reported-by: Vega <vega@nebusec.ai>
Reviewed-by: Eric Dumazet <edumazet@google.com>
Tested-by: Victor Nogueira <victor@mojatatu.com>
Signed-off-by: Jamal Hadi Salim <jhs@mojatatu.com>
---
include/net/codel.h | 5 +++++
include/net/codel_impl.h | 16 +++++++++++++++-
2 files changed, 20 insertions(+), 1 deletion(-)
diff --git a/include/net/codel.h b/include/net/codel.h
index aa80f744826c..183d43c2bd43 100644
--- a/include/net/codel.h
+++ b/include/net/codel.h
@@ -140,6 +140,11 @@ struct codel_vars {
/* needed shift to get a Q0.32 number from rec_inv_sqrt */
#define REC_INV_SQRT_SHIFT (32 - REC_INV_SQRT_BITS)
+/* Cap on drops per codel_dequeue() call: the loop's work depends on the
+ * idle gap and backlog, both outside our control; resync when exceeded.
+ */
+#define CODEL_MAX_DROPS_PER_DEQUEUE 256
+
/**
* struct codel_stats - contains codel shared variables and stats
* @maxpacket: largest packet we've seen so far
diff --git a/include/net/codel_impl.h b/include/net/codel_impl.h
index 2c1f0ec309e9..8f26132d45b7 100644
--- a/include/net/codel_impl.h
+++ b/include/net/codel_impl.h
@@ -93,12 +93,17 @@ static void codel_Newton_step(struct codel_vars *vars)
* CoDel control_law is t + interval/sqrt(count)
* We maintain in rec_inv_sqrt the reciprocal value of sqrt(count) to avoid
* both sqrt() and divide operation.
+ *
+ * Clamp the increment to at least 1 tick: a very small interval (or a
+ * large count) can truncate it to zero, stalling the dropping loop.
*/
static codel_time_t codel_control_law(codel_time_t t,
codel_time_t interval,
u32 rec_inv_sqrt)
{
- return t + reciprocal_scale(interval, rec_inv_sqrt << REC_INV_SQRT_SHIFT);
+ return t + max_t(u32, 1,
+ reciprocal_scale(interval,
+ rec_inv_sqrt << REC_INV_SQRT_SHIFT));
}
static bool codel_should_drop(const struct sk_buff *skb,
@@ -154,6 +159,7 @@ static struct sk_buff *codel_dequeue(void *ctx,
codel_skb_dequeue_t dequeue_func)
{
struct sk_buff *skb = dequeue_func(vars, ctx);
+ unsigned int drops = 0;
codel_time_t now;
bool drop;
@@ -180,6 +186,14 @@ static struct sk_buff *codel_dequeue(void *ctx,
*/
while (vars->dropping &&
codel_time_after_eq(now, vars->drop_next)) {
+ if (++drops > CODEL_MAX_DROPS_PER_DEQUEUE) {
+ /* fell far behind the schedule */
+ WRITE_ONCE(vars->drop_next,
+ codel_control_law(now,
+ params->interval,
+ vars->rec_inv_sqrt));
+ break;
+ }
/* dont care of possible wrap
* since there is no more divide.
*/
--
2.43.0
^ permalink raw reply related [flat|nested] 3+ messages in thread* Re: [PATCH net repost 1/2] net/sched: codel: bound the dropping loop per dequeue call
2026-09-12 18:08 [PATCH net repost 1/2] net/sched: codel: bound the dropping loop per dequeue call Jamal Hadi Salim
@ 2026-09-14 11:36 ` Toke Høiland-Jørgensen
2026-09-17 0:30 ` patchwork-bot+netdevbpf
1 sibling, 0 replies; 3+ messages in thread
From: Toke Høiland-Jørgensen @ 2026-09-14 11:36 UTC (permalink / raw)
To: Jamal Hadi Salim, netdev
Cc: Jamal Hadi Salim, stable, Jiri Pirko, David S. Miller,
Eric Dumazet, Jakub Kicinski, Paolo Abeni, Simon Horman,
Victor Nogueira, Johannes Berg, linux-wireless, Vega
Jamal Hadi Salim <jhs@mojatatu.com> writes:
> The CoDel control law schedules the next drop one interval/sqrt(count)
> after the previous drop, using the configured interval
> (codel_params.interval). For very small intervals the scheduled step
> rounds down to zero, so the dropping loop in codel_dequeue() never
> advances and drains the entire backlog under the qdisc lock in one
> call - an unprivileged user can trigger a soft lockup this way.
>
> Fix in the shared codel code used by both codel and fq_codel:
>
> 1. Make the control-law step at least 1 tick so the dropping loop
> always moves forward.
>
> 2. Cap the dropping loop at CODEL_MAX_DROPS_PER_DEQUEUE (256) drops
> per codel_dequeue() call, resyncing drop_next to now when the cap
> is hit: the catch-up owed to the loop grows with the idle gap and
> the backlog, which no interval threshold can bound. This is a
> deliberate behaviour change after long idle gaps.
Both of these seem reasonable!
Reviewed-by: Toke Høiland-Jørgensen <toke@toke.dk>
^ permalink raw reply [flat|nested] 3+ messages in thread
* Re: [PATCH net repost 1/2] net/sched: codel: bound the dropping loop per dequeue call
2026-09-12 18:08 [PATCH net repost 1/2] net/sched: codel: bound the dropping loop per dequeue call Jamal Hadi Salim
2026-09-14 11:36 ` Toke Høiland-Jørgensen
@ 2026-09-17 0:30 ` patchwork-bot+netdevbpf
1 sibling, 0 replies; 3+ messages in thread
From: patchwork-bot+netdevbpf @ 2026-09-17 0:30 UTC (permalink / raw)
To: Jamal Hadi Salim
Cc: netdev, stable, jiri, davem, edumazet, kuba, pabeni, horms,
victor, johannes, linux-wireless, vega
Hello:
This series was applied to netdev/net.git (main)
by Jakub Kicinski <kuba@kernel.org>:
On Sat, 12 Sep 2026 14:08:30 -0400 you wrote:
> The CoDel control law schedules the next drop one interval/sqrt(count)
> after the previous drop, using the configured interval
> (codel_params.interval). For very small intervals the scheduled step
> rounds down to zero, so the dropping loop in codel_dequeue() never
> advances and drains the entire backlog under the qdisc lock in one
> call - an unprivileged user can trigger a soft lockup this way.
>
> [...]
Here is the summary with links:
- [net,repost,1/2] net/sched: codel: bound the dropping loop per dequeue call
https://git.kernel.org/netdev/net/c/7f4a5ec6258f
- [net,repost,2/2] selftests/tc-testing: add codel/fq_codel interval boundary cases
https://git.kernel.org/netdev/net/c/f6fb2ac5e19a
You are awesome, thank you!
--
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html
^ permalink raw reply [flat|nested] 3+ messages in thread
end of thread, other threads:[~2026-09-17 0:31 UTC | newest]
Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-12 18:08 [PATCH net repost 1/2] net/sched: codel: bound the dropping loop per dequeue call Jamal Hadi Salim
2026-09-14 11:36 ` Toke Høiland-Jørgensen
2026-09-17 0:30 ` patchwork-bot+netdevbpf
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox