net/sched: defer qdisc freeing after failed creation
Subsystems (3)
v2 cover net/sched: defer qdisc freeing after failed creation
This is v2 of the qdisc failed-creation lifetime fix posted by Kyle Zeng and David Lee [1]. The public reproducer is available in a follow-up message [2].
When clsact binds a populated shared ingress block, ->init() can publish an embedded mini_Qdisc before qdisc_create() completes. A later invalid TCA_RATE makes estimator setup fail. The unwind removes the published pointer but frees the containing qdisc synchronously while tc_run() can still hold it.
The fix itself is unchanged: failed qdiscs are retired through RCU.
With the public reproducer from [2], the vulnerable kernel reports a KASAN UAF in tc_run(), with the free in qdisc_create(). The same test completes without a KASAN report on v2.
Changes in v2:
- inline qdisc_free() into qdisc_free_cb()
- use qdisc_free_rcu() for normal and failed-construction teardown
- retain Fixes: 51ab2994c387 as requested
- credit the earlier security-list reporter
[1] https://lore.kernel.org/netdev/[email protected]/
[2] https://lore.kernel.org/netdev/CAC_etQFk%[email protected]/
Weiming Shi (1):
net/sched: defer qdisc freeing after failed creation
include/net/sch_generic.h | 2 +- net/sched/sch_api.c | 2 +- net/sched/sch_generic.c | 20 ++++++++++---------- 3 files changed, 12 insertions(+), 12 deletions(-)
PATCH v2 1/1 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.
---
Changes in v2:
- inline qdisc_free() into qdisc_free_cb()
- use qdisc_free_rcu() for normal and failed-construction teardown
- use the precise 51ab2994c387 Fixes tag requested on the v1 thread
- credit the earlier security-list reporter and link the public v1
include/net/sch_generic.h | 2 +- net/sched/sch_api.c | 2 +- net/sched/sch_generic.c | 20 ++++++++++---------- 3 files changed, 12 insertions(+), 12 deletions(-)
include/net/sch_generic.h +1 -1
@@ -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);
net/sched/sch_api.c +1 -1
@@ -1382,7 +1382,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:
net/sched/sch_generic.c +10 -10
@@ -1086,21 +1086,21 @@ void qdisc_reset(struct Qdisc *qdisc) }EXPORT_SYMBOL (qdisc_reset); -void qdisc_free (struct Qdisc *qdisc) +static void qdisc_free_cb (struct rcu_head *head) { -if (qdisc_is_percpu_stats (qdisc)) { -free_percpu (qdisc->cpu_bstats); -free_percpu (qdisc->cpu_qstats); + struct Qdisc *q = container_of(head, struct Qdisc, rcu); + +if (qdisc_is_percpu_stats (q)) { +free_percpu (q->cpu_bstats); +free_percpu (q->cpu_qstats); } -kfree (qdisc); +kfree (q); } -static void qdisc_free_cb (struct rcu_head *head) +void qdisc_free_rcu (struct Qdisc *qdisc) { - struct Qdisc *q = container_of(head, struct Qdisc, rcu); - -qdisc_free (q); +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)
Signature · 1 line
2.55.0
This patch was applied to netdev/net.git (main) by Jakub Kicinski <[email protected]>:
Hello:
This patch was applied to netdev/net.git (main)
by Jakub Kicinski <[email protected]>:
Quoted 11 lines · On Wed, 2 Sep 2026 23:52:30 +0800 you wrote:
This is v2 of the qdisc failed-creation lifetime fix posted by Kyle Zeng and David Lee [1]. The public reproducer is available in a follow-up message [2].
When clsact binds a populated shared ingress block, ->init() can publish an embedded
mini_Qdiscbeforeqdisc_create()completes. A later invalidTCA_RATEmakes estimator setup fail. The unwind removes the published pointer but frees the containing qdisc synchronously whiletc_run()can still hold it.[...]
Here is the summary with links:
- [net,v2,1/1] net/sched: defer qdisc freeing after failed creation
https://git.kernel.org/netdev/net/c/e6662f2100f8
You are awesome, thank you!
Signature · 2 lines
Deet-doot-dot, I am a bot. https://korg.docs.kernel.org/patchwork/pwbot.html