Skip to content

Commit

Permalink
net_sched: Flush gso_skb list too during ->change()
Browse files Browse the repository at this point in the history
[ Upstream commit 2d3cbfd ]

Previously, when reducing a qdisc's limit via the ->change() operation, only
the main skb queue was trimmed, potentially leaving packets in the gso_skb
list. This could result in NULL pointer dereference when we only check
sch->limit against sch->q.qlen.

This patch introduces a new helper, qdisc_dequeue_internal(), which ensures
both the gso_skb list and the main queue are properly flushed when trimming
excess packets. All relevant qdiscs (codel, fq, fq_codel, fq_pie, hhf, pie)
are updated to use this helper in their ->change() routines.

Fixes: 76e3cc1 ("codel: Controlled Delay AQM")
Fixes: 4b549a2 ("fq_codel: Fair Queue Codel AQM")
Fixes: afe4fd0 ("pkt_sched: fq: Fair Queue packet scheduler")
Fixes: ec97ecf ("net: sched: add Flow Queue PIE packet scheduler")
Fixes: 10239ed ("net-qdisc-hhf: Heavy-Hitter Filter (HHF) qdisc")
Fixes: d4b3621 ("net: pkt_sched: PIE AQM scheme")
Reported-by: Will <willsroot@protonmail.com>
Reported-by: Savy <savy@syst3mfailure.io>
Signed-off-by: Cong Wang <xiyou.wangcong@gmail.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
Signed-off-by: Sasha Levin <sashal@kernel.org>
  • Loading branch information
Cong Wang authored and Greg Kroah-Hartman committed May 22, 2025
1 parent 915c3de commit d38939e
Show file tree
Hide file tree
Showing 7 changed files with 21 additions and 6 deletions.
15 changes: 15 additions & 0 deletions include/net/sch_generic.h
Original file line number Diff line number Diff line change
Expand Up @@ -997,6 +997,21 @@ static inline struct sk_buff *__qdisc_dequeue_head(struct qdisc_skb_head *qh)
return skb;
}

static inline struct sk_buff *qdisc_dequeue_internal(struct Qdisc *sch, bool direct)
{
struct sk_buff *skb;

skb = __skb_dequeue(&sch->gso_skb);
if (skb) {
sch->q.qlen--;
return skb;
}
if (direct)
return __qdisc_dequeue_head(&sch->q);
else
return sch->dequeue(sch);
}

static inline struct sk_buff *qdisc_dequeue_head(struct Qdisc *sch)
{
struct sk_buff *skb = __qdisc_dequeue_head(&sch->q);
Expand Down
2 changes: 1 addition & 1 deletion net/sched/sch_codel.c
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ static int codel_change(struct Qdisc *sch, struct nlattr *opt,

qlen = sch->q.qlen;
while (sch->q.qlen > sch->limit) {
struct sk_buff *skb = __qdisc_dequeue_head(&sch->q);
struct sk_buff *skb = qdisc_dequeue_internal(sch, true);

dropped += qdisc_pkt_len(skb);
qdisc_qstats_backlog_dec(sch, skb);
Expand Down
2 changes: 1 addition & 1 deletion net/sched/sch_fq.c
Original file line number Diff line number Diff line change
Expand Up @@ -901,7 +901,7 @@ static int fq_change(struct Qdisc *sch, struct nlattr *opt,
sch_tree_lock(sch);
}
while (sch->q.qlen > sch->limit) {
struct sk_buff *skb = fq_dequeue(sch);
struct sk_buff *skb = qdisc_dequeue_internal(sch, false);

if (!skb)
break;
Expand Down
2 changes: 1 addition & 1 deletion net/sched/sch_fq_codel.c
Original file line number Diff line number Diff line change
Expand Up @@ -431,7 +431,7 @@ static int fq_codel_change(struct Qdisc *sch, struct nlattr *opt,

while (sch->q.qlen > sch->limit ||
q->memory_usage > q->memory_limit) {
struct sk_buff *skb = fq_codel_dequeue(sch);
struct sk_buff *skb = qdisc_dequeue_internal(sch, false);

q->cstats.drop_len += qdisc_pkt_len(skb);
rtnl_kfree_skbs(skb, skb);
Expand Down
2 changes: 1 addition & 1 deletion net/sched/sch_fq_pie.c
Original file line number Diff line number Diff line change
Expand Up @@ -357,7 +357,7 @@ static int fq_pie_change(struct Qdisc *sch, struct nlattr *opt,

/* Drop excess packets if new limit is lower */
while (sch->q.qlen > sch->limit) {
struct sk_buff *skb = fq_pie_qdisc_dequeue(sch);
struct sk_buff *skb = qdisc_dequeue_internal(sch, false);

len_dropped += qdisc_pkt_len(skb);
num_dropped += 1;
Expand Down
2 changes: 1 addition & 1 deletion net/sched/sch_hhf.c
Original file line number Diff line number Diff line change
Expand Up @@ -560,7 +560,7 @@ static int hhf_change(struct Qdisc *sch, struct nlattr *opt,
qlen = sch->q.qlen;
prev_backlog = sch->qstats.backlog;
while (sch->q.qlen > sch->limit) {
struct sk_buff *skb = hhf_dequeue(sch);
struct sk_buff *skb = qdisc_dequeue_internal(sch, false);

rtnl_kfree_skbs(skb, skb);
}
Expand Down
2 changes: 1 addition & 1 deletion net/sched/sch_pie.c
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ static int pie_change(struct Qdisc *sch, struct nlattr *opt,
/* Drop excess packets if new limit is lower */
qlen = sch->q.qlen;
while (sch->q.qlen > sch->limit) {
struct sk_buff *skb = __qdisc_dequeue_head(&sch->q);
struct sk_buff *skb = qdisc_dequeue_internal(sch, true);

dropped += qdisc_pkt_len(skb);
qdisc_qstats_backlog_dec(sch, skb);
Expand Down

0 comments on commit d38939e

Please sign in to comment.