]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
bpf: Fix netns reference imbalance in conntrack kfuncs
authorChengfeng Ye <nicoyip.dev@gmail.com>
Fri, 31 Jul 2026 16:09:21 +0000 (00:09 +0800)
committerKumar Kartikeya Dwivedi <memxor@gmail.com>
Sat, 1 Aug 2026 18:45:24 +0000 (20:45 +0200)
The opts argument of the BPF conntrack kfuncs can point to a shared
map value.  __bpf_nf_ct_lookup() and __bpf_nf_ct_alloc_entry() read
opts->netns_id separately when acquiring and releasing the network
namespace reference.

The reference imbalance can occur as follows:

  CPU 0                                  CPU 1
  read opts->netns_id (-1)
  skip get_net_ns_by_id()
                                         write opts->netns_id (id)
  read opts->netns_id (id)
  put_net(net) /* no matching get */

The reverse transition leaks the reference.  Repeating the unmatched put
can destroy a live namespace and crash later users.

The kernel reported:

  Oops: general protection fault, probably for non-canonical address
  KASAN: null-ptr-deref in range [0x00000000000000e8-0x00000000000000ef]
  RIP: 0010:bpf_prog_test_run_xdp+0x52c/0x1700
  Call Trace:
   __sys_bpf+0x1662/0x50c0
   __x64_sys_bpf+0x73/0xb0
   do_syscall_64+0xf9/0x540
   entry_SYSCALL_64_after_hwframe+0x77/0x7f
  Kernel panic - not syncing: Fatal exception

Snapshot every input field of opts with READ_ONCE() before validating or
using it.  The netns_id snapshot keeps the namespace get/put pair
balanced, while the other snapshots keep the remaining options from
changing partway through an invocation.  The individual reads can still
observe an inconsistent combination during a concurrent update, but each
selected field value remains stable for that invocation.

Fixes: aed8ee7feb44 ("net: netfilter: Deduplicate code in bpf_{xdp,skb}_ct_lookup")
Fixes: d7e79c97c00c ("net: netfilter: Add kfuncs to allocate and insert CT")
Signed-off-by: Chengfeng Ye <nicoyip.dev@gmail.com>
Reviewed-by: Emil Tsalapatis <emil@etsalapatis.com>
Link: https://lore.kernel.org/bpf/20260731160921.3245840-1-nicoyip.dev@gmail.com
Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
net/netfilter/nf_conntrack_bpf.c

