* [PATCH net-next 0/8] Netfilter/IPVS fixes for net-next
@ 2026-08-17 23:29 Pablo Neira Ayuso
2026-08-17 23:29 ` [PATCH net-next 1/8] netfilter: validate L4 headers after userspace packet writes Pablo Neira Ayuso
` (7 more replies)
0 siblings, 8 replies; 9+ messages in thread
From: Pablo Neira Ayuso @ 2026-08-17 23:29 UTC (permalink / raw)
To: netfilter-devel; +Cc: davem, netdev, kuba, pabeni, edumazet, horms, fw, ja
Hi,
The following patchset contains Netfilter/IPVS fixes for net-next,
I am targetting this tree since 7.2 has been already released.
This contains fixes for nf_tables, revisit issues with expectation
infra updates reported by sashiko, an ipset fix for deletions in the
hash:net type and tne fix for the IPVS FTP helper.
1) Validate layer 4 header mangling done via nfnetlink_queue and
nft_payload, this is a follow up to recent similar validation
at layer 3. From Zhiling Zou.
2) Do not allocate memory on delete operations in ipset hash:net
type, delete operation must always succeed. From Florian Westphal.
3) Deliver nft_obj overquota packet path notification directly via
nfnetlink, do not use the control plane batch logic.
From Fourie Zhang.
4) Follow up to controlidate check for reinserted dead expectations,
to cover the nf_conntrack_expect_related_pair() function too.
5) Do not expose expectation dead flag to userspace via ctnetlink.
6) Make commit set_update_list per-netns to prepare to publish
set clone earlier.
7) Publish the set clone earlier from commit path to address set
lookup failures during table re-creation, this is targetting
the rbtree and pipapo set backends.
8) Fix an integer overflow in the IPVS FTP helper. A similar fix
was already proposed for the conntrack FTP helper months ago.
From Joas Antonio dos Santos.
Please, pull these changes from:
git://git.kernel.org/pub/scm/linux/kernel/git/netfilter/nf-next.git nf-next-26-08-18
Thanks.
----------------------------------------------------------------
The following changes since commit e6a5d573d24cd375e09d24f136523cb3cc85c9d3:
net: dsa: drop explicit NULL comparisons (2026-08-14 13:57:27 -0700)
are available in the Git repository at:
git://git.kernel.org/pub/scm/linux/kernel/git/netfilter/nf-next.git nf-next-26-08-18
for you to fetch changes up to e625a9477d12baaff4025c5f9989184a907ea8fc:
ipvs: fix integer overflow in ftp helper port/address parsing (2026-08-18 00:56:43 +0200)
----------------------------------------------------------------
netfilter pull request 26-08-18
----------------------------------------------------------------
Florian Westphal (1):
netfilter: ipset: remove need to allocate memory on delete operations
Fourie Zhang (1):
netfilter: nf_tables: don't queue packet path object notifications
Joas Antonio dos Santos (1):
ipvs: fix integer overflow in ftp helper port/address parsing
Pablo Neira Ayuso (4):
netfilter: nf_conntrack_expect: consolidate check for insertion of dead expectation
netfilter: ctnetlink: do not expose expectation DEAD flag
netfilter: nf_tables: move set_update_list to nftables per-netns
netfilter: nf_tables: call set ops .commit when building new ruleset blob
Zhiling Zou (1):
netfilter: validate L4 headers after userspace packet writes
include/net/netfilter/nf_tables.h | 1 +
net/netfilter/ipset/ip_set_hash_gen.h | 168 +++++++++++++++++++--------
net/netfilter/ipset/ip_set_hash_netiface.c | 1 -
net/netfilter/ipset/ip_set_hash_netportnet.c | 1 -
net/netfilter/ipvs/ip_vs_ftp.c | 10 +-
net/netfilter/nf_conntrack_expect.c | 11 +-
net/netfilter/nf_conntrack_netlink.c | 2 +-
net/netfilter/nf_tables_api.c | 137 +++++++++++++---------
net/netfilter/nfnetlink_queue.c | 72 +++++++++++-
net/netfilter/nft_payload.c | 13 +++
10 files changed, 302 insertions(+), 114 deletions(-)
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH net-next 1/8] netfilter: validate L4 headers after userspace packet writes
2026-08-17 23:29 [PATCH net-next 0/8] Netfilter/IPVS fixes for net-next Pablo Neira Ayuso
@ 2026-08-17 23:29 ` Pablo Neira Ayuso
2026-08-17 23:29 ` [PATCH net-next 2/8] netfilter: ipset: remove need to allocate memory on delete operations Pablo Neira Ayuso
` (6 subsequent siblings)
7 siblings, 0 replies; 9+ messages in thread
From: Pablo Neira Ayuso @ 2026-08-17 23:29 UTC (permalink / raw)
To: netfilter-devel; +Cc: davem, netdev, kuba, pabeni, edumazet, horms, fw, ja
From: Zhiling Zou <zhilinz@nebusec.ai>
NFQUEUE and nft_payload can hand packet data modified by userspace back
to the stack. Recent restrictions keep link and network headers stable,
but transport header fields can still be changed.
A packet can therefore keep the same network header and conntrack entry
while changing the transport header layout. For TCP, increasing doff can
make later helper or NAT code use a different transport-header base than
the parser used, and can make offsets point past skb->tail.
Extend NFQUEUE payload validation to check the final L4 protocol and
known base headers after IPv4 options or IPv6 extension headers. Reject
packets whose L4 protocol no longer matches an attached non-template
conntrack entry, and reject IP fragments that already have such a
conntrack entry before trying to validate transport headers. Unknown L4
protocols are left to their normal protocol handlers.
For nft payload writes, reject transport-header stores that overlap TCP
doff. nft_nh_write_ok() already rejects network-header protocol changes,
so keeping doff stable prevents nft payload writes from changing the TCP
header length underneath conntrack and helper users.
This patch is a follow up to commit df07998dfd40 ("netfilter: nftables:
restrict linklayer and network header writes") and commit 54f34607d184
("netfilter: nfnetlink_queue: restrict writes to network header").
Reported-by: Vega <vega@nebusec.ai>
Signed-off-by: Zhiling Zou <zhilinz@nebusec.ai>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
---
net/netfilter/nfnetlink_queue.c | 72 +++++++++++++++++++++++++++++++--
net/netfilter/nft_payload.c | 13 ++++++
2 files changed, 82 insertions(+), 3 deletions(-)
diff --git a/net/netfilter/nfnetlink_queue.c b/net/netfilter/nfnetlink_queue.c
index b8aaf39cb4d8..c727668b0c5b 100644
--- a/net/netfilter/nfnetlink_queue.c
+++ b/net/netfilter/nfnetlink_queue.c
@@ -28,10 +28,17 @@
#include <linux/netfilter/nfnetlink.h>
#include <linux/netfilter/nfnetlink_queue.h>
#include <linux/netfilter/nf_conntrack_common.h>
+#include <linux/icmp.h>
+#include <linux/icmpv6.h>
+#include <linux/ip.h>
#include <linux/list.h>
+#include <linux/sctp.h>
#include <linux/cgroup-defs.h>
#include <linux/rhashtable.h>
#include <linux/jhash.h>
+#include <linux/tcp.h>
+#include <linux/udp.h>
+#include <net/gre.h>
#include <net/gso.h>
#include <net/sock.h>
#include <net/tcp_states.h>
@@ -1206,10 +1213,62 @@ static bool nfqnl_validate_ipopts(const struct iphdr *iph_new,
return memcmp(iph_new + 1, ip_hdr(e->skb) + 1, ihl - sizeof(*iph_orig)) == 0;
}
+static bool nfqnl_validate_l4(const u8 *data, unsigned int data_len,
+ const struct nf_queue_entry *e, u8 proto,
+ bool fragment)
+{
+#if IS_ENABLED(CONFIG_NF_CONNTRACK)
+ enum ip_conntrack_info ctinfo;
+ const struct nf_conn *ct;
+
+ ct = nf_ct_get(e->skb, &ctinfo);
+ if (ct && !nf_ct_is_template(ct)) {
+ if (fragment || nf_ct_protonum(ct) != proto)
+ return false;
+ }
+#endif
+
+ if (fragment)
+ return true;
+
+ switch (proto) {
+ case IPPROTO_TCP: {
+ const struct tcphdr *th = (const struct tcphdr *)data;
+ unsigned int thlen;
+
+ if (data_len < sizeof(*th))
+ return false;
+
+ thlen = __tcp_hdrlen(th);
+ if (thlen < sizeof(*th) || data_len < thlen)
+ return false;
+
+ return true;
+ }
+ case IPPROTO_UDP:
+ return data_len >= sizeof(struct udphdr);
+ case IPPROTO_ICMP:
+ return data_len >= sizeof(struct icmphdr);
+ case IPPROTO_ICMPV6:
+ return data_len >= sizeof(struct icmp6hdr);
+ case IPPROTO_SCTP:
+ return data_len >= sizeof(struct sctphdr);
+ case IPPROTO_GRE:
+ return data_len >= sizeof(struct gre_base_hdr);
+ case IPPROTO_AH:
+ return data_len >= sizeof(struct ip_auth_hdr);
+ case IPPROTO_ESP:
+ return data_len >= sizeof(struct ip_esp_hdr);
+ }
+
+ return true;
+}
+
static bool nfqnl_validate_ip4(const struct iphdr *iph, unsigned int data_len,
const struct nf_queue_entry *e)
{
unsigned int ihl;
+ bool fragment;
if (data_len < sizeof(*iph))
return false;
@@ -1226,10 +1285,14 @@ static bool nfqnl_validate_ip4(const struct iphdr *iph, unsigned int data_len,
if (ntohs(iph->tot_len) != data_len)
return false;
+ fragment = iph->frag_off & htons(IP_MF | IP_OFFSET);
+
/* support for ipopts mangling would require
* recompile + skb transport header update.
*/
- return nfqnl_validate_ipopts(iph, e);
+ return nfqnl_validate_ipopts(iph, e) &&
+ nfqnl_validate_l4((const u8 *)iph + ihl, data_len - ihl, e,
+ iph->protocol, fragment);
}
static bool nfqnl_validate_one_exthdr(const u8 *data,
@@ -1273,6 +1336,7 @@ static bool nfqnl_validate_exthdr(const struct ipv6hdr *ip6_new,
const u8 *data = (const u8 *)ip6_new;
u8 orig_nexthdr = ip6_orig->nexthdr;
u8 new_nexthdr = ip6_new->nexthdr;
+ bool fragment = false;
if (new_nexthdr != orig_nexthdr)
return false;
@@ -1286,7 +1350,8 @@ static bool nfqnl_validate_exthdr(const struct ipv6hdr *ip6_new,
int hdrlen;
if (orig_nexthdr == NEXTHDR_NONE)
- return true;
+ return nfqnl_validate_l4(data, data_len, e,
+ new_nexthdr, fragment);
if (unlikely(exthdr_cnt++ >= IP6_MAX_EXT_HDRS_CNT))
return false;
@@ -1297,6 +1362,7 @@ static bool nfqnl_validate_exthdr(const struct ipv6hdr *ip6_new,
switch (orig_nexthdr) {
case NEXTHDR_FRAGMENT:
+ fragment = true;
hdrlen = sizeof(struct frag_hdr);
break;
case NEXTHDR_AUTH:
@@ -1323,7 +1389,7 @@ static bool nfqnl_validate_exthdr(const struct ipv6hdr *ip6_new,
data += hdrlen;
}
- return true;
+ return nfqnl_validate_l4(data, data_len, e, new_nexthdr, fragment);
}
static bool nfqnl_validate_ip6(const struct ipv6hdr *ip6, unsigned int data_len,
diff --git a/net/netfilter/nft_payload.c b/net/netfilter/nft_payload.c
index 8a4472fd77d9..e315d35f73d4 100644
--- a/net/netfilter/nft_payload.c
+++ b/net/netfilter/nft_payload.c
@@ -1067,6 +1067,17 @@ static bool nft_payload_csum_write_ok(const struct nft_pktinfo *pkt,
return false;
}
+static bool nft_th_write_ok(const struct nft_pktinfo *pkt,
+ const struct nft_payload_set *priv)
+{
+ unsigned int doff = offsetof(struct tcphdr, ack_seq) + sizeof(__be32);
+
+ if (pkt->tprot != IPPROTO_TCP)
+ return true;
+
+ return priv->offset > doff || priv->offset + priv->len <= doff;
+}
+
static void nft_payload_set_eval(const struct nft_expr *expr,
struct nft_regs *regs,
const struct nft_pktinfo *pkt)
@@ -1105,6 +1116,8 @@ static void nft_payload_set_eval(const struct nft_expr *expr,
case NFT_PAYLOAD_TRANSPORT_HEADER:
if (!(pkt->flags & NFT_PKTINFO_L4PROTO) || pkt->fragoff)
goto err;
+ if (!nft_th_write_ok(pkt, priv))
+ goto err;
offset = nft_thoff(pkt);
break;
case NFT_PAYLOAD_INNER_HEADER:
--
2.47.3
^ permalink raw reply related [flat|nested] 9+ messages in thread
* [PATCH net-next 2/8] netfilter: ipset: remove need to allocate memory on delete operations
2026-08-17 23:29 [PATCH net-next 0/8] Netfilter/IPVS fixes for net-next Pablo Neira Ayuso
2026-08-17 23:29 ` [PATCH net-next 1/8] netfilter: validate L4 headers after userspace packet writes Pablo Neira Ayuso
@ 2026-08-17 23:29 ` Pablo Neira Ayuso
2026-08-17 23:29 ` [PATCH net-next 3/8] netfilter: nf_tables: don't queue packet path object notifications Pablo Neira Ayuso
` (5 subsequent siblings)
7 siblings, 0 replies; 9+ messages in thread
From: Pablo Neira Ayuso @ 2026-08-17 23:29 UTC (permalink / raw)
To: netfilter-devel; +Cc: davem, netdev, kuba, pabeni, edumazet, horms, fw, ja
From: Florian Westphal <fw@strlen.de>
Allocating mem via GFP_ATOMIC on delete is problematic, delete operations
should always succeed.
Do in-place substitution: When /cidr reaches 0 count (no more elements in
the range), move ranges stored later in the array forward and keep the
count 0 ones at the end.
INIT_CIDR() can then check count == 0 without a need to search next element
in the array.
To avoid problems on weakly ordered architectures, pack the structure so it
is only 32bit wide, then use READ/WRITE_ONCE to store both cidr and count.
atomically.
Also update comments to mention the possible presence of ignored
0-count-0-cidr structures at the end and need for seqcount.
seqcount is used to restart. This avoids bogus range misses.
Given: [0]: /29 [1]: /24
cpu1 reads slot 0. then, right after, cpu2 removes /29. count drops to 0,
so it updates array to: [0], /24, [1], /0 (count 0).
cpu1 then skips /28: slot 0 was already visited, but slot 1 already replaced.
Note that mtype_add() doesn't check mtype_add_cidr() return value.
Doing this here is useless noise as this code is extensively rewritten
in the rhashtable replacement patch.
Assisted-by: Claude:claude-sonnet-5
Fixes: 8e5fd2a55e24 ("netfilter: ipset: rework cidr bookkeeping")
Signed-off-by: Florian Westphal <fw@strlen.de>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
---
net/netfilter/ipset/ip_set_hash_gen.h | 168 +++++++++++++------
net/netfilter/ipset/ip_set_hash_netiface.c | 1 -
net/netfilter/ipset/ip_set_hash_netportnet.c | 1 -
3 files changed, 121 insertions(+), 49 deletions(-)
diff --git a/net/netfilter/ipset/ip_set_hash_gen.h b/net/netfilter/ipset/ip_set_hash_gen.h
index f00c82acd7f0..80ca523f304b 100644
--- a/net/netfilter/ipset/ip_set_hash_gen.h
+++ b/net/netfilter/ipset/ip_set_hash_gen.h
@@ -8,6 +8,7 @@
#include <linux/rcupdate_wait.h>
#include <linux/jhash.h>
#include <linux/types.h>
+#include <linux/seqlock.h>
#include <linux/netfilter/nfnetlink.h>
#include <linux/netfilter/ipset/ip_set.h>
@@ -98,14 +99,34 @@ struct htable {
#define IPSET_NET_COUNT 1
#endif
-/* Book-keeping of the prefixes added to the set */
+/**
+ * struct net_prefix - Representation of a network prefix.
+ * @cidr: The CIDR prefix length.
+ * @count: Number of occurrences.
+ */
struct net_prefix {
- u8 cidr; /* the cidr value */
- u32 count; /* number of elements of this cidr */
+ u32 cidr:8;
+ u32 count:24;
};
+#define CIDR_MAX_COUNT ((1 << 24) - 1)
+
+/**
+ * struct net_prefixes - A collection of network prefixes.
+ * @rcu: RCU head
+ * @seq: Sequence counter guarding in-place reordering of @nets
+ * @len: Number of entries in the array.
+ * @nets: Array of net_prefix structures (sorted by CIDR descending).
+ *
+ * @nets entries are updated in place under @set's lock. A single entry's
+ * cidr/count pair is always updated atomically via READ_ONCE()/WRITE_ONCE(),
+ * but removing an entry also shifts every following entry down by one slot.
+ * Lockless readers that scan the whole array (i.e. more than a single
+ * indexed slot) must use @seq to detect and retry across such a shift.
+ */
struct net_prefixes {
struct rcu_head rcu;
+ seqcount_spinlock_t seq;
u8 len;
struct net_prefix nets[] __counted_by(len);
};
@@ -143,8 +164,11 @@ htable_size(u8 hbits)
#endif
#define INIT_CIDR(n, host_mask) ({ \
- const struct net_prefixes *__n = rcu_dereference(n); \
- DCIDR_PUT((__n)->len ? (__n)->nets[0].cidr : host_mask);\
+ const struct net_prefixes *__n = rcu_dereference(n); \
+ struct net_prefix __p = \
+ __n->len ? READ_ONCE(__n->nets[0]) \
+ : (struct net_prefix){}; \
+ DCIDR_PUT(__p.count ? __p.cidr : host_mask); \
})
#endif /* IP_SET_HASH_WITH_NETS */
@@ -318,27 +342,43 @@ struct mtype_resize_ad {
};
#ifdef IP_SET_HASH_WITH_NETS
-/* Network cidr size book keeping when the hash stores different
- * sized networks. cidr == real cidr + 1 to support /0.
+/**
+ * mtype_add_cidr - Add a CIDR entry to hash table bookkeeping
+ * @set: Pointer to the ip_set
+ * @h: Pointer to the htype
+ * @cidr: The CIDR prefix length
+ * @n: The index of the net_prefix array to add @cidr to
+ *
+ * Performs an update if @cidr is found, otherwise performs COW-style
+ * allocation and replacement via RCU.
+ *
+ * Return: 0 on success, negative error code on failure.
*/
static int
mtype_add_cidr(struct ip_set *set, struct htype *h, u8 cidr, u8 n)
{
- struct net_prefixes *nets, *tmp;
int i, j, found, len = 0, ret = 0;
+ struct net_prefixes *nets, *tmp;
+ struct net_prefix np;
spin_lock_bh(&set->lock);
nets = __ipset_dereference(h->rnets[n]);
/* Add in increasing prefix order, so larger cidr first */
for (i = 0, found = -1; i < nets->len; i++) {
- if (nets->nets[i].count)
+ np = READ_ONCE(nets->nets[i]);
+ if (np.count)
len++;
if (found != -1) {
continue;
- } else if (nets->nets[i].cidr < cidr) {
+ } else if (np.cidr < cidr) {
found = i;
- } else if (nets->nets[i].cidr == cidr) {
- nets->nets[i].count++;
+ } else if (np.cidr == cidr) {
+ if (np.count < CIDR_MAX_COUNT) {
+ np.count++;
+ WRITE_ONCE(nets->nets[i], np);
+ } else {
+ ret = -EOVERFLOW;
+ }
goto unlock;
}
}
@@ -350,6 +390,7 @@ mtype_add_cidr(struct ip_set *set, struct htype *h, u8 cidr, u8 n)
}
tmp->len = len;
+ seqcount_spinlock_init(&tmp->seq, &set->lock);
for (i = 0, j = 0; i < nets->len; i++) {
if (i == found) {
tmp->nets[j].cidr = cidr;
@@ -371,42 +412,60 @@ mtype_add_cidr(struct ip_set *set, struct htype *h, u8 cidr, u8 n)
return ret;
}
+/**
+ * mtype_del_cidr - Remove CIDR entry and maintain array integrity.
+ * @set: Pointer to the ip_set.
+ * @h: Pointer to the htype.
+ * @cidr: The CIDR prefix length.
+ * @n: The index of the net_prefix array to remove @cidr from
+ *
+ * If CIDR entry count falls to 0, this function performs a "shift-left"
+ * operation on all following elements. This ensures that the array remains
+ * contiguous and maintains its descending order by CIDR. The vacated slot
+ * at the end of the array is zeroed out (cidr=0, count=0).
+ */
static void
mtype_del_cidr(struct ip_set *set, struct htype *h, u8 cidr, u8 n)
{
- struct net_prefixes *nets, *tmp;
- u8 i, j, len = 0;
+ struct net_prefixes *nets;
+ struct net_prefix np;
int found;
+ u8 i, j;
+
+ BUILD_BUG_ON(sizeof(struct net_prefix) != sizeof(u32));
spin_lock_bh(&set->lock);
nets = __ipset_dereference(h->rnets[n]);
for (i = 0, found = -1; i < nets->len; i++) {
- if (nets->nets[i].count)
- len++;
- if (nets->nets[i].cidr == cidr)
+ np = READ_ONCE(nets->nets[i]);
+ if (np.count && np.cidr == cidr) {
+ np.count--;
found = i;
+ break;
+ }
}
if (unlikely(found == -1))
goto unlock;
- nets->nets[found].count--;
- if (nets->nets[found].count)
- goto unlock;
- len--;
- tmp = kzalloc_flex(*tmp, nets, len, GFP_ATOMIC);
- if (!tmp)
- /* Leave a hole */
+ if (np.count) {
+ WRITE_ONCE(nets->nets[found], np);
goto unlock;
+ }
- tmp->len = len;
+ write_seqcount_begin(&nets->seq);
for (i = 0, j = 0; i < nets->len; i++) {
- if (!nets->nets[i].count || i == found)
+ if (i == found)
continue;
- tmp->nets[j].cidr = nets->nets[i].cidr;
- tmp->nets[j++].count = nets->nets[i].count;
+
+ np = READ_ONCE(nets->nets[i]);
+ if (i != j)
+ WRITE_ONCE(nets->nets[j], np);
+ j++;
}
- rcu_assign_pointer(h->rnets[n], tmp);
- kfree_rcu(nets, rcu);
+
+ while (j < nets->len)
+ WRITE_ONCE(nets->nets[j++], (struct net_prefix){});
+ write_seqcount_end(&nets->seq);
unlock:
spin_unlock_bh(&set->lock);
}
@@ -451,7 +510,7 @@ mtype_flush(struct ip_set *set)
{
struct htype *h = set->data;
#ifdef IP_SET_HASH_WITH_NETS
- struct net_prefixes *nets, *tmp;
+ struct net_prefixes *nets;
#endif
struct htable *t;
struct hbucket *n;
@@ -477,17 +536,15 @@ mtype_flush(struct ip_set *set)
}
#ifdef IP_SET_HASH_WITH_NETS
for (i = 0; i < IPSET_NET_COUNT; i++) {
- nets = ipset_dereference_nfnl(h->rnets[i]);
- tmp = kzalloc_obj(*tmp, GFP_ATOMIC);
- if (!tmp) {
- u8 j;
+ u8 j;
- for (j = 0; j < nets->len; j++)
- nets->nets[j].count = 0;
- } else {
- rcu_assign_pointer(h->rnets[i], tmp);
- kfree_rcu(nets, rcu);
- }
+ spin_lock_bh(&set->lock);
+ nets = ipset_dereference_nfnl(h->rnets[i]);
+ write_seqcount_begin(&nets->seq);
+ for (j = 0; j < nets->len; j++)
+ WRITE_ONCE(nets->nets[j], (struct net_prefix){});
+ write_seqcount_end(&nets->seq);
+ spin_unlock_bh(&set->lock);
}
#endif
}
@@ -1253,31 +1310,41 @@ mtype_test_cidrs(struct ip_set *set, struct mtype_elem *d,
#if IPSET_NET_COUNT == 2
struct net_prefixes *nets1;
struct mtype_elem orig = *d;
+ unsigned int seq1;
int ret, i, j, k;
#else
int ret, i, j;
#endif
- u32 key, multi = 0;
+ unsigned int seq0;
+ u32 key, multi;
u8 pos;
pr_debug("test by nets\n");
rcu_read_lock_bh();
+retry:
+ multi = 0;
nets0 = rcu_dereference_bh(h->rnets[0]);
+ seq0 = read_seqcount_begin(&nets0->seq);
#if IPSET_NET_COUNT == 2
nets1 = rcu_dereference_bh(h->rnets[1]);
+ seq1 = read_seqcount_begin(&nets1->seq);
#endif
for (j = 0; j < nets0->len && !multi; j++) {
- if (!nets0->nets[j].count)
+ struct net_prefix p0 = READ_ONCE(nets0->nets[j]);
+
+ if (!p0.count)
continue;
#if IPSET_NET_COUNT == 2
mtype_data_reset_elem(d, &orig);
- mtype_data_netmask(d, nets0->nets[j].cidr, false);
+ mtype_data_netmask(d, p0.cidr, false);
for (k = 0; k < nets1->len && !multi; k++) {
- if (!nets1->nets[k].count)
+ struct net_prefix p1 = READ_ONCE(nets1->nets[k]);
+
+ if (!p1.count)
continue;
- mtype_data_netmask(d, nets1->nets[k].cidr, true);
+ mtype_data_netmask(d, p1.cidr, true);
#else
- mtype_data_netmask(d, nets0->nets[j].cidr);
+ mtype_data_netmask(d, p0.cidr);
#endif
key = HKEY(d, h->initval, t->htable_bits);
n = rcu_dereference_bh(hbucket(t, key));
@@ -1304,6 +1371,12 @@ mtype_test_cidrs(struct ip_set *set, struct mtype_elem *d,
}
ret = 0;
unlock:
+ if (read_seqcount_retry(&nets0->seq, seq0))
+ goto retry;
+#if IPSET_NET_COUNT == 2
+ if (read_seqcount_retry(&nets1->seq, seq1))
+ goto retry;
+#endif
rcu_read_unlock_bh();
return ret;
}
@@ -1707,6 +1780,7 @@ IPSET_TOKEN(HTYPE, _create)(struct net *net, struct ip_set *set,
kfree(rcu_dereference_raw(h->rnets[--i]));
goto free_hregion;
}
+ seqcount_spinlock_init(&nets->seq, &set->lock);
RCU_INIT_POINTER(h->rnets[i], nets);
}
#endif
diff --git a/net/netfilter/ipset/ip_set_hash_netiface.c b/net/netfilter/ipset/ip_set_hash_netiface.c
index b44b95f766b7..b602cc43565d 100644
--- a/net/netfilter/ipset/ip_set_hash_netiface.c
+++ b/net/netfilter/ipset/ip_set_hash_netiface.c
@@ -38,7 +38,6 @@ MODULE_ALIAS("ip_set_hash:net,iface");
#define HTYPE hash_netiface
#define IP_SET_HASH_WITH_NETS
#define IP_SET_HASH_WITH_MULTI
-#define IP_SET_HASH_WITH_NET0
#define STRSCPY(a, b) strscpy(a, b, IFNAMSIZ)
diff --git a/net/netfilter/ipset/ip_set_hash_netportnet.c b/net/netfilter/ipset/ip_set_hash_netportnet.c
index 6291532be7a5..61af1ce27127 100644
--- a/net/netfilter/ipset/ip_set_hash_netportnet.c
+++ b/net/netfilter/ipset/ip_set_hash_netportnet.c
@@ -36,7 +36,6 @@ MODULE_ALIAS("ip_set_hash:net,port,net");
#define IP_SET_HASH_WITH_PROTO
#define IP_SET_HASH_WITH_NETS
#define IPSET_NET_COUNT 2
-#define IP_SET_HASH_WITH_NET0
/* IPv4 variant */
--
2.47.3
^ permalink raw reply related [flat|nested] 9+ messages in thread
* [PATCH net-next 3/8] netfilter: nf_tables: don't queue packet path object notifications
2026-08-17 23:29 [PATCH net-next 0/8] Netfilter/IPVS fixes for net-next Pablo Neira Ayuso
2026-08-17 23:29 ` [PATCH net-next 1/8] netfilter: validate L4 headers after userspace packet writes Pablo Neira Ayuso
2026-08-17 23:29 ` [PATCH net-next 2/8] netfilter: ipset: remove need to allocate memory on delete operations Pablo Neira Ayuso
@ 2026-08-17 23:29 ` Pablo Neira Ayuso
2026-08-17 23:29 ` [PATCH net-next 4/8] netfilter: nf_conntrack_expect: consolidate check for insertion of dead expectation Pablo Neira Ayuso
` (4 subsequent siblings)
7 siblings, 0 replies; 9+ messages in thread
From: Pablo Neira Ayuso @ 2026-08-17 23:29 UTC (permalink / raw)
To: netfilter-devel; +Cc: davem, netdev, kuba, pabeni, edumazet, horms, fw, ja
From: Fourie Zhang <littleddfu@gmail.com>
All file:line references below are against v7.2-rc4 (ac5b0e5651b1). The
trace was captured on 7.2.0-rc6-kasan72rc6 (075b74841bd0), where the same
lines apply.
nft_obj_notify() is exported and reached from the packet path. Its only
in-tree caller is nft_quota_obj_eval() (net/netfilter/nft_quota.c:68),
which notifies with GFP_ATOMIC while evaluating a rule for a transiting
packet, holding no mutex.
Since commit 67cc570edaa0 ("netfilter: nf_tables: coalesce multiple
notifications into one skbuff") that notification is no longer sent
immediately. __nft_obj_notify() queues it onto nft_net->notify_list via
nft_notify_enqueue() (net/netfilter/nf_tables_api.c:1211), which is a bare
list_add_tail(). notify_list has no lock of its own
(include/net/netfilter/nf_tables.h:1951), it is serialised by commit_mutex:
the six other enqueue sites all run inside a netlink transaction, and the
drain in nft_commit_notify() (net/netfilter/nf_tables_api.c:10746) does
list_del() + kfree_skb() from nf_tables_commit() with commit_mutex held.
Sending packets through a chain that references a depleted quota object
therefore races an unlocked list_add_tail() against list_del() +
kfree_skb() on another CPU. The WRITE_ONCE(prev->next, new) in __list_add()
then stores through an sk_buff that has already been freed:
BUG: KASAN: slab-use-after-free in __nft_obj_notify+0x2c5/0x2d0
Write of size 8 at addr ff110001047183c0 by task poc/76
CPU: 0 UID: 1000 PID: 76 Comm: poc Tainted: G W 7.2.0-rc6-kasan72rc6 #4
Call Trace:
<IRQ>
__nft_obj_notify (include/linux/list.h:164 include/linux/list.h:191
net/netfilter/nf_tables_api.c:1211
net/netfilter/nf_tables_api.c:8743)
nft_quota_obj_eval (net/netfilter/nft_quota.c:68)
nft_do_chain_inet
nf_hook_slow
__ip_local_out
ip_push_pending_frames
udp_send_skb
udp_sendmsg
__x64_sys_sendto
Allocated by task 77:
__alloc_skb (net/core/skbuff.c:704)
__nft_obj_notify (include/net/netlink.h:1055
net/netfilter/nf_tables_api.c:8731)
nft_quota_obj_eval (net/netfilter/nft_quota.c:68)
nft_do_chain
Freed by task 79:
nf_tables_commit (include/linux/skbuff.h:1332
net/netfilter/nf_tables_api.c:10759
net/netfilter/nf_tables_api.c:11185)
nfnetlink_rcv_batch (net/netfilter/nfnetlink.c:574)
netlink_unicast
netlink_sendmsg
The buggy address belongs to the cache skbuff_head_cache of size 232
Queueing from the packet path is wrong even leaving the race aside:
notify_list is only drained by nft_commit_notify() from nf_tables_commit()
(:11185), so a notification enqueued outside a transaction is not sent
until some later netlink batch commits, if one ever does.
The gfp argument that nft_obj_notify() still takes is a leftover of the
pre-67cc570edaa0 behaviour, where this path called nfnetlink_send()
directly. Restore that: split the message construction out into
nft_obj_notify_alloc() and let each caller decide what to do with the skb.
nft_obj_notify(), the exported one reached from the packet path, sends it
straight away; nf_tables_obj_notify(), which runs under commit_mutex, keeps
queueing it, so transaction notifications are still coalesced.
Fixes: 67cc570edaa0 ("netfilter: nf_tables: coalesce multiple notifications into one skbuff")
Cc: stable@kernel.org
Reported-by: TencentOS Corvus AI <corvus@tencent.com>
Assisted-by: tencentos-corvus-ai:kimi-k3
Signed-off-by: Fourie Zhang <fouriezhang@tencent.com>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
---
net/netfilter/nf_tables_api.c | 36 ++++++++++++++++++++++-------------
1 file changed, 23 insertions(+), 13 deletions(-)
diff --git a/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c
index af357f6c5070..3a7c8f7a6304 100644
--- a/net/netfilter/nf_tables_api.c
+++ b/net/netfilter/nf_tables_api.c
@@ -8715,18 +8715,17 @@ static int nf_tables_delobj(struct sk_buff *skb, const struct nfnl_info *info,
return nft_delobj(&ctx, obj);
}
-static void
-__nft_obj_notify(struct net *net, const struct nft_table *table,
- struct nft_object *obj, u32 portid, u32 seq, int event,
- u16 flags, int family, int report, gfp_t gfp)
+static struct sk_buff *
+nft_obj_notify_alloc(struct net *net, const struct nft_table *table,
+ struct nft_object *obj, u32 portid, u32 seq, int event,
+ u16 flags, int family, int report, gfp_t gfp)
{
- struct nftables_pernet *nft_net = nft_pernet(net);
struct sk_buff *skb;
int err;
if (!report &&
!nfnetlink_has_listeners(net, NFNLGRP_NFTABLES))
- return;
+ return NULL;
skb = nlmsg_new(NLMSG_GOODSIZE, gfp);
if (skb == NULL)
@@ -8740,10 +8739,10 @@ __nft_obj_notify(struct net *net, const struct nft_table *table,
goto err;
}
- nft_notify_enqueue(skb, report, &nft_net->notify_list);
- return;
+ return skb;
err:
nfnetlink_set_err(net, portid, NFNLGRP_NFTABLES, -ENOBUFS);
+ return NULL;
}
void nft_obj_notify(struct net *net, const struct nft_table *table,
@@ -8752,6 +8751,7 @@ void nft_obj_notify(struct net *net, const struct nft_table *table,
{
char *buf = kasprintf(gfp, "%s:%u",
table->name, nft_base_seq(net));
+ struct sk_buff *skb;
audit_log_nfcfg(buf,
family,
@@ -8762,17 +8762,27 @@ void nft_obj_notify(struct net *net, const struct nft_table *table,
gfp);
kfree(buf);
- __nft_obj_notify(net, table, obj, portid, seq, event,
- flags, family, report, gfp);
+ /* Called from the packet path, holding no mutex: notify_list is
+ * serialised by commit_mutex, so send this notification directly.
+ */
+ skb = nft_obj_notify_alloc(net, table, obj, portid, seq, event,
+ flags, family, report, gfp);
+ if (skb)
+ nfnetlink_send(skb, net, portid, NFNLGRP_NFTABLES, report, gfp);
}
EXPORT_SYMBOL_GPL(nft_obj_notify);
static void nf_tables_obj_notify(const struct nft_ctx *ctx,
struct nft_object *obj, int event)
{
- __nft_obj_notify(ctx->net, ctx->table, obj, ctx->portid,
- ctx->seq, event, ctx->flags, ctx->family,
- ctx->report, GFP_KERNEL);
+ struct nftables_pernet *nft_net = nft_pernet(ctx->net);
+ struct sk_buff *skb;
+
+ skb = nft_obj_notify_alloc(ctx->net, ctx->table, obj, ctx->portid,
+ ctx->seq, event, ctx->flags, ctx->family,
+ ctx->report, GFP_KERNEL);
+ if (skb)
+ nft_notify_enqueue(skb, ctx->report, &nft_net->notify_list);
}
/*
--
2.47.3
^ permalink raw reply related [flat|nested] 9+ messages in thread
* [PATCH net-next 4/8] netfilter: nf_conntrack_expect: consolidate check for insertion of dead expectation
2026-08-17 23:29 [PATCH net-next 0/8] Netfilter/IPVS fixes for net-next Pablo Neira Ayuso
` (2 preceding siblings ...)
2026-08-17 23:29 ` [PATCH net-next 3/8] netfilter: nf_tables: don't queue packet path object notifications Pablo Neira Ayuso
@ 2026-08-17 23:29 ` Pablo Neira Ayuso
2026-08-17 23:29 ` [PATCH net-next 5/8] netfilter: ctnetlink: do not expose expectation DEAD flag Pablo Neira Ayuso
` (3 subsequent siblings)
7 siblings, 0 replies; 9+ messages in thread
From: Pablo Neira Ayuso @ 2026-08-17 23:29 UTC (permalink / raw)
To: netfilter-devel; +Cc: davem, netdev, kuba, pabeni, edumazet, horms, fw, ja
Consolidate the check for buggy expectations with DEAD flag on
insertion, which is called both by nf_ct_expect_related() and
nf_ct_expect_related_pair().
Fixes: e765c95faa10 ("netfilter: nf_conntrack_expect: bail out on insert dead expectations")
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
---
net/netfilter/nf_conntrack_expect.c | 11 +++++------
1 file changed, 5 insertions(+), 6 deletions(-)
diff --git a/net/netfilter/nf_conntrack_expect.c b/net/netfilter/nf_conntrack_expect.c
index f1f0c582db5d..06242c86e1dc 100644
--- a/net/netfilter/nf_conntrack_expect.c
+++ b/net/netfilter/nf_conntrack_expect.c
@@ -477,6 +477,11 @@ static inline int __nf_ct_expect_check(struct nf_conntrack_expect *expect,
lockdep_nfct_expect_lock_held();
+ if (expect->flags & NF_CT_EXPECT_DEAD) {
+ DEBUG_NET_WARN_ON_ONCE(1);
+ return -EINVAL;
+ }
+
h = nf_ct_expect_dst_hash(net, &expect->tuple);
hlist_for_each_entry_safe(i, next, &nf_ct_expect_hash[h], hnode) {
if (nf_ct_exp_is_expired(i)) {
@@ -528,12 +533,6 @@ int nf_ct_expect_related_report(struct nf_conntrack_expect *expect,
int ret;
spin_lock_bh(&nf_conntrack_expect_lock);
- if (expect->flags & NF_CT_EXPECT_DEAD) {
- DEBUG_NET_WARN_ON_ONCE(1);
- ret = -EINVAL;
- goto out;
- }
-
master_help = nfct_help(expect->master);
if (!master_help) {
ret = -ESHUTDOWN;
--
2.47.3
^ permalink raw reply related [flat|nested] 9+ messages in thread
* [PATCH net-next 5/8] netfilter: ctnetlink: do not expose expectation DEAD flag
2026-08-17 23:29 [PATCH net-next 0/8] Netfilter/IPVS fixes for net-next Pablo Neira Ayuso
` (3 preceding siblings ...)
2026-08-17 23:29 ` [PATCH net-next 4/8] netfilter: nf_conntrack_expect: consolidate check for insertion of dead expectation Pablo Neira Ayuso
@ 2026-08-17 23:29 ` Pablo Neira Ayuso
2026-08-17 23:29 ` [PATCH net-next 6/8] netfilter: nf_tables: move set_update_list to nftables per-netns Pablo Neira Ayuso
` (2 subsequent siblings)
7 siblings, 0 replies; 9+ messages in thread
From: Pablo Neira Ayuso @ 2026-08-17 23:29 UTC (permalink / raw)
To: netfilter-devel; +Cc: davem, netdev, kuba, pabeni, edumazet, horms, fw, ja
Expose expectation flags included in the NF_CT_EXPECT_MASK bitmask
only. The DEAD flag is internal, do not expose it.
Fixes: b8b09dc2bf35 ("netfilter: nf_conntrack_expect: use conntrack GC to reap expectations")
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
---
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 fc3f60099af3..9b4e29557ec3 100644
--- a/net/netfilter/nf_conntrack_netlink.c
+++ b/net/netfilter/nf_conntrack_netlink.c
@@ -3042,7 +3042,7 @@ ctnetlink_exp_dump_expect(struct sk_buff *skb,
#endif
if (nla_put_be32(skb, CTA_EXPECT_TIMEOUT, htonl(timeout)) ||
nla_put_be32(skb, CTA_EXPECT_ID, nf_expect_get_id(exp)) ||
- nla_put_be32(skb, CTA_EXPECT_FLAGS, htonl(exp->flags)) ||
+ nla_put_be32(skb, CTA_EXPECT_FLAGS, htonl(exp->flags & NF_CT_EXPECT_MASK)) ||
nla_put_be32(skb, CTA_EXPECT_CLASS, htonl(exp->class)))
goto nla_put_failure;
--
2.47.3
^ permalink raw reply related [flat|nested] 9+ messages in thread
* [PATCH net-next 6/8] netfilter: nf_tables: move set_update_list to nftables per-netns
2026-08-17 23:29 [PATCH net-next 0/8] Netfilter/IPVS fixes for net-next Pablo Neira Ayuso
` (4 preceding siblings ...)
2026-08-17 23:29 ` [PATCH net-next 5/8] netfilter: ctnetlink: do not expose expectation DEAD flag Pablo Neira Ayuso
@ 2026-08-17 23:29 ` Pablo Neira Ayuso
2026-08-17 23:29 ` [PATCH net-next 7/8] netfilter: nf_tables: call set ops .commit when building new ruleset blob Pablo Neira Ayuso
2026-08-17 23:29 ` [PATCH net-next 8/8] ipvs: fix integer overflow in ftp helper port/address parsing Pablo Neira Ayuso
7 siblings, 0 replies; 9+ messages in thread
From: Pablo Neira Ayuso @ 2026-08-17 23:29 UTC (permalink / raw)
To: netfilter-devel; +Cc: davem, netdev, kuba, pabeni, edumazet, horms, fw, ja
This list is used to invoke the set .commit and .abort ops for the
rbtree and pipapo to run GC on expired elements and replace the current
datastructure view by the clone. For the rbtree, this also rebuild the
datapath b-search array.
From abort path, remove the set from the update_list if it is already
bound to rule, then the rule itself takes care of releasing the set and
its elements, otherwise, memleak is possible because set ops .abort
only deals with removing the set data structure, not the elements.
This is a preparation patch to call set .commit before processing the
transaction list for the rbtree, no functional changes are intended.
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
---
include/net/netfilter/nf_tables.h | 1 +
net/netfilter/nf_tables_api.c | 49 ++++++++++---------------------
2 files changed, 16 insertions(+), 34 deletions(-)
diff --git a/include/net/netfilter/nf_tables.h b/include/net/netfilter/nf_tables.h
index 3be612145c13..238f6ecb90e9 100644
--- a/include/net/netfilter/nf_tables.h
+++ b/include/net/netfilter/nf_tables.h
@@ -1949,6 +1949,7 @@ struct nftables_pernet {
struct list_head binding_list;
struct list_head module_list;
struct list_head notify_list;
+ struct list_head set_update_list;
struct mutex commit_mutex;
u64 table_handle;
u64 tstamp;
diff --git a/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c
index 3a7c8f7a6304..b51ba77b5151 100644
--- a/net/netfilter/nf_tables_api.c
+++ b/net/netfilter/nf_tables_api.c
@@ -595,10 +595,15 @@ static void nft_trans_commit_list_add_tail(struct net *net, struct nft_trans *tr
static void nft_trans_commit_list_add_elem(struct net *net, struct nft_trans *trans)
{
struct nftables_pernet *nft_net = nft_pernet(net);
+ struct nft_trans_elem *te;
WARN_ON_ONCE(trans->msg_type != NFT_MSG_NEWSETELEM &&
trans->msg_type != NFT_MSG_DELSETELEM);
+ te = nft_trans_container_elem(trans);
+ if (te->set->ops->commit && list_empty(&te->set->pending_update))
+ list_add_tail(&te->set->pending_update, &nft_net->set_update_list);
+
if (nft_trans_try_collapse(nft_net, trans)) {
kfree(trans);
return;
@@ -10858,11 +10863,11 @@ static void nf_tables_commit_audit_log(struct list_head *adl, u32 generation)
}
}
-static void nft_set_commit_update(struct list_head *set_update_list)
+static void nft_set_commit_update(struct nftables_pernet *nft_net)
{
struct nft_set *set, *next;
- list_for_each_entry_safe(set, next, set_update_list, pending_update) {
+ list_for_each_entry_safe(set, next, &nft_net->set_update_list, pending_update) {
list_del_init(&set->pending_update);
if (!set->ops->commit || set->dead)
@@ -10895,7 +10900,6 @@ static int nf_tables_commit(struct net *net, struct sk_buff *skb)
struct nft_trans_binding *trans_binding;
struct nft_trans *trans, *next;
unsigned int base_seq, gc_seq;
- LIST_HEAD(set_update_list);
struct nft_trans_elem *te;
struct nft_chain *chain;
struct nft_table *table;
@@ -11101,27 +11105,13 @@ static int nf_tables_commit(struct net *net, struct sk_buff *skb)
break;
case NFT_MSG_NEWSETELEM:
te = nft_trans_container_elem(trans);
-
nft_trans_elems_add(&ctx, te);
-
- if (te->set->ops->commit &&
- list_empty(&te->set->pending_update)) {
- list_add_tail(&te->set->pending_update,
- &set_update_list);
- }
nft_trans_destroy(trans);
break;
case NFT_MSG_DELSETELEM:
case NFT_MSG_DESTROYSETELEM:
te = nft_trans_container_elem(trans);
-
nft_trans_elems_remove(&ctx, te);
-
- if (te->set->ops->commit &&
- list_empty(&te->set->pending_update)) {
- list_add_tail(&te->set->pending_update,
- &set_update_list);
- }
break;
case NFT_MSG_NEWOBJ:
if (nft_trans_obj_update(trans)) {
@@ -11190,7 +11180,7 @@ static int nf_tables_commit(struct net *net, struct sk_buff *skb)
}
}
- nft_set_commit_update(&set_update_list);
+ nft_set_commit_update(nft_net);
nft_commit_notify(net, NETLINK_CB(skb).portid);
nf_tables_gen_notify(net, skb, NFT_MSG_NEWGEN);
@@ -11257,11 +11247,11 @@ static void nf_tables_abort_release(struct nft_trans *trans)
kfree(trans);
}
-static void nft_set_abort_update(struct list_head *set_update_list)
+static void nft_set_abort_update(struct nftables_pernet *nft_net)
{
struct nft_set *set, *next;
- list_for_each_entry_safe(set, next, set_update_list, pending_update) {
+ list_for_each_entry_safe(set, next, &nft_net->set_update_list, pending_update) {
list_del_init(&set->pending_update);
if (!set->ops->abort)
@@ -11396,33 +11386,22 @@ static int __nf_tables_abort(struct net *net, enum nfnl_abort_action action)
nft_trans_destroy(trans);
break;
case NFT_MSG_NEWSETELEM:
+ te = nft_trans_container_elem(trans);
if (nft_trans_elem_set_bound(trans)) {
+ list_del_init(&te->set->pending_update);
nft_trans_destroy(trans);
break;
}
- te = nft_trans_container_elem(trans);
if (!nft_trans_elems_new_abort(&ctx, te)) {
nft_trans_destroy(trans);
break;
}
-
- if (te->set->ops->abort &&
- list_empty(&te->set->pending_update)) {
- list_add_tail(&te->set->pending_update,
- &set_update_list);
- }
break;
case NFT_MSG_DELSETELEM:
case NFT_MSG_DESTROYSETELEM:
te = nft_trans_container_elem(trans);
nft_trans_elems_destroy_abort(&ctx, te);
-
- if (te->set->ops->abort &&
- list_empty(&te->set->pending_update)) {
- list_add_tail(&te->set->pending_update,
- &set_update_list);
- }
nft_trans_destroy(trans);
break;
case NFT_MSG_NEWOBJ:
@@ -11468,7 +11447,7 @@ static int __nf_tables_abort(struct net *net, enum nfnl_abort_action action)
WARN_ON_ONCE(!list_empty(&nft_net->commit_set_list));
- nft_set_abort_update(&set_update_list);
+ nft_set_abort_update(nft_net);
synchronize_rcu();
@@ -12152,6 +12131,7 @@ static int __net_init nf_tables_init_net(struct net *net)
INIT_LIST_HEAD(&nft_net->binding_list);
INIT_LIST_HEAD(&nft_net->module_list);
INIT_LIST_HEAD(&nft_net->notify_list);
+ INIT_LIST_HEAD(&nft_net->set_update_list);
mutex_init(&nft_net->commit_mutex);
net->nft.base_seq = 1;
nft_net->gc_seq = 0;
@@ -12196,6 +12176,7 @@ static void __net_exit nf_tables_exit_net(struct net *net)
WARN_ON_ONCE(!list_empty(&nft_net->module_list));
WARN_ON_ONCE(!list_empty(&nft_net->notify_list));
WARN_ON_ONCE(!list_empty(&nft_net->destroy_list));
+ WARN_ON_ONCE(!list_empty(&nft_net->set_update_list));
}
static void nf_tables_exit_batch(struct list_head *net_exit_list)
--
2.47.3
^ permalink raw reply related [flat|nested] 9+ messages in thread
* [PATCH net-next 7/8] netfilter: nf_tables: call set ops .commit when building new ruleset blob
2026-08-17 23:29 [PATCH net-next 0/8] Netfilter/IPVS fixes for net-next Pablo Neira Ayuso
` (5 preceding siblings ...)
2026-08-17 23:29 ` [PATCH net-next 6/8] netfilter: nf_tables: move set_update_list to nftables per-netns Pablo Neira Ayuso
@ 2026-08-17 23:29 ` Pablo Neira Ayuso
2026-08-17 23:29 ` [PATCH net-next 8/8] ipvs: fix integer overflow in ftp helper port/address parsing Pablo Neira Ayuso
7 siblings, 0 replies; 9+ messages in thread
From: Pablo Neira Ayuso @ 2026-08-17 23:29 UTC (permalink / raw)
To: netfilter-devel; +Cc: davem, netdev, kuba, pabeni, edumazet, horms, fw, ja
The rbtree set only builds the b-search array after the new ruleset has
been published through set ops .commit.
This exposes an empty set for a short time span which results in a bogus
mismatch for the following batch:
destroy table ip x
table ip x {
...
}
The same problem also affects the pipapo set backend which also provides
a set ops .commit interface too.
This patch moves the set ops .commit call right before building and
publishing the chain blob. The commit path now performs an early
handling of the DELSETELEM command to remove stale elements from the
clone before it is published via rcu. Note that DELSETELEM notifications
are still delivered in order. NEWSETELEM commands are handled after the
set is published, since this clears the previous genbit to 1 to prepare
the element for the next control plane transaction. This comes at the
cost of one extra iteration over the transaction list.
Suggested-by: Florian Westphal <fw@strlen.de>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
---
net/netfilter/nf_tables_api.c | 56 ++++++++++++++++++++++++++++++-----
1 file changed, 48 insertions(+), 8 deletions(-)
diff --git a/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c
index b51ba77b5151..c112ecc4fca3 100644
--- a/net/netfilter/nf_tables_api.c
+++ b/net/netfilter/nf_tables_api.c
@@ -7191,16 +7191,19 @@ static void nft_setelem_remove(const struct net *net,
}
static void nft_trans_elems_remove(const struct nft_ctx *ctx,
- const struct nft_trans_elem *te)
+ const struct nft_trans_elem *te,
+ bool notify)
{
int i;
for (i = 0; i < te->nelems; i++) {
WARN_ON_ONCE(te->elems[i].update);
- nf_tables_setelem_notify(ctx, te->set,
- te->elems[i].priv,
- te->nft_trans.msg_type);
+ if (notify) {
+ nf_tables_setelem_notify(ctx, te->set,
+ te->elems[i].priv,
+ te->nft_trans.msg_type);
+ }
nft_setelem_remove(ctx->net, te->set, te->elems[i].priv);
if (!nft_setelem_is_catchall(te->set, te->elems[i].priv)) {
@@ -7210,6 +7213,20 @@ static void nft_trans_elems_remove(const struct nft_ctx *ctx,
}
}
+static void nft_trans_elems_remove_notify(const struct nft_ctx *ctx,
+ const struct nft_trans_elem *te)
+{
+ int i;
+
+ for (i = 0; i < te->nelems; i++) {
+ WARN_ON_ONCE(te->elems[i].update);
+
+ nf_tables_setelem_notify(ctx, te->set,
+ te->elems[i].priv,
+ te->nft_trans.msg_type);
+ }
+}
+
static bool nft_setelem_valid_key_end(const struct nft_set *set,
struct nlattr **nla, u32 flags)
{
@@ -10863,9 +10880,29 @@ static void nf_tables_commit_audit_log(struct list_head *adl, u32 generation)
}
}
-static void nft_set_commit_update(struct nftables_pernet *nft_net)
+static void nft_set_commit_update(struct nft_ctx *ctx,
+ struct nftables_pernet *nft_net)
{
struct nft_set *set, *next;
+ struct nft_trans_elem *te;
+ struct nft_trans *trans;
+
+ if (list_empty(&nft_net->set_update_list))
+ return;
+
+ list_for_each_entry(trans, &nft_net->commit_list, list) {
+ nft_ctx_update(ctx, trans);
+
+ switch (trans->msg_type) {
+ case NFT_MSG_DELSETELEM:
+ te = nft_trans_container_elem(trans);
+ if (!te->set->ops->commit)
+ break;
+
+ nft_trans_elems_remove(ctx, te, false);
+ break;
+ }
+ }
list_for_each_entry_safe(set, next, &nft_net->set_update_list, pending_update) {
list_del_init(&set->pending_update);
@@ -10974,6 +11011,8 @@ static int nf_tables_commit(struct net *net, struct sk_buff *skb)
}
/* step 2. Make rules_gen_X visible to packet path */
+ nft_set_commit_update(&ctx, nft_net);
+
list_for_each_entry(table, &nft_net->tables, list) {
list_for_each_entry(chain, &table->chains, list)
nf_tables_commit_chain(net, chain);
@@ -11111,7 +11150,10 @@ static int nf_tables_commit(struct net *net, struct sk_buff *skb)
case NFT_MSG_DELSETELEM:
case NFT_MSG_DESTROYSETELEM:
te = nft_trans_container_elem(trans);
- nft_trans_elems_remove(&ctx, te);
+ if (te->set->ops->commit)
+ nft_trans_elems_remove_notify(&ctx, te);
+ else
+ nft_trans_elems_remove(&ctx, te, true);
break;
case NFT_MSG_NEWOBJ:
if (nft_trans_obj_update(trans)) {
@@ -11180,8 +11222,6 @@ static int nf_tables_commit(struct net *net, struct sk_buff *skb)
}
}
- nft_set_commit_update(nft_net);
-
nft_commit_notify(net, NETLINK_CB(skb).portid);
nf_tables_gen_notify(net, skb, NFT_MSG_NEWGEN);
nf_tables_commit_audit_log(&adl, nft_base_seq(net));
--
2.47.3
^ permalink raw reply related [flat|nested] 9+ messages in thread
* [PATCH net-next 8/8] ipvs: fix integer overflow in ftp helper port/address parsing
2026-08-17 23:29 [PATCH net-next 0/8] Netfilter/IPVS fixes for net-next Pablo Neira Ayuso
` (6 preceding siblings ...)
2026-08-17 23:29 ` [PATCH net-next 7/8] netfilter: nf_tables: call set ops .commit when building new ruleset blob Pablo Neira Ayuso
@ 2026-08-17 23:29 ` Pablo Neira Ayuso
7 siblings, 0 replies; 9+ messages in thread
From: Pablo Neira Ayuso @ 2026-08-17 23:29 UTC (permalink / raw)
To: netfilter-devel; +Cc: davem, netdev, kuba, pabeni, edumazet, horms, fw, ja
From: Joas Antonio dos Santos <joasantonio108@gmail.com>
ip_vs_ftp_get_addrport() accumulates decimal digits into a __u16
(hport) and into unsigned char (p[]) without checking for overflow.
A crafted FTP PASV/EPSV response with an over-long port or address
octet wraps the value, so the helper configures the data connection
with a truncated port/address.
The netfilter conntrack FTP helper had the same defect, fixed in
commit 2b413fc689ba ("netfilter: nf_conntrack_ftp: avoid u16
overflows"). Apply the equivalent fix here: widen the port accumulator
to u32 and reject values above 65535, and reject address octets above
255.
Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Signed-off-by: Joas Antonio dos Santos <joasantonio108@gmail.com>
Acked-by: Julian Anastasov <ja@ssi.bg>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
---
net/netfilter/ipvs/ip_vs_ftp.c | 10 ++++++++--
1 file changed, 8 insertions(+), 2 deletions(-)
diff --git a/net/netfilter/ipvs/ip_vs_ftp.c b/net/netfilter/ipvs/ip_vs_ftp.c
index b315c608fda4..9e3e005a8263 100644
--- a/net/netfilter/ipvs/ip_vs_ftp.c
+++ b/net/netfilter/ipvs/ip_vs_ftp.c
@@ -102,7 +102,7 @@ static int ip_vs_ftp_get_addrport(char *data, char *data_limit,
char *s, c;
unsigned char p[6];
char edelim;
- __u16 hport;
+ __u32 hport;
int i = 0;
if (data_limit - data < plen) {
@@ -144,7 +144,11 @@ static int ip_vs_ftp_get_addrport(char *data, char *data_limit,
return -1;
c = *data;
if (isdigit(c)) {
- p[i] = p[i]*10 + c - '0';
+ unsigned int val = p[i] * 10 + c - '0';
+
+ if (val > 255)
+ return -1;
+ p[i] = val;
} else if (c == ',' && i < 5) {
i++;
p[i] = 0;
@@ -222,6 +226,8 @@ static int ip_vs_ftp_get_addrport(char *data, char *data_limit,
if (!isdigit(*s))
break;
hport = hport * 10 + *s - '0';
+ if (hport > 65535)
+ return -1;
}
if (s == data_limit || !hport || *s != edelim)
return -1;
--
2.47.3
^ permalink raw reply related [flat|nested] 9+ messages in thread
end of thread, other threads:[~2026-08-17 23:30 UTC | newest]
Thread overview: 9+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-17 23:29 [PATCH net-next 0/8] Netfilter/IPVS fixes for net-next Pablo Neira Ayuso
2026-08-17 23:29 ` [PATCH net-next 1/8] netfilter: validate L4 headers after userspace packet writes Pablo Neira Ayuso
2026-08-17 23:29 ` [PATCH net-next 2/8] netfilter: ipset: remove need to allocate memory on delete operations Pablo Neira Ayuso
2026-08-17 23:29 ` [PATCH net-next 3/8] netfilter: nf_tables: don't queue packet path object notifications Pablo Neira Ayuso
2026-08-17 23:29 ` [PATCH net-next 4/8] netfilter: nf_conntrack_expect: consolidate check for insertion of dead expectation Pablo Neira Ayuso
2026-08-17 23:29 ` [PATCH net-next 5/8] netfilter: ctnetlink: do not expose expectation DEAD flag Pablo Neira Ayuso
2026-08-17 23:29 ` [PATCH net-next 6/8] netfilter: nf_tables: move set_update_list to nftables per-netns Pablo Neira Ayuso
2026-08-17 23:29 ` [PATCH net-next 7/8] netfilter: nf_tables: call set ops .commit when building new ruleset blob Pablo Neira Ayuso
2026-08-17 23:29 ` [PATCH net-next 8/8] ipvs: fix integer overflow in ftp helper port/address parsing Pablo Neira Ayuso
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox