Re: [PATCH net] net: sched: fix quantum/backlog overflow in fq, fq_codel, hhf, sfq
Eric Dumazet <[email protected]>
| Newsgroups | gmane.linux.kernel.stable,gmane.linux.network |
|---|---|
| Message-ID | <CANn89i+edjh3JMZKTwG-gVtvMv_ya-wVUfK-yuKahRcoUoYDdQ@mail.gmail.com> |
On Tue, Aug 18, 2026 at 2:55 PM Eric Dumazet <[email protected]> wrote: > > On Tue, Aug 18, 2026 at 12:11 PM Jamal Hadi Salim <[email protected]> wrote: > > > > 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. > > [email protected] 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: [email protected] > > Tested-by: Victor Nogueira <[email protected]> > > Signed-off-by: Jamal Hadi Salim <[email protected]> > > --- > > 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? > > > 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.