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
- codel-patchlet [application/octet-stream] 1271 bytes