Skip to content

Commit a6d7351

Browse files
n132gregkh
authored andcommitted
net/sched: sch_qfq: Fix race condition on qfq_aggregate
[ Upstream 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 <[email protected]> Reviewed-by: Cong Wang <[email protected]> Signed-off-by: David S. Miller <[email protected]> Signed-off-by: Sasha Levin <[email protected]>
1 parent e8767b8 commit a6d7351

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

@@ -446,12 +446,16 @@ static int qfq_change_class(struct Qdisc *sch, u32 classid, u32 parentid,
446446
inv_w = ONE_FP / weight;
447447
weight = ONE_FP / inv_w;
448448

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

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

456460
if (q->wsum + delta_w > QFQ_MAX_WSUM) {
457461
NL_SET_ERR_MSG_FMT_MOD(extack,
@@ -558,10 +562,10 @@ static int qfq_delete_class(struct Qdisc *sch, unsigned long arg,
558562

559563
qdisc_purge_queue(cl->qdisc);
560564
qdisc_class_hash_remove(&q->clhash, &cl->common);
565+
qfq_destroy_class(sch, cl);
561566

562567
sch_tree_unlock(sch);
563568

564-
qfq_destroy_class(sch, cl);
565569
return 0;
566570
}
567571

@@ -628,6 +632,7 @@ static int qfq_dump_class(struct Qdisc *sch, unsigned long arg,
628632
{
629633
struct qfq_class *cl = (struct qfq_class *)arg;
630634
struct nlattr *nest;
635+
u32 class_weight, lmax;
631636

632637
tcm->tcm_parent = TC_H_ROOT;
633638
tcm->tcm_handle = cl->common.classid;
@@ -636,8 +641,13 @@ static int qfq_dump_class(struct Qdisc *sch, unsigned long arg,
636641
nest = nla_nest_start_noflag(skb, TCA_OPTIONS);
637642
if (nest == NULL)
638643
goto nla_put_failure;
639-
if (nla_put_u32(skb, TCA_QFQ_WEIGHT, cl->agg->class_weight) ||
640-
nla_put_u32(skb, TCA_QFQ_LMAX, cl->agg->lmax))
644+
645+
sch_tree_lock(sch);
646+
class_weight = cl->agg->class_weight;
647+
lmax = cl->agg->lmax;
648+
sch_tree_unlock(sch);
649+
if (nla_put_u32(skb, TCA_QFQ_WEIGHT, class_weight) ||
650+
nla_put_u32(skb, TCA_QFQ_LMAX, lmax))
641651
goto nla_put_failure;
642652
return nla_nest_end(skb, nest);
643653

@@ -654,8 +664,10 @@ static int qfq_dump_class_stats(struct Qdisc *sch, unsigned long arg,
654664

655665
memset(&xstats, 0, sizeof(xstats));
656666

667+
sch_tree_lock(sch);
657668
xstats.weight = cl->agg->class_weight;
658669
xstats.lmax = cl->agg->lmax;
670+
sch_tree_unlock(sch);
659671

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

0 commit comments

Comments
 (0)