Skip to content

Commit 67a2eda

Browse files
committed
net/sched: sch_qfq: Fix race condition on qfq_aggregate
jira VULN-89291 cve CVE-2025-38477 commit-author Xiang Mei <xmei5@asu.edu> commit 5e28d5a A race condition can occur when 'agg' is modified in qfq_change_agg (called during qfq_enqueue) while other threads access it concurrently. For example, qfq_dump_class may trigger a NULL dereference, and qfq_delete_class may cause a use-after-free. This patch addresses the issue by: 1. Moved qfq_destroy_class into the critical section. 2. Added sch_tree_lock protection to qfq_dump_class and qfq_dump_class_stats. Fixes: 462dbc9 ("pkt_sched: QFQ Plus: fair-queueing service at DRR cost") Signed-off-by: Xiang Mei <xmei5@asu.edu> Reviewed-by: Cong Wang <xiyou.wangcong@gmail.com> Signed-off-by: David S. Miller <davem@davemloft.net> (cherry picked from commit 5e28d5a) Signed-off-by: Jonathan Maple <jmaple@ciq.com>
1 parent d073069 commit 67a2eda

File tree

1 file changed

+21
-9
lines changed

1 file changed

+21
-9
lines changed

net/sched/sch_qfq.c

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -406,7 +406,7 @@ static int qfq_change_class(struct Qdisc *sch, u32 classid, u32 parentid,
406406
bool existing = false;
407407
struct nlattr *tb[TCA_QFQ_MAX + 1];
408408
struct qfq_aggregate *new_agg = NULL;
409-
u32 weight, lmax, inv_w;
409+
u32 weight, lmax, inv_w, old_weight, old_lmax;
410410
int err;
411411
int delta_w;
412412

@@ -442,12 +442,16 @@ static int qfq_change_class(struct Qdisc *sch, u32 classid, u32 parentid,
442442
inv_w = ONE_FP / weight;
443443
weight = ONE_FP / inv_w;
444444

445-
if (cl != NULL &&
446-
lmax == cl->agg->lmax &&
447-
weight == cl->agg->class_weight)
448-
return 0; /* nothing to change */
445+
if (cl != NULL) {
446+
sch_tree_lock(sch);
447+
old_weight = cl->agg->class_weight;
448+
old_lmax = cl->agg->lmax;
449+
sch_tree_unlock(sch);
450+
if (lmax == old_lmax && weight == old_weight)
451+
return 0; /* nothing to change */
452+
}
449453

450-
delta_w = weight - (cl ? cl->agg->class_weight : 0);
454+
delta_w = weight - (cl ? old_weight : 0);
451455

452456
if (q->wsum + delta_w > QFQ_MAX_WSUM) {
453457
pr_notice("qfq: total weight out of range (%d + %u)\n",
@@ -550,10 +554,10 @@ static int qfq_delete_class(struct Qdisc *sch, unsigned long arg,
550554

551555
qdisc_purge_queue(cl->qdisc);
552556
qdisc_class_hash_remove(&q->clhash, &cl->common);
557+
qfq_destroy_class(sch, cl);
553558

554559
sch_tree_unlock(sch);
555560

556-
qfq_destroy_class(sch, cl);
557561
return 0;
558562
}
559563

@@ -620,6 +624,7 @@ static int qfq_dump_class(struct Qdisc *sch, unsigned long arg,
620624
{
621625
struct qfq_class *cl = (struct qfq_class *)arg;
622626
struct nlattr *nest;
627+
u32 class_weight, lmax;
623628

624629
tcm->tcm_parent = TC_H_ROOT;
625630
tcm->tcm_handle = cl->common.classid;
@@ -628,8 +633,13 @@ static int qfq_dump_class(struct Qdisc *sch, unsigned long arg,
628633
nest = nla_nest_start_noflag(skb, TCA_OPTIONS);
629634
if (nest == NULL)
630635
goto nla_put_failure;
631-
if (nla_put_u32(skb, TCA_QFQ_WEIGHT, cl->agg->class_weight) ||
632-
nla_put_u32(skb, TCA_QFQ_LMAX, cl->agg->lmax))
636+
637+
sch_tree_lock(sch);
638+
class_weight = cl->agg->class_weight;
639+
lmax = cl->agg->lmax;
640+
sch_tree_unlock(sch);
641+
if (nla_put_u32(skb, TCA_QFQ_WEIGHT, class_weight) ||
642+
nla_put_u32(skb, TCA_QFQ_LMAX, lmax))
633643
goto nla_put_failure;
634644
return nla_nest_end(skb, nest);
635645

@@ -646,8 +656,10 @@ static int qfq_dump_class_stats(struct Qdisc *sch, unsigned long arg,
646656

647657
memset(&xstats, 0, sizeof(xstats));
648658

659+
sch_tree_lock(sch);
649660
xstats.weight = cl->agg->class_weight;
650661
xstats.lmax = cl->agg->lmax;
662+
sch_tree_unlock(sch);
651663

652664
if (gnet_stats_copy_basic(d, NULL, &cl->bstats, true) < 0 ||
653665
gnet_stats_copy_rate_est(d, &cl->rate_est) < 0 ||

0 commit comments

Comments
 (0)