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: Jamal Hadi Salim <jhs@mojatatu.com>
Date: 2026-08-18 14:25:31
Also in: stable

On Tue, Aug 18, 2026 at 9:00 AM Eric Dumazet [off-list ref] wrote:
On Tue, Aug 18, 2026 at 2:55 PM Eric Dumazet [off-list ref] wrote:
quoted
On Tue, Aug 18, 2026 at 12:11 PM Jamal Hadi Salim [off-list ref] wrote:
quoted
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
                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.
Actually, we use 1M in TCA_FQ_QUANTUM in fq_change()

We also need to change fq_change() because TCA_FQ_INITIAL_QUANTUM can be set up
to INT_MAX.
Good point on avoiding fast path changes. I will review the others and
resend after hearing from the naughty AI.
So something like attached (compiles, untested)?

cheers,
jamal

Attachments

Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help