From d3190ff134c94b2451c79ada57bf642a014eda5e Mon Sep 17 00:00:00 2001 From: Chengfeng Ye Date: Sat, 1 Aug 2026 00:09:21 +0800 Subject: [PATCH] bpf: Fix netns reference imbalance in conntrack kfuncs ANBZ: #44423 commit fdeba03fea78407a8c52faa99177c9f7f29f90eb upstream. 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. [backport-note] PatchPilot-Conflict-Type: context_drift, missing_prereq 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 Reviewed-by: Emil Tsalapatis Link: https://lore.kernel.org/bpf/20260731160921.3245840-1-nicoyip.dev@gmail.com Signed-off-by: Kumar Kartikeya Dwivedi Fixes: CVE-2026-74715 Assisted-by: PatchPilot Signed-off-by: D. Wythe --- net/netfilter/nf_conntrack_bpf.c | 49 ++++++++++++++++++++------------ 1 file changed, 31 insertions(+), 18 deletions(-) diff --git a/net/netfilter/nf_conntrack_bpf.c b/net/netfilter/nf_conntrack_bpf.c index d2492d050fe6..f28b66d2dfc3 100644 --- a/net/netfilter/nf_conntrack_bpf.c +++ b/net/netfilter/nf_conntrack_bpf.c @@ -37,7 +37,7 @@ * -ENONET - No network namespace found for netns_id * -ENOENT - Conntrack lookup could not find entry for tuple * -EAFNOSUPPORT - tuple__sz isn't one of sizeof(tuple->ipv4) - * or sizeof(tuple->ipv6) + * or sizeof(tuple->ipv6) * @l4proto - Layer 4 protocol * Values: * IPPROTO_TCP, IPPROTO_UDP @@ -100,32 +100,38 @@ static int bpf_nf_ct_tuple_parse(struct bpf_sock_tuple *bpf_tuple, static struct nf_conn * __bpf_nf_ct_alloc_entry(struct net *net, struct bpf_sock_tuple *bpf_tuple, - u32 tuple_len, struct bpf_ct_opts *opts, u32 opts_len, - u32 timeout) + u32 tuple_len, struct bpf_ct_opts *opts, u32 opts_len, + u32 timeout) { struct nf_conntrack_tuple otuple, rtuple; struct nf_conn *ct; + s32 netns_id; + u8 l4proto; int err; - if (!opts || !bpf_tuple || opts->reserved[0] || opts->reserved[1] || + if (!opts || !bpf_tuple || READ_ONCE(opts->reserved[0]) || + READ_ONCE(opts->reserved[1]) || opts_len != NF_BPF_CT_OPTS_SZ) return ERR_PTR(-EINVAL); - if (unlikely(opts->netns_id < BPF_F_CURRENT_NETNS)) + netns_id = READ_ONCE(opts->netns_id); + l4proto = READ_ONCE(opts->l4proto); + + 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); } @@ -139,7 +145,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; @@ -153,29 +159,36 @@ static struct nf_conn *__bpf_nf_ct_lookup(struct net *net, struct nf_conntrack_tuple_hash *hash; struct nf_conntrack_tuple tuple; struct nf_conn *ct; + s32 netns_id; + u8 l4proto; int err; - if (!opts || !bpf_tuple || opts->reserved[0] || opts->reserved[1] || + if (!opts || !bpf_tuple || READ_ONCE(opts->reserved[0]) || + READ_ONCE(opts->reserved[1]) || opts_len != NF_BPF_CT_OPTS_SZ) return ERR_PTR(-EINVAL); - if (unlikely(opts->l4proto != IPPROTO_TCP && opts->l4proto != IPPROTO_UDP)) + + netns_id = READ_ONCE(opts->netns_id); + l4proto = READ_ONCE(opts->l4proto); + + 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); } hash = nf_conntrack_find_get(net, &nf_ct_zone_dflt, &tuple); - if (opts->netns_id >= 0) + if (netns_id >= 0) put_net(net); if (!hash) return ERR_PTR(-ENOENT); @@ -236,7 +249,7 @@ __bpf_kfunc_start_defs(); * * Parameters: * @xdp_ctx - Pointer to ctx (xdp_md) in XDP program - * Cannot be NULL + * - Cannot be NULL * @bpf_tuple - Pointer to memory representing the tuple to look up * Cannot be NULL * @tuple__sz - Length of the tuple structure -- Gitee