net/sched: defer qdisc freeing after failed creation

An RTM_NEWQDISC request can make clsact bind a populated shared ingress
block during ->init(), publishing an embedded mini_Qdisc to lockless
readers.  If the same request has an invalid TCA_RATE, estimator setup
fails after ->init(); the unwind removes the pointer but synchronously
frees its containing qdisc while tc_run() may still hold it.

Retire failed qdiscs through the same RCU helper as normal destruction.
Inline the synchronous free into the callback now that no direct callers
remain.

Fixes: 51ab2994c3 ("net: sched: allow ingress and clsact qdiscs to share filter blocks")
Reported-by: Xiang Mei <xmei5@asu.edu>
Link: https://lore.kernel.org/netdev/20260805102505.740806-1-david.lee@trailofbits.com/
Signed-off-by: Weiming Shi <bestswngs@gmail.com>
Link: https://patch.msgid.link/20260902155231.2149915-2-bestswngs@gmail.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
Weiming Shi 2026-09-02 23:52:31 +08:00 committed by Jakub Kicinski
parent 2b4707a149
commit e6662f2100
3 changed files with 14 additions and 14 deletions

View File

@ -793,7 +793,7 @@ void qdisc_offload_query_caps(struct net_device *dev,
struct Qdisc *qdisc_alloc(struct netdev_queue *dev_queue,
const struct Qdisc_ops *ops,
struct netlink_ext_ack *extack);
void qdisc_free(struct Qdisc *qdisc);
void qdisc_free_rcu(struct Qdisc *qdisc);
struct Qdisc *qdisc_create_dflt(struct netdev_queue *dev_queue,
const struct Qdisc_ops *ops, u32 parentid,
struct netlink_ext_ack *extack);

View File

@ -1385,7 +1385,7 @@ static struct Qdisc *qdisc_create(struct net_device *dev,
err_out3:
qdisc_lock_uninit(sch, ops);
netdev_put(dev, &sch->dev_tracker);
qdisc_free(sch);
qdisc_free_rcu(sch);
err_out2:
bpf_module_put(ops, ops->owner);
err_out:

View File

@ -1086,21 +1086,21 @@ void qdisc_reset(struct Qdisc *qdisc)
}
EXPORT_SYMBOL(qdisc_reset);
void qdisc_free(struct Qdisc *qdisc)
{
if (qdisc_is_percpu_stats(qdisc)) {
free_percpu(qdisc->cpu_bstats);
free_percpu(qdisc->cpu_qstats);
}
kfree(qdisc);
}
static void qdisc_free_cb(struct rcu_head *head)
{
struct Qdisc *q = container_of(head, struct Qdisc, rcu);
qdisc_free(q);
if (qdisc_is_percpu_stats(q)) {
free_percpu(q->cpu_bstats);
free_percpu(q->cpu_qstats);
}
kfree(q);
}
void qdisc_free_rcu(struct Qdisc *qdisc)
{
call_rcu(&qdisc->rcu, qdisc_free_cb);
}
static void __qdisc_destroy(struct Qdisc *qdisc)
@ -1127,7 +1127,7 @@ static void __qdisc_destroy(struct Qdisc *qdisc)
trace_qdisc_destroy(qdisc);
call_rcu(&qdisc->rcu, qdisc_free_cb);
qdisc_free_rcu(qdisc);
}
void qdisc_destroy(struct Qdisc *qdisc)