Skip to content

Commit cb7fe85

Browse files
committed
net/sched: sch_qfq: Fix race condition on qfq_aggregate
jira VULN-89287 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: Brett Mastbergen <bmastbergen@ciq.com>
1 parent 80bf90d commit cb7fe85

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
@@ -414,7 +414,7 @@ static int qfq_change_class(struct Qdisc *sch, u32 classid, u32 parentid,
414414
bool existing = false;
415415
struct nlattr *tb[TCA_QFQ_MAX + 1];
416416
struct qfq_aggregate *new_agg = NULL;
417-
u32 weight, lmax, inv_w;
417+
u32 weight, lmax, inv_w, old_weight, old_lmax;
418418
int err;
419419
int delta_w;
420420

@@ -449,12 +449,16 @@ static int qfq_change_class(struct Qdisc *sch, u32 classid, u32 parentid,
449449
inv_w = ONE_FP / weight;
450450
weight = ONE_FP / inv_w;
451451

452-
if (cl != NULL &&
453-
lmax == cl->agg->lmax &&
454-
weight == cl->agg->class_weight)
455-
return 0; /* nothing to change */
452+
if (cl != NULL) {
453+
sch_tree_lock(sch);
454+
old_weight = cl->agg->class_weight;
455+
old_lmax = cl->agg->lmax;
456+
sch_tree_unlock(sch);
457+
if (lmax == old_lmax && weight == old_weight)
458+
return 0; /* nothing to change */
459+
}
456460

457-
delta_w = weight - (cl ? cl->agg->class_weight : 0);
461+
delta_w = weight - (cl ? old_weight : 0);
458462

459463
if (q->wsum + delta_w > QFQ_MAX_WSUM) {
460464
pr_notice("qfq: total weight out of range (%d + %u)\n",
@@ -557,10 +561,10 @@ static int qfq_delete_class(struct Qdisc *sch, unsigned long arg)
557561

558562
qfq_purge_queue(cl);
559563
qdisc_class_hash_remove(&q->clhash, &cl->common);
564+
qfq_destroy_class(sch, cl);
560565

561566
sch_tree_unlock(sch);
562567

563-
qfq_destroy_class(sch, cl);
564568
return 0;
565569
}
566570

@@ -625,6 +629,7 @@ static int qfq_dump_class(struct Qdisc *sch, unsigned long arg,
625629
{
626630
struct qfq_class *cl = (struct qfq_class *)arg;
627631
struct nlattr *nest;
632+
u32 class_weight, lmax;
628633

629634
tcm->tcm_parent = TC_H_ROOT;
630635
tcm->tcm_handle = cl->common.classid;
@@ -633,8 +638,13 @@ static int qfq_dump_class(struct Qdisc *sch, unsigned long arg,
633638
nest = nla_nest_start(skb, TCA_OPTIONS);
634639
if (nest == NULL)
635640
goto nla_put_failure;
636-
if (nla_put_u32(skb, TCA_QFQ_WEIGHT, cl->agg->class_weight) ||
637-
nla_put_u32(skb, TCA_QFQ_LMAX, cl->agg->lmax))
641+
642+
sch_tree_lock(sch);
643+
class_weight = cl->agg->class_weight;
644+
lmax = cl->agg->lmax;
645+
sch_tree_unlock(sch);
646+
if (nla_put_u32(skb, TCA_QFQ_WEIGHT, class_weight) ||
647+
nla_put_u32(skb, TCA_QFQ_LMAX, lmax))
638648
goto nla_put_failure;
639649
return nla_nest_end(skb, nest);
640650

@@ -651,8 +661,10 @@ static int qfq_dump_class_stats(struct Qdisc *sch, unsigned long arg,
651661

652662
memset(&xstats, 0, sizeof(xstats));
653663

664+
sch_tree_lock(sch);
654665
xstats.weight = cl->agg->class_weight;
655666
xstats.lmax = cl->agg->lmax;
667+
sch_tree_unlock(sch);
656668

657669
if (gnet_stats_copy_basic(qdisc_root_sleeping_running(sch),
658670
d, NULL, &cl->bstats) < 0 ||

0 commit comments

Comments
 (0)