Commit cd23e77e authored by Paolo Abeni's avatar Paolo Abeni
Browse files

Merge branch 'net_sched-make-qlen_notify-idempotent'



Cong Wang says:

====================
net_sched: make ->qlen_notify() idempotent

Gerrard reported a vulnerability exists in fq_codel where manipulating
the MTU can cause codel_dequeue() to drop all packets. The parent qdisc's
sch->q.qlen is only updated via ->qlen_notify() if the fq_codel queue
remains non-empty after the drops. This discrepancy in qlen between
fq_codel and its parent can lead to a use-after-free condition.

Let's fix this by making all existing ->qlen_notify() idempotent so that
the sch->q.qlen check will be no longer necessary.

Patch 1~5 make all existing ->qlen_notify() idempotent to prepare for
patch 6 which removes the sch->q.qlen check. They are followed by 5
selftests for each type of Qdisc's we touch here.

All existing and new Qdisc selftests pass after this patchset.

Fixes: 4b549a2e ("fq_codel: Fair Queue Codel AQM")
Fixes: 76e3cc12 ("codel: Controlled Delay AQM")
Acked-by: default avatarJamal Hadi Salim <jhs@mojatatu.com>
====================

Link: https://patch.msgid.link/20250403211033.166059-1-xiyou.wangcong@gmail.com


Signed-off-by: default avatarPaolo Abeni <pabeni@redhat.com>
parents 69ae9472 ce94507f
Loading
Loading
Loading
Loading
+1 −4
Original line number Diff line number Diff line
@@ -65,10 +65,7 @@ static struct sk_buff *codel_qdisc_dequeue(struct Qdisc *sch)
			    &q->stats, qdisc_pkt_len, codel_get_enqueue_time,
			    drop_func, dequeue_func);

	/* We cant call qdisc_tree_reduce_backlog() if our qlen is 0,
	 * or HTB crashes. Defer it for next round.
	 */
	if (q->stats.drop_count && sch->q.qlen) {
	if (q->stats.drop_count) {
		qdisc_tree_reduce_backlog(sch, q->stats.drop_count, q->stats.drop_len);
		q->stats.drop_count = 0;
		q->stats.drop_len = 0;
+4 −3
Original line number Diff line number Diff line
@@ -105,6 +105,7 @@ static int drr_change_class(struct Qdisc *sch, u32 classid, u32 parentid,
		return -ENOBUFS;

	gnet_stats_basic_sync_init(&cl->bstats);
	INIT_LIST_HEAD(&cl->alist);
	cl->common.classid = classid;
	cl->quantum	   = quantum;
	cl->qdisc	   = qdisc_create_dflt(sch->dev_queue,
@@ -229,7 +230,7 @@ static void drr_qlen_notify(struct Qdisc *csh, unsigned long arg)
{
	struct drr_class *cl = (struct drr_class *)arg;

	list_del(&cl->alist);
	list_del_init(&cl->alist);
}

static int drr_dump_class(struct Qdisc *sch, unsigned long arg,
@@ -390,7 +391,7 @@ static struct sk_buff *drr_dequeue(struct Qdisc *sch)
			if (unlikely(skb == NULL))
				goto out;
			if (cl->qdisc->q.qlen == 0)
				list_del(&cl->alist);
				list_del_init(&cl->alist);

			bstats_update(&cl->bstats, skb);
			qdisc_bstats_update(sch, skb);
@@ -431,7 +432,7 @@ static void drr_reset_qdisc(struct Qdisc *sch)
	for (i = 0; i < q->clhash.hashsize; i++) {
		hlist_for_each_entry(cl, &q->clhash.hash[i], common.hnode) {
			if (cl->qdisc->q.qlen)
				list_del(&cl->alist);
				list_del_init(&cl->alist);
			qdisc_reset(cl->qdisc);
		}
	}
+4 −4
Original line number Diff line number Diff line
@@ -293,7 +293,7 @@ static void ets_class_qlen_notify(struct Qdisc *sch, unsigned long arg)
	 * to remove them.
	 */
	if (!ets_class_is_strict(q, cl) && sch->q.qlen)
		list_del(&cl->alist);
		list_del_init(&cl->alist);
}

static int ets_class_dump(struct Qdisc *sch, unsigned long arg,
@@ -488,7 +488,7 @@ static struct sk_buff *ets_qdisc_dequeue(struct Qdisc *sch)
			if (unlikely(!skb))
				goto out;
			if (cl->qdisc->q.qlen == 0)
				list_del(&cl->alist);
				list_del_init(&cl->alist);
			return ets_qdisc_dequeue_skb(sch, skb);
		}

@@ -657,7 +657,7 @@ static int ets_qdisc_change(struct Qdisc *sch, struct nlattr *opt,
	}
	for (i = q->nbands; i < oldbands; i++) {
		if (i >= q->nstrict && q->classes[i].qdisc->q.qlen)
			list_del(&q->classes[i].alist);
			list_del_init(&q->classes[i].alist);
		qdisc_tree_flush_backlog(q->classes[i].qdisc);
	}
	WRITE_ONCE(q->nstrict, nstrict);
@@ -713,7 +713,7 @@ static void ets_qdisc_reset(struct Qdisc *sch)

	for (band = q->nstrict; band < q->nbands; band++) {
		if (q->classes[band].qdisc->q.qlen)
			list_del(&q->classes[band].alist);
			list_del_init(&q->classes[band].alist);
	}
	for (band = 0; band < q->nbands; band++)
		qdisc_reset(q->classes[band].qdisc);
+2 −4
Original line number Diff line number Diff line
@@ -315,10 +315,8 @@ static struct sk_buff *fq_codel_dequeue(struct Qdisc *sch)
	}
	qdisc_bstats_update(sch, skb);
	flow->deficit -= qdisc_pkt_len(skb);
	/* We cant call qdisc_tree_reduce_backlog() if our qlen is 0,
	 * or HTB crashes. Defer it for next round.
	 */
	if (q->cstats.drop_count && sch->q.qlen) {

	if (q->cstats.drop_count) {
		qdisc_tree_reduce_backlog(sch, q->cstats.drop_count,
					  q->cstats.drop_len);
		q->cstats.drop_count = 0;
+6 −2
Original line number Diff line number Diff line
@@ -203,7 +203,10 @@ eltree_insert(struct hfsc_class *cl)
static inline void
eltree_remove(struct hfsc_class *cl)
{
	if (!RB_EMPTY_NODE(&cl->el_node)) {
		rb_erase(&cl->el_node, &cl->sched->eligible);
		RB_CLEAR_NODE(&cl->el_node);
	}
}

static inline void
@@ -1220,6 +1223,7 @@ hfsc_qlen_notify(struct Qdisc *sch, unsigned long arg)
	/* vttree is now handled in update_vf() so that update_vf(cl, 0, 0)
	 * needs to be called explicitly to remove a class from vttree.
	 */
	if (cl->cl_nactive)
		update_vf(cl, 0, 0);
	if (cl->cl_flags & HFSC_RSC)
		eltree_remove(cl);
Loading