Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

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: 462dbc9101ac ("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>

authored by

Xiang Mei and committed by
David S. Miller
5e28d5a3 7727ec15

+21 -9
+21 -9
net/sched/sch_qfq.c
··· 412 412 bool existing = false; 413 413 struct nlattr *tb[TCA_QFQ_MAX + 1]; 414 414 struct qfq_aggregate *new_agg = NULL; 415 - u32 weight, lmax, inv_w; 415 + u32 weight, lmax, inv_w, old_weight, old_lmax; 416 416 int err; 417 417 int delta_w; 418 418 ··· 443 443 inv_w = ONE_FP / weight; 444 444 weight = ONE_FP / inv_w; 445 445 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 + } 450 454 451 - delta_w = weight - (cl ? cl->agg->class_weight : 0); 455 + delta_w = weight - (cl ? old_weight : 0); 452 456 453 457 if (q->wsum + delta_w > QFQ_MAX_WSUM) { 454 458 NL_SET_ERR_MSG_FMT_MOD(extack, ··· 559 555 560 556 qdisc_purge_queue(cl->qdisc); 561 557 qdisc_class_hash_remove(&q->clhash, &cl->common); 558 + qfq_destroy_class(sch, cl); 562 559 563 560 sch_tree_unlock(sch); 564 561 565 - qfq_destroy_class(sch, cl); 566 562 return 0; 567 563 } 568 564 ··· 629 625 { 630 626 struct qfq_class *cl = (struct qfq_class *)arg; 631 627 struct nlattr *nest; 628 + u32 class_weight, lmax; 632 629 633 630 tcm->tcm_parent = TC_H_ROOT; 634 631 tcm->tcm_handle = cl->common.classid; ··· 638 633 nest = nla_nest_start_noflag(skb, TCA_OPTIONS); 639 634 if (nest == NULL) 640 635 goto nla_put_failure; 641 - if (nla_put_u32(skb, TCA_QFQ_WEIGHT, cl->agg->class_weight) || 642 - 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)) 643 643 goto nla_put_failure; 644 644 return nla_nest_end(skb, nest); 645 645 ··· 661 651 662 652 memset(&xstats, 0, sizeof(xstats)); 663 653 654 + sch_tree_lock(sch); 664 655 xstats.weight = cl->agg->class_weight; 665 656 xstats.lmax = cl->agg->lmax; 657 + sch_tree_unlock(sch); 666 658 667 659 if (gnet_stats_copy_basic(d, NULL, &cl->bstats, true) < 0 || 668 660 gnet_stats_copy_rate_est(d, &cl->rate_est) < 0 ||