From ec1806a730a1c0b3d68a7f9afe81514fb0dd7991 Mon Sep 17 00:00:00 2001 From: Florian Westphal Date: Sat, 25 Apr 2026 15:00:50 +0200 Subject: [PATCH 01/11] netfilter: x_tables: disable 32bit compat interface in user namespaces This feature is required to use 32bit arp/ip/ip6/ebtables binaries on 64bit kernels. I don't think there are many users left. Support has been a compile-time option since 2021 and defaults to off since 2023. The XTABLES_COMPAT config option is already off in many distributions including Debian and Fedora. Give a few more months before complete removal but disable support in user namespaces already. Assisted-by: Claude:claude-sonnet-4-6 Signed-off-by: Florian Westphal --- include/linux/netfilter/x_tables.h | 17 +++++++++++++++++ net/bridge/netfilter/ebtables.c | 4 ++++ net/ipv4/netfilter/arp_tables.c | 4 ++++ net/ipv4/netfilter/ip_tables.c | 4 ++++ net/ipv6/netfilter/ip6_tables.c | 4 ++++ 5 files changed, 33 insertions(+) diff --git a/include/linux/netfilter/x_tables.h b/include/linux/netfilter/x_tables.h index 5a1c5c336fa4..20d70dddbe50 100644 --- a/include/linux/netfilter/x_tables.h +++ b/include/linux/netfilter/x_tables.h @@ -534,4 +534,21 @@ int xt_compat_check_entry_offsets(const void *base, const char *elems, unsigned int next_offset); #endif /* CONFIG_NETFILTER_XTABLES_COMPAT */ + +static inline bool xt_compat_check(void) +{ +#ifdef CONFIG_NETFILTER_XTABLES_COMPAT + if (!in_compat_syscall()) + return true; + + pr_warn_once("%s %s\n", + "xtables 32bit compat interface no longer supported", + "in namespaces and will be removed soon."); + + if (!capable(CAP_NET_ADMIN)) + return false; +#endif + return true; +} + #endif /* _X_TABLES_H */ diff --git a/net/bridge/netfilter/ebtables.c b/net/bridge/netfilter/ebtables.c index b9f4daac09af..8d8f1a7c9ad5 100644 --- a/net/bridge/netfilter/ebtables.c +++ b/net/bridge/netfilter/ebtables.c @@ -2455,6 +2455,8 @@ static int do_ebt_get_ctl(struct sock *sk, int cmd, void __user *user, int *len) if (!ns_capable(net->user_ns, CAP_NET_ADMIN)) return -EPERM; + if (!xt_compat_check()) + return -EPERM; #ifdef CONFIG_NETFILTER_XTABLES_COMPAT /* try real handler in case userland supplied needed padding */ @@ -2520,6 +2522,8 @@ static int do_ebt_set_ctl(struct sock *sk, int cmd, sockptr_t arg, if (!ns_capable(net->user_ns, CAP_NET_ADMIN)) return -EPERM; + if (!xt_compat_check()) + return -EPERM; switch (cmd) { case EBT_SO_SET_ENTRIES: diff --git a/net/ipv4/netfilter/arp_tables.c b/net/ipv4/netfilter/arp_tables.c index ad2259678c78..341ae049e5a2 100644 --- a/net/ipv4/netfilter/arp_tables.c +++ b/net/ipv4/netfilter/arp_tables.c @@ -1430,6 +1430,8 @@ static int do_arpt_set_ctl(struct sock *sk, int cmd, sockptr_t arg, if (!ns_capable(sock_net(sk)->user_ns, CAP_NET_ADMIN)) return -EPERM; + if (!xt_compat_check()) + return -EPERM; switch (cmd) { case ARPT_SO_SET_REPLACE: @@ -1458,6 +1460,8 @@ static int do_arpt_get_ctl(struct sock *sk, int cmd, void __user *user, int *len if (!ns_capable(sock_net(sk)->user_ns, CAP_NET_ADMIN)) return -EPERM; + if (!xt_compat_check()) + return -EPERM; switch (cmd) { case ARPT_SO_GET_INFO: diff --git a/net/ipv4/netfilter/ip_tables.c b/net/ipv4/netfilter/ip_tables.c index 5cbdb0815857..f917a9004a01 100644 --- a/net/ipv4/netfilter/ip_tables.c +++ b/net/ipv4/netfilter/ip_tables.c @@ -1624,6 +1624,8 @@ do_ipt_set_ctl(struct sock *sk, int cmd, sockptr_t arg, unsigned int len) if (!ns_capable(sock_net(sk)->user_ns, CAP_NET_ADMIN)) return -EPERM; + if (!xt_compat_check()) + return -EPERM; switch (cmd) { case IPT_SO_SET_REPLACE: @@ -1653,6 +1655,8 @@ do_ipt_get_ctl(struct sock *sk, int cmd, void __user *user, int *len) if (!ns_capable(sock_net(sk)->user_ns, CAP_NET_ADMIN)) return -EPERM; + if (!xt_compat_check()) + return -EPERM; switch (cmd) { case IPT_SO_GET_INFO: diff --git a/net/ipv6/netfilter/ip6_tables.c b/net/ipv6/netfilter/ip6_tables.c index 9d9c3763f2f5..ecf79d05a51b 100644 --- a/net/ipv6/netfilter/ip6_tables.c +++ b/net/ipv6/netfilter/ip6_tables.c @@ -1633,6 +1633,8 @@ do_ip6t_set_ctl(struct sock *sk, int cmd, sockptr_t arg, unsigned int len) if (!ns_capable(sock_net(sk)->user_ns, CAP_NET_ADMIN)) return -EPERM; + if (!xt_compat_check()) + return -EPERM; switch (cmd) { case IP6T_SO_SET_REPLACE: @@ -1662,6 +1664,8 @@ do_ip6t_get_ctl(struct sock *sk, int cmd, void __user *user, int *len) if (!ns_capable(sock_net(sk)->user_ns, CAP_NET_ADMIN)) return -EPERM; + if (!xt_compat_check()) + return -EPERM; switch (cmd) { case IP6T_SO_GET_INFO: From 403cec8ab6d002ee26b8345cfd83e58ca3b0606d Mon Sep 17 00:00:00 2001 From: Florian Westphal Date: Thu, 7 May 2026 11:34:15 +0200 Subject: [PATCH 02/11] netfilter: add option for GCOV profiling Similar to a few other subsystems: add a new config toggle to enable netfilter gcov profiling in netfilter, including ebtables, arptables and so on. ipset and ipvs gain their own, dedicated toggles. Acked-by: Julian Anastasov Signed-off-by: Florian Westphal --- net/bridge/Makefile | 6 ++++++ net/bridge/netfilter/Makefile | 4 ++++ net/ipv4/Makefile | 4 ++++ net/ipv4/netfilter/Makefile | 4 ++++ net/ipv6/Makefile | 4 ++++ net/ipv6/netfilter/Makefile | 4 ++++ net/netfilter/Kconfig | 8 ++++++++ net/netfilter/Makefile | 4 ++++ net/netfilter/ipset/Kconfig | 9 +++++++++ net/netfilter/ipset/Makefile | 3 +++ net/netfilter/ipvs/Kconfig | 9 +++++++++ net/netfilter/ipvs/Makefile | 3 +++ 12 files changed, 62 insertions(+) diff --git a/net/bridge/Makefile b/net/bridge/Makefile index 24bd1c0a9a5a..1203dc19e15c 100644 --- a/net/bridge/Makefile +++ b/net/bridge/Makefile @@ -29,3 +29,9 @@ obj-$(CONFIG_NETFILTER) += netfilter/ bridge-$(CONFIG_BRIDGE_MRP) += br_mrp_switchdev.o br_mrp.o br_mrp_netlink.o bridge-$(CONFIG_BRIDGE_CFM) += br_cfm.o br_cfm_netlink.o + +ifdef CONFIG_GCOV_PROFILE_NETFILTER +GCOV_PROFILE_br_nf_core.o := y +GCOV_PROFILE_br_netfilter_hooks.o := y +GCOV_PROFILE_br_netfilter_ipv6.o := y +endif diff --git a/net/bridge/netfilter/Makefile b/net/bridge/netfilter/Makefile index b9a1303da977..af0c903aa4ac 100644 --- a/net/bridge/netfilter/Makefile +++ b/net/bridge/netfilter/Makefile @@ -38,3 +38,7 @@ obj-$(CONFIG_BRIDGE_EBT_SNAT) += ebt_snat.o # watchers obj-$(CONFIG_BRIDGE_EBT_LOG) += ebt_log.o obj-$(CONFIG_BRIDGE_EBT_NFLOG) += ebt_nflog.o + +ifdef CONFIG_GCOV_PROFILE_NETFILTER +GCOV_PROFILE := y +endif diff --git a/net/ipv4/Makefile b/net/ipv4/Makefile index 7964234f0d08..06e21c26b76f 100644 --- a/net/ipv4/Makefile +++ b/net/ipv4/Makefile @@ -71,3 +71,7 @@ obj-$(CONFIG_TCP_AO) += tcp_ao.o ifeq ($(CONFIG_BPF_JIT),y) obj-$(CONFIG_BPF_SYSCALL) += bpf_tcp_ca.o endif + +ifdef CONFIG_GCOV_PROFILE_NETFILTER +GCOV_PROFILE_netfilter.o := y +endif diff --git a/net/ipv4/netfilter/Makefile b/net/ipv4/netfilter/Makefile index 85502d4dfbb4..dbfb1c4739a8 100644 --- a/net/ipv4/netfilter/Makefile +++ b/net/ipv4/netfilter/Makefile @@ -51,3 +51,7 @@ obj-$(CONFIG_IP_NF_ARP_MANGLE) += arpt_mangle.o obj-$(CONFIG_IP_NF_ARPFILTER) += arptable_filter.o obj-$(CONFIG_NF_DUP_IPV4) += nf_dup_ipv4.o + +ifdef CONFIG_GCOV_PROFILE_NETFILTER +GCOV_PROFILE := y +endif diff --git a/net/ipv6/Makefile b/net/ipv6/Makefile index 2c9ce2ccbde1..5b0cd6488021 100644 --- a/net/ipv6/Makefile +++ b/net/ipv6/Makefile @@ -54,3 +54,7 @@ obj-$(CONFIG_NET_UDP_TUNNEL) += ip6_udp_tunnel.o obj-y += mcast_snoop.o obj-$(CONFIG_TCP_AO) += tcp_ao.o endif + +ifdef CONFIG_GCOV_PROFILE_NETFILTER +GCOV_PROFILE_netfilter.o := y +endif diff --git a/net/ipv6/netfilter/Makefile b/net/ipv6/netfilter/Makefile index 66ce6fa5b2f5..72902d8005ad 100644 --- a/net/ipv6/netfilter/Makefile +++ b/net/ipv6/netfilter/Makefile @@ -43,3 +43,7 @@ obj-$(CONFIG_IP6_NF_MATCH_SRH) += ip6t_srh.o obj-$(CONFIG_IP6_NF_TARGET_NPT) += ip6t_NPT.o obj-$(CONFIG_IP6_NF_TARGET_REJECT) += ip6t_REJECT.o obj-$(CONFIG_IP6_NF_TARGET_SYNPROXY) += ip6t_SYNPROXY.o + +ifdef CONFIG_GCOV_PROFILE_NETFILTER +GCOV_PROFILE := y +endif diff --git a/net/netfilter/Kconfig b/net/netfilter/Kconfig index 682c675125fc..f71ff98eb5d0 100644 --- a/net/netfilter/Kconfig +++ b/net/netfilter/Kconfig @@ -1648,6 +1648,14 @@ config NETFILTER_XT_MATCH_U32 endif # NETFILTER_XTABLES +config GCOV_PROFILE_NETFILTER + bool "Enable GCOV profiling for netfilter" + depends on GCOV_KERNEL + help + Enable GCOV profiling for netfilter to check which functions/lines + are executed. + + If unsure, say N. endmenu source "net/netfilter/ipset/Kconfig" diff --git a/net/netfilter/Makefile b/net/netfilter/Makefile index 6bfc250e474f..f0751ca302c6 100644 --- a/net/netfilter/Makefile +++ b/net/netfilter/Makefile @@ -240,3 +240,7 @@ obj-$(CONFIG_IP_VS) += ipvs/ # lwtunnel obj-$(CONFIG_LWTUNNEL) += nf_hooks_lwtunnel.o + +ifdef CONFIG_GCOV_PROFILE_NETFILTER +GCOV_PROFILE := y +endif diff --git a/net/netfilter/ipset/Kconfig b/net/netfilter/ipset/Kconfig index b1ea054bb82c..6c4d54758106 100644 --- a/net/netfilter/ipset/Kconfig +++ b/net/netfilter/ipset/Kconfig @@ -175,4 +175,13 @@ config IP_SET_LIST_SET To compile it as a module, choose M here. If unsure, say N. +config GCOV_PROFILE_IPSET + bool "Enable GCOV profiling for ipset" + depends on GCOV_KERNEL + help + Enable GCOV profiling for ipset to check which functions/lines + are executed. + + If unsure, say N. + endif # IP_SET diff --git a/net/netfilter/ipset/Makefile b/net/netfilter/ipset/Makefile index a445a6bf4f11..4f48df5406cd 100644 --- a/net/netfilter/ipset/Makefile +++ b/net/netfilter/ipset/Makefile @@ -29,3 +29,6 @@ obj-$(CONFIG_IP_SET_HASH_NETPORTNET) += ip_set_hash_netportnet.o # list types obj-$(CONFIG_IP_SET_LIST_SET) += ip_set_list_set.o +ifdef CONFIG_GCOV_PROFILE_IPSET +GCOV_PROFILE := y +endif diff --git a/net/netfilter/ipvs/Kconfig b/net/netfilter/ipvs/Kconfig index c203252e856d..7724cb44e6de 100644 --- a/net/netfilter/ipvs/Kconfig +++ b/net/netfilter/ipvs/Kconfig @@ -349,4 +349,13 @@ config IP_VS_PE_SIP help Allow persistence based on the SIP Call-ID +config GCOV_PROFILE_IPVS + bool "Enable GCOV profiling for IPVS" + depends on GCOV_KERNEL + help + Enable GCOV profiling for IPVS to check which functions/lines + are executed. + + If unsure, say N. + endif # IP_VS diff --git a/net/netfilter/ipvs/Makefile b/net/netfilter/ipvs/Makefile index bb5d8125c82a..8e4cc67ad39d 100644 --- a/net/netfilter/ipvs/Makefile +++ b/net/netfilter/ipvs/Makefile @@ -43,3 +43,6 @@ obj-$(CONFIG_IP_VS_FTP) += ip_vs_ftp.o # IPVS connection template retrievers obj-$(CONFIG_IP_VS_PE_SIP) += ip_vs_pe_sip.o +ifdef CONFIG_GCOV_PROFILE_IPVS +GCOV_PROFILE := y +endif From d4349ba9872d0c97a31fb2a18789297731061e88 Mon Sep 17 00:00:00 2001 From: Pablo Neira Ayuso Date: Wed, 15 Apr 2026 13:12:36 +0200 Subject: [PATCH 03/11] netfilter: allow nfnetlink built-in only Netfilter has its own netlink multiplexer, initially only a few subsystem were using it, most notably conntrack, queue and log, later in time nf_tables. These days it is the control plane of preference. Just remove modular support for this, allow it built-in only. Signed-off-by: Pablo Neira Ayuso Reviewed-by: Fernando Fernandez Mancera Signed-off-by: Florian Westphal --- net/netfilter/Kconfig | 2 +- net/netfilter/Makefile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/net/netfilter/Kconfig b/net/netfilter/Kconfig index f71ff98eb5d0..665f8008cc4b 100644 --- a/net/netfilter/Kconfig +++ b/net/netfilter/Kconfig @@ -22,7 +22,7 @@ config NETFILTER_SKIP_EGRESS def_bool NETFILTER_EGRESS && (NET_CLS_ACT || IFB) config NETFILTER_NETLINK - tristate + bool config NETFILTER_FAMILY_BRIDGE bool diff --git a/net/netfilter/Makefile b/net/netfilter/Makefile index f0751ca302c6..6bf74d488a29 100644 --- a/net/netfilter/Makefile +++ b/net/netfilter/Makefile @@ -1,5 +1,6 @@ # SPDX-License-Identifier: GPL-2.0 netfilter-objs := core.o nf_log.o nf_queue.o nf_sockopt.o utils.o +netfilter-$(CONFIG_NETFILTER_NETLINK) += nfnetlink.o nf_conntrack-y := nf_conntrack_core.o nf_conntrack_standalone.o nf_conntrack_expect.o nf_conntrack_helper.o \ nf_conntrack_proto.o nf_conntrack_proto_generic.o nf_conntrack_proto_tcp.o nf_conntrack_proto_udp.o \ @@ -23,7 +24,6 @@ endif obj-$(CONFIG_NETFILTER) = netfilter.o obj-$(CONFIG_NETFILTER_BPF_LINK) += nf_bpf_link.o -obj-$(CONFIG_NETFILTER_NETLINK) += nfnetlink.o obj-$(CONFIG_NETFILTER_NETLINK_ACCT) += nfnetlink_acct.o obj-$(CONFIG_NETFILTER_NETLINK_QUEUE) += nfnetlink_queue.o obj-$(CONFIG_NETFILTER_NETLINK_LOG) += nfnetlink_log.o From e9fd2fb09cfe4abb5c6238141ffbbfcb4a01aa4b Mon Sep 17 00:00:00 2001 From: Florian Westphal Date: Wed, 29 Apr 2026 08:30:00 +0200 Subject: [PATCH 04/11] netfilter: nf_conncount: use per-rule hash initval As-is, different netns will use same slots if the key is the same. OVS uses this infrastructure to limit conntrack counts per zones. Those can easily overlap. Make them hash to different slots internally. Signed-off-by: Florian Westphal --- net/netfilter/nf_conncount.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/net/netfilter/nf_conncount.c b/net/netfilter/nf_conncount.c index 00eed5b4d1b1..ab28b47395bd 100644 --- a/net/netfilter/nf_conncount.c +++ b/net/netfilter/nf_conncount.c @@ -58,6 +58,7 @@ static spinlock_t nf_conncount_locks[CONNCOUNT_SLOTS] __cacheline_aligned_in_smp struct nf_conncount_data { unsigned int keylen; + u32 initval; struct rb_root root[CONNCOUNT_SLOTS]; struct net *net; struct work_struct gc_work; @@ -65,7 +66,6 @@ struct nf_conncount_data { unsigned int gc_tree; }; -static u_int32_t conncount_rnd __read_mostly; static struct kmem_cache *conncount_rb_cachep __read_mostly; static struct kmem_cache *conncount_conn_cachep __read_mostly; @@ -496,7 +496,7 @@ count_tree(struct net *net, struct nf_conncount_rb *rbconn; unsigned int hash; - hash = jhash2(key, data->keylen, conncount_rnd) % CONNCOUNT_SLOTS; + hash = jhash2(key, data->keylen, data->initval) % CONNCOUNT_SLOTS; root = &data->root[hash]; parent = rcu_dereference_raw(root->rb_node); @@ -630,8 +630,6 @@ struct nf_conncount_data *nf_conncount_init(struct net *net, unsigned int keylen keylen == 0) return ERR_PTR(-EINVAL); - net_get_random_once(&conncount_rnd, sizeof(conncount_rnd)); - data = kmalloc_obj(*data); if (!data) return ERR_PTR(-ENOMEM); @@ -641,6 +639,7 @@ struct nf_conncount_data *nf_conncount_init(struct net *net, unsigned int keylen data->keylen = keylen / sizeof(u32); data->net = net; + data->initval = get_random_u32(); INIT_WORK(&data->gc_work, tree_gc_worker); return data; From a7f57320bbbc67e347bf5fff4b4a9bab980d5956 Mon Sep 17 00:00:00 2001 From: Pratham Gupta Date: Mon, 4 May 2026 22:11:57 -0700 Subject: [PATCH 05/11] netfilter: ctnetlink: use nf_ct_exp_net() in expectation dump Commit 02a3231b6d82 ("netfilter: nf_conntrack_expect: store netns and zone in expectation") introduced exp->net so RCU-only expectation paths no longer need to dereference exp->master for netns lookups. Commit 3db5647984de ("netfilter: nf_conntrack_expect: skip expectations in other netns via proc") updated the proc path accordingly, but ctnetlink_exp_dump_table() still compares against nf_ct_net(exp->master). Use nf_ct_exp_net(exp) here as well so the netlink dump path matches the rest of the March 2026 expectation netns/RCU cleanup. Fixes: 02a3231b6d82 ("netfilter: nf_conntrack_expect: store netns and zone in expectation") Cc: stable@vger.kernel.org Signed-off-by: Pratham Gupta Signed-off-by: Florian Westphal --- net/netfilter/nf_conntrack_netlink.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/netfilter/nf_conntrack_netlink.c b/net/netfilter/nf_conntrack_netlink.c index befa7e83ee49..d429f9c9546c 100644 --- a/net/netfilter/nf_conntrack_netlink.c +++ b/net/netfilter/nf_conntrack_netlink.c @@ -3173,7 +3173,7 @@ ctnetlink_exp_dump_table(struct sk_buff *skb, struct netlink_callback *cb) if (l3proto && exp->tuple.src.l3num != l3proto) continue; - if (!net_eq(nf_ct_net(exp->master), net)) + if (!net_eq(nf_ct_exp_net(exp), net)) continue; if (cb->args[1]) { From 73ce4a2949d97288ebee96102224f75506f6b14f Mon Sep 17 00:00:00 2001 From: Florian Westphal Date: Sat, 11 Apr 2026 23:13:08 +0200 Subject: [PATCH 06/11] netfilter: nft_set_rbtree: remove dead conditional net/netfilter/nft_set_rbtree.c:399 __nft_rbtree_insert() warn: 'removed_end' is not an error pointer Since commit : 087388278e0f ("netfilter: nf_tables: nft_set_rbtree: fix spurious insertion failure") __nft_rbtree_insert() can no longer fail and this condition is always false. Remove it. Reported-by: Dan Carpenter Closes: https://lore.kernel.org/netfilter-devel/adjSaolTji0mPgqx@stanley.mountain/ Signed-off-by: Florian Westphal --- net/netfilter/nft_set_rbtree.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/net/netfilter/nft_set_rbtree.c b/net/netfilter/nft_set_rbtree.c index 560fbe6e3f75..b4f0b5fdf1f2 100644 --- a/net/netfilter/nft_set_rbtree.c +++ b/net/netfilter/nft_set_rbtree.c @@ -396,9 +396,6 @@ static int __nft_rbtree_insert(const struct net *net, const struct nft_set *set, const struct nft_rbtree_elem *removed_end; removed_end = nft_rbtree_gc_elem(set, priv, rbe); - if (IS_ERR(removed_end)) - return PTR_ERR(removed_end); - if (removed_end == rbe_le || removed_end == rbe_ge) return -EAGAIN; From d738feccb98cb224ebabecb703e98f5008276bff Mon Sep 17 00:00:00 2001 From: David Carlier Date: Sat, 11 Apr 2026 19:57:21 +0100 Subject: [PATCH 07/11] netfilter: nfnl_cthelper: apply per-class values when updating policies When a userspace conntrack helper with multiple expectation classes is updated via nfnetlink, every class ends up with the first class's max_expected and timeout values. nfnl_cthelper_update_policy_all() validates each new policy into the corresponding slot of the temporary new_policy array, but the second loop that commits the values into the live helper dereferences new_policy as a pointer instead of indexing it, so every iteration reads new_policy[0] regardless of i. An update that changes per-class values is silently collapsed onto class 0's values with no error returned to userspace. Index the temporary array by i in the commit loop so each class gets its own validated values. Fixes: 2c422257550f ("netfilter: nfnl_cthelper: fix runtime expectation policy updates") Cc: stable@vger.kernel.org Signed-off-by: David Carlier Signed-off-by: Florian Westphal --- net/netfilter/nfnetlink_cthelper.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/net/netfilter/nfnetlink_cthelper.c b/net/netfilter/nfnetlink_cthelper.c index 0d16ad82d70c..34af6840803e 100644 --- a/net/netfilter/nfnetlink_cthelper.c +++ b/net/netfilter/nfnetlink_cthelper.c @@ -346,8 +346,8 @@ static int nfnl_cthelper_update_policy_all(struct nlattr *tb[], for (i = 0; i < helper->expect_class_max + 1; i++) { policy = (struct nf_conntrack_expect_policy *) &helper->expect_policy[i]; - policy->max_expected = new_policy->max_expected; - policy->timeout = new_policy->timeout; + policy->max_expected = new_policy[i].max_expected; + policy->timeout = new_policy[i].timeout; } err: From ef6400ca25a13fd6dedbe8ef4a1d0979bbbfe88a Mon Sep 17 00:00:00 2001 From: Muhammad Bilal Date: Tue, 19 May 2026 17:23:28 -0400 Subject: [PATCH 08/11] netfilter: nf_conntrack_irc: fix parse_dcc() off-by-one OOB read parse_dcc() treats data_end as an inclusive end pointer, but its only caller passes data_limit = ib_ptr + datalen, which points one past the last valid byte. The newline search loop iterates while tmp <= data_end, so when no newline is present, *tmp is read at tmp == data_end, one byte beyond the region filled by skb_header_pointer(). irc_buffer is kmalloc'd as MAX_SEARCH_SIZE + 1 bytes and datalen is capped at MAX_SEARCH_SIZE, so the stray read does not fault. The byte is uninitialized or stale; if it contains an ASCII digit, simple_strtoul will consume it and produce a wrong DCC IP or port in the conntrack expectation. The extra allocation byte is also a fragile guard: if the cap or allocation size changes, this becomes a real out-of-bounds read. Change the loop and its post-loop check to use strict less-than, consistent with the caller's exclusive-end convention. Update the function comment accordingly. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org Signed-off-by: Muhammad Bilal Signed-off-by: Florian Westphal --- net/netfilter/nf_conntrack_irc.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/net/netfilter/nf_conntrack_irc.c b/net/netfilter/nf_conntrack_irc.c index 522183b9a604..9a7b8f6221eb 100644 --- a/net/netfilter/nf_conntrack_irc.c +++ b/net/netfilter/nf_conntrack_irc.c @@ -59,7 +59,7 @@ static const char *const dccprotos[] = { /* tries to get the ip_addr and port out of a dcc command * return value: -1 on failure, 0 on success * data pointer to first byte of DCC command data - * data_end pointer to last byte of dcc command data + * data_end one past end of data * ip returns parsed ip of dcc command * port returns parsed port of dcc command * ad_beg_p returns pointer to first byte of addr data @@ -77,10 +77,10 @@ static int parse_dcc(char *data, const char *data_end, __be32 *ip, /* Make sure we have a newline character within the packet boundaries * because simple_strtoul parses until the first invalid character. */ - for (tmp = data; tmp <= data_end; tmp++) + for (tmp = data; tmp < data_end; tmp++) if (*tmp == '\n') break; - if (tmp > data_end || *tmp != '\n') + if (tmp >= data_end || *tmp != '\n') return -1; *ad_beg_p = data; From 22fad3d5b135e53f8648991bf3724a14abc579c3 Mon Sep 17 00:00:00 2001 From: Avinash Duduskar Date: Wed, 13 May 2026 03:25:01 +0530 Subject: [PATCH 09/11] netfilter: nf_conntrack_proto_tcp: fix typos in comments Fix three typos in comments: - "migth"/"Migth" -> "might" (two adjacent occurrences in the tcp_conntracks[] state-transition table comment block). - "agaist" -> "against" (tcp_error() header comment). - "intrepretated" -> "interpreted" (RFC 5961 challenge-ACK marker comment in nf_conntrack_tcp_packet()). Signed-off-by: Avinash Duduskar Signed-off-by: Florian Westphal --- net/netfilter/nf_conntrack_proto_tcp.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/net/netfilter/nf_conntrack_proto_tcp.c b/net/netfilter/nf_conntrack_proto_tcp.c index b67426c2189b..47dc6edb4431 100644 --- a/net/netfilter/nf_conntrack_proto_tcp.c +++ b/net/netfilter/nf_conntrack_proto_tcp.c @@ -169,14 +169,14 @@ static const u8 tcp_conntracks[2][6][TCP_CONNTRACK_MAX] = { /*fin*/ { sIV, sIV, sFW, sFW, sLA, sLA, sLA, sTW, sCL, sIV }, /* * sNO -> sIV Too late and no reason to do anything... - * sSS -> sIV Client migth not send FIN in this state: + * sSS -> sIV Client might not send FIN in this state: * we enforce waiting for a SYN/ACK reply first. * sS2 -> sIV * sSR -> sFW Close started. * sES -> sFW * sFW -> sLA FIN seen in both directions, waiting for * the last ACK. - * Migth be a retransmitted FIN as well... + * Might be a retransmitted FIN as well... * sCW -> sLA * sLA -> sLA Retransmitted FIN. Remain in the same state. * sTW -> sTW @@ -798,7 +798,7 @@ static void tcp_error_log(const struct sk_buff *skb, nf_l4proto_log_invalid(skb, state, IPPROTO_TCP, "%s", msg); } -/* Protect conntrack agaist broken packets. Code taken from ipt_unclean.c. */ +/* Protect conntrack against broken packets. Code taken from ipt_unclean.c. */ static bool tcp_error(const struct tcphdr *th, struct sk_buff *skb, unsigned int dataoff, @@ -1098,7 +1098,7 @@ int nf_conntrack_tcp_packet(struct nf_conn *ct, } /* Mark the potential for RFC5961 challenge ACK, * this pose a special problem for LAST_ACK state - * as ACK is intrepretated as ACKing last FIN. + * as ACK is interpreted as ACKing last FIN. */ if (old_state == TCP_CONNTRACK_LAST_ACK) ct->proto.tcp.last_flags |= From e928ab085d8ab775fe0cb8bad15340081b201f52 Mon Sep 17 00:00:00 2001 From: Florian Westphal Date: Sat, 11 Apr 2026 14:12:59 +0200 Subject: [PATCH 10/11] netfilter: nft_set_pipapo_avx2: restore performance optimization The avx2 lookup routines get the next map index to process passes as a function argument, but this isn't obvious because it's hidden in the lookup macro. Additionally, a recent LLM review pointed out following "bug": ------------------------------------------------------------- > b = nft_pipapo_avx2_refill(i_ul, &map[i_ul], fill, f->mt, last); > if (last) > - return b; > + ret = b; > > if (unlikely(ret == -1)) > ret = b / XSAVE_YMM_SIZE; Does this change introduce a logic error when last=true and no match is found? [..] Should this be changed to an else-if structure instead? ------------------------------------------------------------- LLM sees a control-flow change, but there is none: All call sites invoke nft_pipapo_avx2_refill() only when at least one bit in the map is set, i.e. nft_pipapo_avx2_refill() never returns -1. Add a runtime debug check that fires if we'd return -1 as additional documentation and also make the suggested change, code might be easier to understand this way. In commit 17a20e09f086 ("netfilter: nft_set: remove one argument from lookup and update functions") I incorrectly moved the "ret" scope into the loop. This has no effect on the correctness, but it can (depending on map sizes) cause a redundant repeat of an earlier processing step. Restore the intended 'pass map index' instead of always-0. Note that I did not see any change in performance numbers, but Stefano correctly points out that the existing perf test likely lack a sparse intermediate bitmap (between fields) with a lot of leading zeroes. Reviewed-by: Stefano Brivio Signed-off-by: Florian Westphal --- net/netfilter/nft_set_pipapo_avx2.c | 35 ++++++++++++----------------- 1 file changed, 14 insertions(+), 21 deletions(-) diff --git a/net/netfilter/nft_set_pipapo_avx2.c b/net/netfilter/nft_set_pipapo_avx2.c index dad265807b8b..b3f105520a85 100644 --- a/net/netfilter/nft_set_pipapo_avx2.c +++ b/net/netfilter/nft_set_pipapo_avx2.c @@ -144,6 +144,7 @@ static void nft_pipapo_avx2_fill(unsigned long *data, int start, int len) * This is an alternative implementation of pipapo_refill() suitable for usage * with AVX2 lookup routines: we know there are four words to be scanned, at * a given offset inside the map, for each matching iteration. + * The caller must ensure at least one bit in the four words is set. * * This function doesn't actually use any AVX2 instruction. * @@ -179,6 +180,7 @@ static int nft_pipapo_avx2_refill(int offset, unsigned long *map, NFT_PIPAPO_AVX2_REFILL_ONE_WORD(3); #undef NFT_PIPAPO_AVX2_REFILL_ONE_WORD + DEBUG_NET_WARN_ON_ONCE(ret < 0); return ret; } @@ -243,8 +245,7 @@ static int nft_pipapo_avx2_lookup_4b_2(unsigned long *map, unsigned long *fill, b = nft_pipapo_avx2_refill(i_ul, &map[i_ul], fill, f->mt, last); if (last) ret = b; - - if (unlikely(ret == -1)) + else if (unlikely(ret == -1)) ret = b / XSAVE_YMM_SIZE; continue; @@ -320,8 +321,7 @@ static int nft_pipapo_avx2_lookup_4b_4(unsigned long *map, unsigned long *fill, b = nft_pipapo_avx2_refill(i_ul, &map[i_ul], fill, f->mt, last); if (last) ret = b; - - if (unlikely(ret == -1)) + else if (unlikely(ret == -1)) ret = b / XSAVE_YMM_SIZE; continue; @@ -415,8 +415,7 @@ static int nft_pipapo_avx2_lookup_4b_8(unsigned long *map, unsigned long *fill, b = nft_pipapo_avx2_refill(i_ul, &map[i_ul], fill, f->mt, last); if (last) ret = b; - - if (unlikely(ret == -1)) + else if (unlikely(ret == -1)) ret = b / XSAVE_YMM_SIZE; continue; @@ -506,8 +505,7 @@ static int nft_pipapo_avx2_lookup_4b_12(unsigned long *map, unsigned long *fill, b = nft_pipapo_avx2_refill(i_ul, &map[i_ul], fill, f->mt, last); if (last) ret = b; - - if (unlikely(ret == -1)) + else if (unlikely(ret == -1)) ret = b / XSAVE_YMM_SIZE; continue; @@ -642,8 +640,7 @@ static int nft_pipapo_avx2_lookup_4b_32(unsigned long *map, unsigned long *fill, b = nft_pipapo_avx2_refill(i_ul, &map[i_ul], fill, f->mt, last); if (last) ret = b; - - if (unlikely(ret == -1)) + else if (unlikely(ret == -1)) ret = b / XSAVE_YMM_SIZE; continue; @@ -700,8 +697,7 @@ static int nft_pipapo_avx2_lookup_8b_1(unsigned long *map, unsigned long *fill, b = nft_pipapo_avx2_refill(i_ul, &map[i_ul], fill, f->mt, last); if (last) ret = b; - - if (unlikely(ret == -1)) + else if (unlikely(ret == -1)) ret = b / XSAVE_YMM_SIZE; continue; @@ -765,8 +761,7 @@ static int nft_pipapo_avx2_lookup_8b_2(unsigned long *map, unsigned long *fill, b = nft_pipapo_avx2_refill(i_ul, &map[i_ul], fill, f->mt, last); if (last) ret = b; - - if (unlikely(ret == -1)) + else if (unlikely(ret == -1)) ret = b / XSAVE_YMM_SIZE; continue; @@ -840,8 +835,7 @@ static int nft_pipapo_avx2_lookup_8b_4(unsigned long *map, unsigned long *fill, b = nft_pipapo_avx2_refill(i_ul, &map[i_ul], fill, f->mt, last); if (last) ret = b; - - if (unlikely(ret == -1)) + else if (unlikely(ret == -1)) ret = b / XSAVE_YMM_SIZE; continue; @@ -926,8 +920,7 @@ static int nft_pipapo_avx2_lookup_8b_6(unsigned long *map, unsigned long *fill, b = nft_pipapo_avx2_refill(i_ul, &map[i_ul], fill, f->mt, last); if (last) ret = b; - - if (unlikely(ret == -1)) + else if (unlikely(ret == -1)) ret = b / XSAVE_YMM_SIZE; continue; @@ -1020,8 +1013,7 @@ static int nft_pipapo_avx2_lookup_8b_16(unsigned long *map, unsigned long *fill, b = nft_pipapo_avx2_refill(i_ul, &map[i_ul], fill, f->mt, last); if (last) ret = b; - - if (unlikely(ret == -1)) + else if (unlikely(ret == -1)) ret = b / XSAVE_YMM_SIZE; continue; @@ -1143,6 +1135,7 @@ struct nft_pipapo_elem *pipapo_get_avx2(const struct nft_pipapo_match *m, const struct nft_pipapo_field *f; unsigned long *res, *fill, *map; bool map_index; + int ret = 0; int i; scratch = *raw_cpu_ptr(m->scratch); @@ -1167,8 +1160,8 @@ struct nft_pipapo_elem *pipapo_get_avx2(const struct nft_pipapo_match *m, nft_pipapo_for_each_field(f, i, m) { bool last = i == m->field_count - 1, first = !i; - int ret = 0; + /* NB: previous round @ret is passed to avx2 lookup fn */ #define NFT_SET_PIPAPO_AVX2_LOOKUP(b, n) \ (ret = nft_pipapo_avx2_lookup_##b##b_##n(res, fill, f, \ ret, data, \ From 2b413fc689ba890348db13a4daa5adf42846ebca Mon Sep 17 00:00:00 2001 From: Giuseppe Caruso Date: Fri, 10 Apr 2026 09:57:33 -0400 Subject: [PATCH 11/11] netfilter: nf_conntrack_ftp: avoid u16 overflows get_port and try_number() parse comma-separated decimal values from FTP PORT and EPRT commands into a u_int32_t array, but does not validate that each value fits in a single octet. RFC 959 specifies that PORT parameters are decimal integers in the range 0-255, representing the four octets of an IP address followed by two octets encoding the port number. Values exceeding 255 are silently accepted. In try_rfc959(), the raw u32 values are combined via shift-and-OR to form the IP and port: cmd->u3.ip = htonl((array[0] << 24) | (array[1] << 16) | (array[2] << 8) | array[3]); cmd->u.tcp.port = htons((array[4] << 8) | array[5]); When array elements exceed 255, bits from one field bleed into adjacent fields after shifting, producing IP addresses and port numbers that differ from what the text representation suggests. For example, "PORT 10,0,1,2,256,22" yields port (256<<8)|22 = 65558, truncated to u16 = 22. This mismatch between the textual and computed values can confuse network monitoring tools that parse FTP commands independently. Ignore the command by returning 0 (no match) when any accumulated value exceeds 255 so that no expectation is created. Signed-off-by: Giuseppe Caruso Signed-off-by: Florian Westphal --- net/netfilter/nf_conntrack_ftp.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/net/netfilter/nf_conntrack_ftp.c b/net/netfilter/nf_conntrack_ftp.c index de83bf9e6c61..dc6f0017ca6b 100644 --- a/net/netfilter/nf_conntrack_ftp.c +++ b/net/netfilter/nf_conntrack_ftp.c @@ -120,6 +120,8 @@ static int try_number(const char *data, size_t dlen, u_int32_t array[], for (i = 0, len = 0; len < dlen && i < array_size; len++, data++) { if (*data >= '0' && *data <= '9') { array[i] = array[i]*10 + *data - '0'; + if (array[i] > 255) + return 0; } else if (*data == sep) i++; @@ -189,7 +191,7 @@ static int try_rfc1123(const char *data, size_t dlen, static int get_port(const char *data, int start, size_t dlen, char delim, __be16 *port) { - u_int16_t tmp_port = 0; + u32 tmp_port = 0; int i; for (i = start; i < dlen; i++) { @@ -200,10 +202,11 @@ static int get_port(const char *data, int start, size_t dlen, char delim, *port = htons(tmp_port); pr_debug("get_port: return %d\n", tmp_port); return i + 1; - } - else if (data[i] >= '0' && data[i] <= '9') + } else if (data[i] >= '0' && data[i] <= '9') { tmp_port = tmp_port*10 + data[i] - '0'; - else { /* Some other crap */ + if (tmp_port > 65535) + break; + } else { /* Some other crap */ pr_debug("get_port: invalid char.\n"); break; }