index f98d1d4b42c3d6bf6b6e4c39086ab6ac1896b449..c2df7c9482812d74fb05c39f7c56e8521951cb09 100644 (file)
@@ -122,42 +122,54 @@ __bpf_nf_ct_alloc_entry(struct net *net, struct bpf_sock_tuple *bpf_tuple,
        struct nf_conntrack_tuple otuple, rtuple;
        struct nf_conntrack_zone ct_zone;
        struct nf_conn *ct;
+       u8 ct_zone_dir = 0;
+       u16 ct_zone_id;
+       s32 netns_id;
+       u8 l4proto;
        int err;
 
        if (!(opts_len == NF_BPF_CT_OPTS_SZ || opts_len == 12))
                return ERR_PTR(-EINVAL);
+
+       netns_id = READ_ONCE(opts->netns_id);
+       l4proto = READ_ONCE(opts->l4proto);
+       ct_zone_id = READ_ONCE(opts->ct_zone_id);
        if (opts_len == NF_BPF_CT_OPTS_SZ) {
-               if (opts->reserved[0] || opts->reserved[1] || opts->reserved[2])
+               ct_zone_dir = READ_ONCE(opts->ct_zone_dir);
+               if (READ_ONCE(opts->reserved[0]) ||
+                   READ_ONCE(opts->reserved[1]) ||
+                   READ_ONCE(opts->reserved[2]))
                        return ERR_PTR(-EINVAL);
        } else {
-               if (opts->ct_zone_id)
+               if (ct_zone_id)
                        return ERR_PTR(-EINVAL);
        }
 
-       if (unlikely(opts->netns_id < BPF_F_CURRENT_NETNS))
+       if (unlikely(netns_id < BPF_F_CURRENT_NETNS))
                return ERR_PTR(-EINVAL);
 
-       err = bpf_nf_ct_tuple_parse(bpf_tuple, tuple_len, opts->l4proto,
+       err = bpf_nf_ct_tuple_parse(bpf_tuple, tuple_len, l4proto,
                                    IP_CT_DIR_ORIGINAL, &otuple);
        if (err < 0)
                return ERR_PTR(err);
 
-       err = bpf_nf_ct_tuple_parse(bpf_tuple, tuple_len, opts->l4proto,
+       err = bpf_nf_ct_tuple_parse(bpf_tuple, tuple_len, l4proto,
                                    IP_CT_DIR_REPLY, &rtuple);
        if (err < 0)
                return ERR_PTR(err);
 
-       if (opts->netns_id >= 0) {
-               net = get_net_ns_by_id(net, opts->netns_id);
+       if (netns_id >= 0) {
+               net = get_net_ns_by_id(net, netns_id);
                if (unlikely(!net))
                        return ERR_PTR(-ENONET);
        }
 
        if (opts_len == NF_BPF_CT_OPTS_SZ) {
-               if (opts->ct_zone_dir == 0)
-                       opts->ct_zone_dir = NF_CT_DEFAULT_ZONE_DIR;
-               nf_ct_zone_init(&ct_zone,
-                               opts->ct_zone_id, opts->ct_zone_dir, 0);
+               if (ct_zone_dir == 0) {
+                       ct_zone_dir = NF_CT_DEFAULT_ZONE_DIR;
+                       opts->ct_zone_dir = ct_zone_dir;
+               }
+               nf_ct_zone_init(&ct_zone, ct_zone_id, ct_zone_dir, 0);
        } else {
                ct_zone = nf_ct_zone_dflt;
        }
@@ -171,7 +183,7 @@ __bpf_nf_ct_alloc_entry(struct net *net, struct bpf_sock_tuple *bpf_tuple,
        __nf_ct_set_timeout(ct, timeout * HZ);
 
 out:
-       if (opts->netns_id >= 0)
+       if (netns_id >= 0)
                put_net(net);
 
        return ct;
@@ -186,46 +198,58 @@ static struct nf_conn *__bpf_nf_ct_lookup(struct net *net,
        struct nf_conntrack_tuple tuple;
        struct nf_conntrack_zone ct_zone;
        struct nf_conn *ct;
+       u8 ct_zone_dir = 0;
+       u16 ct_zone_id;
+       s32 netns_id;
+       u8 l4proto;
        int err;
 
        if (!opts || !bpf_tuple)
                return ERR_PTR(-EINVAL);
        if (!(opts_len == NF_BPF_CT_OPTS_SZ || opts_len == 12))
                return ERR_PTR(-EINVAL);
+
+       netns_id = READ_ONCE(opts->netns_id);
+       l4proto = READ_ONCE(opts->l4proto);
+       ct_zone_id = READ_ONCE(opts->ct_zone_id);
        if (opts_len == NF_BPF_CT_OPTS_SZ) {
-               if (opts->reserved[0] || opts->reserved[1] || opts->reserved[2])
+               ct_zone_dir = READ_ONCE(opts->ct_zone_dir);
+               if (READ_ONCE(opts->reserved[0]) ||
+                   READ_ONCE(opts->reserved[1]) ||
+                   READ_ONCE(opts->reserved[2]))
                        return ERR_PTR(-EINVAL);
        } else {
-               if (opts->ct_zone_id)
+               if (ct_zone_id)
                        return ERR_PTR(-EINVAL);
        }
-       if (unlikely(opts->l4proto != IPPROTO_TCP && opts->l4proto != IPPROTO_UDP))
+       if (unlikely(l4proto != IPPROTO_TCP && l4proto != IPPROTO_UDP))
                return ERR_PTR(-EPROTO);
-       if (unlikely(opts->netns_id < BPF_F_CURRENT_NETNS))
+       if (unlikely(netns_id < BPF_F_CURRENT_NETNS))
                return ERR_PTR(-EINVAL);
 
-       err = bpf_nf_ct_tuple_parse(bpf_tuple, tuple_len, opts->l4proto,
+       err = bpf_nf_ct_tuple_parse(bpf_tuple, tuple_len, l4proto,
                                    IP_CT_DIR_ORIGINAL, &tuple);
        if (err < 0)
                return ERR_PTR(err);
 
-       if (opts->netns_id >= 0) {
-               net = get_net_ns_by_id(net, opts->netns_id);
+       if (netns_id >= 0) {
+               net = get_net_ns_by_id(net, netns_id);
                if (unlikely(!net))
                        return ERR_PTR(-ENONET);
        }
 
        if (opts_len == NF_BPF_CT_OPTS_SZ) {
-               if (opts->ct_zone_dir == 0)
-                       opts->ct_zone_dir = NF_CT_DEFAULT_ZONE_DIR;
-               nf_ct_zone_init(&ct_zone,
-                               opts->ct_zone_id, opts->ct_zone_dir, 0);
+               if (ct_zone_dir == 0) {
+                       ct_zone_dir = NF_CT_DEFAULT_ZONE_DIR;
+                       opts->ct_zone_dir = ct_zone_dir;
+               }
+               nf_ct_zone_init(&ct_zone, ct_zone_id, ct_zone_dir, 0);
        } else {
                ct_zone = nf_ct_zone_dflt;
        }
 
        hash = nf_conntrack_find_get(net, &ct_zone, &tuple);
-       if (opts->netns_id >= 0)
+       if (netns_id >= 0)
                put_net(net);
        if (!hash)
                return ERR_PTR(-ENOENT);