]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
clsact: Fix use-after-free in init/destroy rollback asymmetry
authorDaniel Borkmann <daniel@iogearbox.net>
Fri, 13 Mar 2026 06:55:31 +0000 (07:55 +0100)
committerPaolo Abeni <pabeni@redhat.com>
Tue, 17 Mar 2026 11:09:16 +0000 (12:09 +0100)
Fix a use-after-free in the clsact qdisc upon init/destroy rollback asymmetry.
The latter is achieved by first fully initializing a clsact instance, and
then in a second step having a replacement failure for the new clsact qdisc
instance. clsact_init() initializes ingress first and then takes care of the
egress part. This can fail midway, for example, via tcf_block_get_ext(). Upon
failure, the kernel will trigger the clsact_destroy() callback.

Commit 1cb6f0bae504 ("bpf: Fix too early release of tcx_entry") details the
way how the transition is happening. If tcf_block_get_ext on the q->ingress_block
ends up failing, we took the tcx_miniq_inc reference count on the ingress
side, but not yet on the egress side. clsact_destroy() tests whether the
{ingress,egress}_entry was non-NULL. However, even in midway failure on the
replacement, both are in fact non-NULL with a valid egress_entry from the
previous clsact instance.

What we really need to test for is whether the qdisc instance-specific ingress
or egress side previously got initialized. This adds a small helper for checking
the miniq initialization called mini_qdisc_pair_inited, and utilizes that upon
clsact_destroy() in order to fix the use-after-free scenario. Convert the
ingress_destroy() side as well so both are consistent to each other.

Fixes: 1cb6f0bae504 ("bpf: Fix too early release of tcx_entry")
Reported-by: Keenan Dong <keenanat2000@gmail.com>
Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
Cc: Martin KaFai Lau <martin.lau@kernel.org>
Acked-by: Martin KaFai Lau <martin.lau@kernel.org>
Link: https://patch.msgid.link/20260313065531.98639-1-daniel@iogearbox.net
Signed-off-by: Paolo Abeni <pabeni@redhat.com>
include/net/sch_generic.h
net/sched/sch_ingress.c

index cafb266a0b80db98a78b8ee85b6c93eb384d06a0..c3d657359a3d2d9d5eef72d3792b0d258b572ae1 100644 (file)
@@ -1457,6 +1457,11 @@ void mini_qdisc_pair_init(struct mini_Qdisc_pair *miniqp, struct Qdisc *qdisc,
 void mini_qdisc_pair_block_init(struct mini_Qdisc_pair *miniqp,
                                struct tcf_block *block);
 
+static inline bool mini_qdisc_pair_inited(struct mini_Qdisc_pair *miniqp)
+{
+       return !!miniqp->p_miniq;
+}
+
 void mq_change_real_num_tx(struct Qdisc *sch, unsigned int new_real_tx);
 
 int sch_frag_xmit_hook(struct sk_buff *skb, int (*xmit)(struct sk_buff *skb));
index cc6051d4f2ef8883c12738fb22be451469ac7c39..c3e18bae8fbfc70cdcee70217e50a5b6d3c109d3 100644 (file)
@@ -113,14 +113,15 @@ static void ingress_destroy(struct Qdisc *sch)
 {
        struct ingress_sched_data *q = qdisc_priv(sch);
        struct net_device *dev = qdisc_dev(sch);
-       struct bpf_mprog_entry *entry = rtnl_dereference(dev->tcx_ingress);
+       struct bpf_mprog_entry *entry;
 
        if (sch->parent != TC_H_INGRESS)
                return;
 
        tcf_block_put_ext(q->block, sch, &q->block_info);
 
-       if (entry) {
+       if (mini_qdisc_pair_inited(&q->miniqp)) {
+               entry = rtnl_dereference(dev->tcx_ingress);
                tcx_miniq_dec(entry);
                if (!tcx_entry_is_active(entry)) {
                        tcx_entry_update(dev, NULL, true);
@@ -290,10 +291,9 @@ static int clsact_init(struct Qdisc *sch, struct nlattr *opt,
 
 static void clsact_destroy(struct Qdisc *sch)
 {
+       struct bpf_mprog_entry *ingress_entry, *egress_entry;
        struct clsact_sched_data *q = qdisc_priv(sch);
        struct net_device *dev = qdisc_dev(sch);
-       struct bpf_mprog_entry *ingress_entry = rtnl_dereference(dev->tcx_ingress);
-       struct bpf_mprog_entry *egress_entry = rtnl_dereference(dev->tcx_egress);
 
        if (sch->parent != TC_H_CLSACT)
                return;
@@ -301,7 +301,8 @@ static void clsact_destroy(struct Qdisc *sch)
        tcf_block_put_ext(q->ingress_block, sch, &q->ingress_block_info);
        tcf_block_put_ext(q->egress_block, sch, &q->egress_block_info);
 
-       if (ingress_entry) {
+       if (mini_qdisc_pair_inited(&q->miniqp_ingress)) {
+               ingress_entry = rtnl_dereference(dev->tcx_ingress);
                tcx_miniq_dec(ingress_entry);
                if (!tcx_entry_is_active(ingress_entry)) {
                        tcx_entry_update(dev, NULL, true);
@@ -309,7 +310,8 @@ static void clsact_destroy(struct Qdisc *sch)
                }
        }
 
-       if (egress_entry) {
+       if (mini_qdisc_pair_inited(&q->miniqp_egress)) {
+               egress_entry = rtnl_dereference(dev->tcx_egress);
                tcx_miniq_dec(egress_entry);
                if (!tcx_entry_is_active(egress_entry)) {
                        tcx_entry_update(dev, NULL, false);