Thread (4 messages) flat view 4 messages, 2 authors, 2026-08-18

Re: [PATCH net] net: sched: fix quantum/backlog overflow in fq, fq_codel, hhf, sfq

From: Eric Dumazet <edumazet@google.com>
Date: 2026-08-18 12:56:02
Also in: stable

On Tue, Aug 18, 2026 at 12:11 PM Jamal Hadi Salim [off-list ref] wrote:
quoted hunk ↗ jump to hunk
Several qdiscs derive their per-flow quantum or backlog from psched_mtu()
or accumulate qdisc_pkt_len() into a u32/int counter without an overflow
or zero clamp, which can drive the dequeue/credit-refill loop into a soft
lockup or a NULL deref.
vega@nebusec.ai provided reports and PoCs which illustrated the following:

- sch_fq: fq_dequeue() credit-refill loop with a small quantum spins ~1B
  iterations under the qdisc lock (soft lockup); fq_init() computes
  quantum = 2 * psched_mtu() with no overflow check.
- sch_fq_codel: fq_codel_enqueue() accumulates qdisc_pkt_len() into a u32
  per-flow backlog; a crafted TCA_STAB inflates pkt_len to 1 GiB so a few
  packets wrap the counter to 0, and fq_codel_drop() then picks an empty
  flow and derefs NULL.
- sch_hhf: hhf_init() sets quantum = psched_mtu() with no overflow check;
  a huge MTU makes it 0x80000000, and hhf_dequeue()'s deficit += weight *
  quantum loops forever.
- sch_sfq: sfq_init() sets quantum = psched_mtu() (unsigned); a huge MTU
  makes allot = INT_MIN, and INT_MIN + INT_MIN is UB that toggles between
  INT_MIN and 0 forever.

Clamp the quantum to a sane minimum and promote the backlog/credit sums to
avoid the wrap, so the dequeue loops terminate and the drop path never
selects an empty flow.

Reported-by: vega@nebusec.ai
Tested-by: Victor Nogueira <redacted>
Signed-off-by: Jamal Hadi Salim <jhs@mojatatu.com>
---
 net/sched/sch_fq.c       |  9 ++++++---
 net/sched/sch_fq_codel.c | 14 +++++++++++++-
 net/sched/sch_hhf.c      | 12 +++++++++++
 net/sched/sch_sfq.c      |  7 +++++++
 4 files changed, 38 insertions(+), 4 deletions(-)
diff --git a/net/sched/sch_fq.c b/net/sched/sch_fq.c
index 7cae082a9847..66e9c6e530d8 100644
--- a/net/sched/sch_fq.c
+++ b/net/sched/sch_fq.c
@@ -750,7 +750,10 @@ static struct sk_buff *fq_dequeue(struct Qdisc *sch)
        f = head->first;
        retry = 0;
        if (f->credit <= 0) {
-               f->credit += q->quantum;
+               if (f->credit + (int)q->quantum > 0)
+                       f->credit += q->quantum;
+               else
+                       f->credit = q->quantum;
Lets not add cost in FQ fast path just because of a configuration issue?
quoted hunk ↗ jump to hunk
                head->first = f->next;
                fq_flow_add_tail(q, f, OLD_FLOW);
                goto begin;
@@ -1226,8 +1229,8 @@ static int fq_init(struct Qdisc *sch, struct nlattr *opt,

        sch->limit              = 10000;
        q->flow_plimit          = 100;
-       q->quantum              = 2 * psched_mtu(qdisc_dev(sch));
-       q->initial_quantum      = 10 * psched_mtu(qdisc_dev(sch));
+       q->quantum              = max_t(u32, 2 * psched_mtu(qdisc_dev(sch)), 256);
+       q->initial_quantum      = max_t(u32, 10 * psched_mtu(qdisc_dev(sch)), 256);
We instead can make sure quantum and initial_quantum are in an acceptable range.

[256, 16M] would probably make sense.
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help