Re: [PATCH bpf] bpf: Fix netns reference imbalance in conntrack kfuncs
From: Emil Tsalapatis
Date: Thu Jul 30 2026 - 02:48:09 EST
On Wed Jul 29, 2026 at 12:31 PM EDT, Chengfeng Ye wrote:
> 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
>
> Read netns_id once with READ_ONCE() and use that value for validation
> and the matching get/put pair. Each invocation then consistently uses
> either the calling namespace or a referenced namespace.
>
> 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@xxxxxxxxx>
Sashiko is right on this regarding all other fields of opts having the
same issue. While they can't lead to a crash, let's still snapshot them
at the beginning of the function so they don't change from under us.
This is still far from ideal - we may read an inconsistent snapshot of
the struct - but it's backed by BPF program-controlled memory so AFAICT
this is the best we can do.
pw-bot: cr
> ---
> Please queue this fix for stable kernels.
>
> net/netfilter/nf_conntrack_bpf.c | 20 ++++++++++++--------
> 1 file changed, 12 insertions(+), 8 deletions(-)
>
> diff --git a/net/netfilter/nf_conntrack_bpf.c b/net/netfilter/nf_conntrack_bpf.c
> index f98d1d4b42c3..8b540846f299 100644
> --- a/net/netfilter/nf_conntrack_bpf.c
> +++ b/net/netfilter/nf_conntrack_bpf.c
> @@ -122,6 +122,7 @@ __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;
> + s32 netns_id;
> int err;
>
> if (!(opts_len == NF_BPF_CT_OPTS_SZ || opts_len == 12))
> @@ -134,7 +135,8 @@ __bpf_nf_ct_alloc_entry(struct net *net, struct bpf_sock_tuple *bpf_tuple,
> return ERR_PTR(-EINVAL);
> }
>
> - if (unlikely(opts->netns_id < BPF_F_CURRENT_NETNS))
> + netns_id = READ_ONCE(opts->netns_id);
> + if (unlikely(netns_id < BPF_F_CURRENT_NETNS))
> return ERR_PTR(-EINVAL);
>
> err = bpf_nf_ct_tuple_parse(bpf_tuple, tuple_len, opts->l4proto,
> @@ -147,8 +149,8 @@ __bpf_nf_ct_alloc_entry(struct net *net, struct bpf_sock_tuple *bpf_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);
> }
> @@ -171,7 +173,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,6 +188,7 @@ 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;
> + s32 netns_id;
> int err;
>
> if (!opts || !bpf_tuple)
> @@ -201,7 +204,8 @@ static struct nf_conn *__bpf_nf_ct_lookup(struct net *net,
> }
> if (unlikely(opts->l4proto != IPPROTO_TCP && opts->l4proto != IPPROTO_UDP))
> return ERR_PTR(-EPROTO);
> - if (unlikely(opts->netns_id < BPF_F_CURRENT_NETNS))
> + netns_id = READ_ONCE(opts->netns_id);
> + if (unlikely(netns_id < BPF_F_CURRENT_NETNS))
> return ERR_PTR(-EINVAL);
>
> err = bpf_nf_ct_tuple_parse(bpf_tuple, tuple_len, opts->l4proto,
> @@ -209,8 +213,8 @@ static struct nf_conn *__bpf_nf_ct_lookup(struct net *net,
> 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);
> }
> @@ -225,7 +229,7 @@ static struct nf_conn *__bpf_nf_ct_lookup(struct net *net,
> }
>
> 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);