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.
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.