Skip to content

Commit 5e28d5a

Browse files
n132davem330
authored andcommitted
net/sched: sch_qfq: Fix race condition on qfq_aggregate
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]>
1 parent 7727ec1 commit 5e28d5a

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

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

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

451-
delta_w = weight - (cl ? cl->agg->class_weight : 0);
455+
delta_w = weight - (cl ? old_weight : 0);
452456

453457
if (q->wsum + delta_w > QFQ_MAX_WSUM) {
454458
NL_SET_ERR_MSG_FMT_MOD(extack,
@@ -555,10 +559,10 @@ static int qfq_delete_class(struct Qdisc *sch, unsigned long arg,
555559

556560
qdisc_purge_queue(cl->qdisc);
557561
qdisc_class_hash_remove(&q->clhash, &cl->common);
562+
qfq_destroy_class(sch, cl);
558563

559564
sch_tree_unlock(sch);
560565

561-
qfq_destroy_class(sch, cl);
562566
return 0;
563567
}
564568

@@ -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_noflag(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(d, NULL, &cl->bstats, true) < 0 ||
658670
gnet_stats_copy_rate_est(d, &cl->rate_est) < 0 ||

0 commit comments

Comments
 (0)