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 | <CANn89iKGuPE0qEp-ucnWfvr+Vo_Ynv7zxuTDaxtTWr3d_YiYLA@mail.gmail.com> |
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.