]> git.ipfire.org Git - thirdparty/kernel/stable.git/commitdiff
netfilter: ctnetlink: remove refcounting in expectation dumpers
authorFlorian Westphal <fw@strlen.de>
Fri, 1 Aug 2025 15:25:09 +0000 (17:25 +0200)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Wed, 20 Aug 2025 16:40:50 +0000 (18:40 +0200)
[ Upstream commit 1492e3dcb2be3aa46d1963da96aa9593e4e4db5a ]

Same pattern as previous patch: do not keep the expectation object
alive via refcount, only store a cookie value and then use that
as the skip hint for dump resumption.

AFAICS this has the same issue as the one resolved in the conntrack
dumper, when we do
  if (!refcount_inc_not_zero(&exp->use))

to increment the refcount, there is a chance that exp == last, which
causes a double-increment of the refcount and subsequent memory leak.

Fixes: cf6994c2b981 ("[NETFILTER]: nf_conntrack_netlink: sync expectation dumping with conntrack table dumping")
Fixes: e844a928431f ("netfilter: ctnetlink: allow to dump expectation per master conntrack")
Signed-off-by: Florian Westphal <fw@strlen.de>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
Signed-off-by: Sasha Levin <sashal@kernel.org>
net/netfilter/nf_conntrack_netlink.c

index 5fdcae45e0bc492d3c14924c4fad4559cc4fb958..2273ead8102f83eb50e2c8213d8f4bd2d3743036 100644 (file)
@@ -3171,23 +3171,27 @@ errout:
        return 0;
 }
 #endif
-static int ctnetlink_exp_done(struct netlink_callback *cb)
+
+static unsigned long ctnetlink_exp_id(const struct nf_conntrack_expect *exp)
 {
-       if (cb->args[1])
-               nf_ct_expect_put((struct nf_conntrack_expect *)cb->args[1]);
-       return 0;
+       unsigned long id = (unsigned long)exp;
+
+       id += nf_ct_get_id(exp->master);
+       id += exp->class;
+
+       return id ? id : 1;
 }
 
 static int
 ctnetlink_exp_dump_table(struct sk_buff *skb, struct netlink_callback *cb)
 {
        struct net *net = sock_net(skb->sk);
-       struct nf_conntrack_expect *exp, *last;
        struct nfgenmsg *nfmsg = nlmsg_data(cb->nlh);
        u_int8_t l3proto = nfmsg->nfgen_family;
+       unsigned long last_id = cb->args[1];
+       struct nf_conntrack_expect *exp;
 
        rcu_read_lock();
-       last = (struct nf_conntrack_expect *)cb->args[1];
        for (; cb->args[0] < nf_ct_expect_hsize; cb->args[0]++) {
 restart:
                hlist_for_each_entry_rcu(exp, &nf_ct_expect_hash[cb->args[0]],
@@ -3199,7 +3203,7 @@ restart:
                                continue;
 
                        if (cb->args[1]) {
-                               if (exp != last)
+                               if (ctnetlink_exp_id(exp) != last_id)
                                        continue;
                                cb->args[1] = 0;
                        }
@@ -3208,9 +3212,7 @@ restart:
                                                    cb->nlh->nlmsg_seq,
                                                    IPCTNL_MSG_EXP_NEW,
                                                    exp) < 0) {
-                               if (!refcount_inc_not_zero(&exp->use))
-                                       continue;
-                               cb->args[1] = (unsigned long)exp;
+                               cb->args[1] = ctnetlink_exp_id(exp);
                                goto out;
                        }
                }
@@ -3221,32 +3223,30 @@ restart:
        }
 out:
        rcu_read_unlock();
-       if (last)
-               nf_ct_expect_put(last);
-
        return skb->len;
 }
 
 static int
 ctnetlink_exp_ct_dump_table(struct sk_buff *skb, struct netlink_callback *cb)
 {
-       struct nf_conntrack_expect *exp, *last;
        struct nfgenmsg *nfmsg = nlmsg_data(cb->nlh);
        struct nf_conn *ct = cb->data;
        struct nf_conn_help *help = nfct_help(ct);
        u_int8_t l3proto = nfmsg->nfgen_family;
+       unsigned long last_id = cb->args[1];
+       struct nf_conntrack_expect *exp;
 
        if (cb->args[0])
                return 0;
 
        rcu_read_lock();
-       last = (struct nf_conntrack_expect *)cb->args[1];
+
 restart:
        hlist_for_each_entry_rcu(exp, &help->expectations, lnode) {
                if (l3proto && exp->tuple.src.l3num != l3proto)
                        continue;
                if (cb->args[1]) {
-                       if (exp != last)
+                       if (ctnetlink_exp_id(exp) != last_id)
                                continue;
                        cb->args[1] = 0;
                }
@@ -3254,9 +3254,7 @@ restart:
                                            cb->nlh->nlmsg_seq,
                                            IPCTNL_MSG_EXP_NEW,
                                            exp) < 0) {
-                       if (!refcount_inc_not_zero(&exp->use))
-                               continue;
-                       cb->args[1] = (unsigned long)exp;
+                       cb->args[1] = ctnetlink_exp_id(exp);
                        goto out;
                }
        }
@@ -3267,9 +3265,6 @@ restart:
        cb->args[0] = 1;
 out:
        rcu_read_unlock();
-       if (last)
-               nf_ct_expect_put(last);
-
        return skb->len;
 }
 
@@ -3288,7 +3283,6 @@ static int ctnetlink_dump_exp_ct(struct net *net, struct sock *ctnl,
        struct nf_conntrack_zone zone;
        struct netlink_dump_control c = {
                .dump = ctnetlink_exp_ct_dump_table,
-               .done = ctnetlink_exp_done,
        };
 
        err = ctnetlink_parse_tuple(cda, &tuple, CTA_EXPECT_MASTER,
@@ -3338,7 +3332,6 @@ static int ctnetlink_get_expect(struct sk_buff *skb,
                else {
                        struct netlink_dump_control c = {
                                .dump = ctnetlink_exp_dump_table,
-                               .done = ctnetlink_exp_done,
                        };
                        return netlink_dump_start(info->sk, skb, info->nlh, &c);
                }