* [PATCH net 01/11] netfilter: ipset: do not update comments from kernel-side adds
2026-09-27 22:08 [PATCH net 00/11] Netfilter/IPVS fixes for net Pablo Neira Ayuso
@ 2026-09-27 22:08 ` Pablo Neira Ayuso
2026-09-27 22:08 ` [PATCH net 02/11] ipvs: fix buffer overflow when sending sync messages Pablo Neira Ayuso
` (10 subsequent siblings)
11 siblings, 0 replies; 27+ messages in thread
From: Pablo Neira Ayuso @ 2026-09-27 22:08 UTC (permalink / raw)
To: netfilter-devel; +Cc: davem, netdev, kuba, pabeni, edumazet, horms, fw, ja
From: Florian Westphal <fw@strlen.de>
'Fixes' commit stopped calling ip_set_init_comment() for hash types
from kernel-side-adds (xtables .. -j SET). ip_set_init_comment() says:
"The kadt functions don't use the comment extensions in any way."
But bitmap set type calls the function from kadt cb too.
While this appears to be safe (serialized via the set spinlock), it seems
better to not call the init function either, least of all to keep
behaviour consistent.
ip_set_list calls ip_set_init_comment() only from uadt cb, it can be
kept as-is.
This was triggered by yet another LLM review, hinting that the existing
rcu_dereference_protected() cannot be downgraded to only check if the
nfnl mutex is held.
Fixes: f30415929be8 ("netfilter: ipset: do not update comments from kernel-side hash adds")
Signed-off-by: Florian Westphal <fw@strlen.de>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
---
net/netfilter/ipset/ip_set_bitmap_gen.h | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/net/netfilter/ipset/ip_set_bitmap_gen.h b/net/netfilter/ipset/ip_set_bitmap_gen.h
index d6a7e6604542..ae376fa3e7a3 100644
--- a/net/netfilter/ipset/ip_set_bitmap_gen.h
+++ b/net/netfilter/ipset/ip_set_bitmap_gen.h
@@ -159,7 +159,7 @@ mtype_add(struct ip_set *set, void *value, const struct ip_set_ext *ext,
if (SET_WITH_COUNTER(set))
ip_set_init_counter(ext_counter(x, set), ext);
- if (SET_WITH_COMMENT(set))
+ if (SET_WITH_COMMENT(set) && !ext->target)
ip_set_init_comment(set, ext_comment(x, set), ext);
if (SET_WITH_SKBINFO(set))
ip_set_init_skbinfo(ext_skbinfo(x, set), ext);
--
2.47.3
^ permalink raw reply related [flat|nested] 27+ messages in thread* [PATCH net 02/11] ipvs: fix buffer overflow when sending sync messages
2026-09-27 22:08 [PATCH net 00/11] Netfilter/IPVS fixes for net Pablo Neira Ayuso
2026-09-27 22:08 ` [PATCH net 01/11] netfilter: ipset: do not update comments from kernel-side adds Pablo Neira Ayuso
@ 2026-09-27 22:08 ` Pablo Neira Ayuso
2026-09-28 23:55 ` netdev-bot+sashiko
2026-09-27 22:08 ` [PATCH net 03/11] netfilter: nft_flow_offload: drop flowtable reference on init error path Pablo Neira Ayuso
` (9 subsequent siblings)
11 siblings, 1 reply; 27+ messages in thread
From: Pablo Neira Ayuso @ 2026-09-27 22:08 UTC (permalink / raw)
To: netfilter-devel; +Cc: davem, netdev, kuba, pabeni, edumazet, horms, fw, ja
From: Julian Anastasov <ja@ssi.bg>
Sashiko reports for possible heap buffer overflow when
generating sync message for the v0 and v1 message formats.
We should read the cp->flags only once because another
CPU can concurrently set the SEQ_MASK between the two
reads.
Note that IPVS does not set the SEQ_MASK anymore for the
ip_vs_ftp.c helper starting from commit 7f1c40757951
("IPVS: make FTP work with full NAT support") (2.6.36+),
so the problem should not be reproducible.
Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Link: https://sashiko.dev/#/patchset/20260903004149.1037028-1-pablo%40netfilter.org
Link: https://sashiko.dev/#/patchset/20260909111338.44357-1-ja%40ssi.bg
Signed-off-by: Julian Anastasov <ja@ssi.bg>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
---
net/netfilter/ipvs/ip_vs_sync.c | 23 ++++++++++++++---------
1 file changed, 14 insertions(+), 9 deletions(-)
diff --git a/net/netfilter/ipvs/ip_vs_sync.c b/net/netfilter/ipvs/ip_vs_sync.c
index 5383aeafb0ae..dfa8487ec0c2 100644
--- a/net/netfilter/ipvs/ip_vs_sync.c
+++ b/net/netfilter/ipvs/ip_vs_sync.c
@@ -543,13 +543,15 @@ static void ip_vs_sync_conn_v0(struct netns_ipvs *ipvs, struct ip_vs_conn *cp,
struct ip_vs_sync_conn_v0 *s;
struct ip_vs_sync_buff *buff;
struct ipvs_master_sync_state *ms;
- int id;
+ u32 flags, seq_mask;
unsigned int len;
+ int id;
if (unlikely(cp->af != AF_INET))
return;
+ flags = READ_ONCE(cp->flags);
/* Do not sync ONE PACKET */
- if (cp->flags & IP_VS_CONN_F_ONE_PACKET)
+ if (flags & IP_VS_CONN_F_ONE_PACKET)
return;
if (!ip_vs_sync_conn_needed(ipvs, cp, pkts))
@@ -564,8 +566,8 @@ static void ip_vs_sync_conn_v0(struct netns_ipvs *ipvs, struct ip_vs_conn *cp,
id = select_master_thread_id(ipvs, cp);
ms = &ipvs->ms[id];
buff = ms->sync_buff;
- len = (cp->flags & IP_VS_CONN_F_SEQ_MASK) ? FULL_CONN_SIZE :
- SIMPLE_CONN_SIZE;
+ seq_mask = flags & IP_VS_CONN_F_SEQ_MASK;
+ len = seq_mask ? FULL_CONN_SIZE : SIMPLE_CONN_SIZE;
if (buff) {
m = (struct ip_vs_sync_mesg_v0 *) buff->mesg;
/* Send buffer if it is for v1 */
@@ -597,9 +599,9 @@ static void ip_vs_sync_conn_v0(struct netns_ipvs *ipvs, struct ip_vs_conn *cp,
s->caddr = cp->caddr.ip;
s->vaddr = cp->vaddr.ip;
s->daddr = cp->daddr.ip;
- s->flags = htons(cp->flags & ~IP_VS_CONN_F_HASHED);
+ s->flags = htons(flags & ~IP_VS_CONN_F_HASHED);
s->state = htons(cp->state);
- if (cp->flags & IP_VS_CONN_F_SEQ_MASK) {
+ if (seq_mask) {
struct ip_vs_sync_conn_options *opt =
(struct ip_vs_sync_conn_options *)&s[1];
memcpy(opt, &cp->sync_conn_opt, sizeof(*opt));
@@ -635,6 +637,7 @@ void ip_vs_sync_conn(struct netns_ipvs *ipvs, struct ip_vs_conn *cp, int pkts)
int id;
__u8 *p;
unsigned int len, pe_name_len, pad;
+ u32 flags, seq_mask;
/* Handle old version of the protocol */
if (sysctl_sync_ver(ipvs) == 0) {
@@ -647,6 +650,7 @@ void ip_vs_sync_conn(struct netns_ipvs *ipvs, struct ip_vs_conn *cp, int pkts)
sloop:
if (!ip_vs_sync_conn_needed(ipvs, cp, pkts))
goto control;
+ flags = READ_ONCE(cp->flags);
/* Sanity checks */
pe_name_len = 0;
@@ -674,7 +678,8 @@ void ip_vs_sync_conn(struct netns_ipvs *ipvs, struct ip_vs_conn *cp, int pkts)
#endif
len = sizeof(struct ip_vs_sync_v4);
- if (cp->flags & IP_VS_CONN_F_SEQ_MASK)
+ seq_mask = flags & IP_VS_CONN_F_SEQ_MASK;
+ if (seq_mask)
len += sizeof(struct ip_vs_sync_conn_options) + 2;
if (cp->pe_data_len)
@@ -720,7 +725,7 @@ void ip_vs_sync_conn(struct netns_ipvs *ipvs, struct ip_vs_conn *cp, int pkts)
/* Set message type & copy members */
s->v4.type = (cp->af == AF_INET6 ? STYPE_F_INET6 : 0);
s->v4.ver_size = htons(len & SVER_MASK); /* Version 0 */
- s->v4.flags = htonl(cp->flags & ~IP_VS_CONN_F_HASHED);
+ s->v4.flags = htonl(flags & ~IP_VS_CONN_F_HASHED);
s->v4.state = htons(cp->state);
s->v4.protocol = cp->protocol;
s->v4.cport = cp->cport;
@@ -744,7 +749,7 @@ void ip_vs_sync_conn(struct netns_ipvs *ipvs, struct ip_vs_conn *cp, int pkts)
s->v4.vaddr = cp->vaddr.ip;
s->v4.daddr = cp->daddr.ip;
}
- if (cp->flags & IP_VS_CONN_F_SEQ_MASK) {
+ if (seq_mask) {
*(p++) = IPVS_OPT_SEQ_DATA;
*(p++) = sizeof(struct ip_vs_sync_conn_options);
hton_seq(&cp->in_seq, (struct ip_vs_seq *)p);
--
2.47.3
^ permalink raw reply related [flat|nested] 27+ messages in thread* Re: [PATCH net 02/11] ipvs: fix buffer overflow when sending sync messages
2026-09-27 22:08 ` [PATCH net 02/11] ipvs: fix buffer overflow when sending sync messages Pablo Neira Ayuso
@ 2026-09-28 23:55 ` netdev-bot+sashiko
2026-09-29 4:06 ` Julian Anastasov
0 siblings, 1 reply; 27+ messages in thread
From: netdev-bot+sashiko @ 2026-09-28 23:55 UTC (permalink / raw)
To: pablo; +Cc: netfilter-devel, davem, netdev, kuba, pabeni, edumazet, horms, fw,
ja
Thank you for your contribution! Sashiko AI review found 1 potential
issue(s) to consider.
Critical: 0 · High: 0 · Medium: 1 (1) · Low: 0
If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).
- [Medium] The commit message for "ipvs: fix buffer overflow when sending
sync messages" says the SEQ_MASK race "should not be reproducible",…
Pre-existing issues:
- [Medium] After the patch, the flags are a consistent snapshot, but the
sequence data is still read without locking and without ordering…
--
ipvs: fix buffer overflow when sending sync messages
This commit changes ip_vs_sync_conn_v0() and ip_vs_sync_conn() to read
cp->flags once with READ_ONCE(). The same value is then used to size the
sync entry and to decide whether to emit the sequence options. This
prevents a heap overflow when another CPU sets IP_VS_CONN_F_SEQ_MASK
between the two reads.
Link: https://sashiko.dev/#/patchset/20260903004149.1037028-1-pablo%40netfilter.org
Link: https://sashiko.dev/#/patchset/20260909111338.44357-1-ja%40ssi.bg
> Note that IPVS does not set the SEQ_MASK anymore for the
> ip_vs_ftp.c helper starting from commit 7f1c40757951
> ("IPVS: make FTP work with full NAT support") (2.6.36+),
> so the problem should not be reproducible.
[Severity: Medium]
Is the "should not be reproducible" part accurate?
The ip_vs_ftp part is true. ip_vs_ftp_out() and ip_vs_ftp_in() always set
*diff = 0, so vs_seq_update() is never reached. However, the SEQ bits
still seem to have another writer, and received sync messages drive it.
In ip_vs_proc_conn(), an existing connection has its flags updated under
cp->lock:
net/netfilter/ipvs/ip_vs_sync.c:ip_vs_proc_conn() {
...
flags &= IP_VS_CONN_F_BACKUP_UPD_MASK;
flags |= cp->flags & ~IP_VS_CONN_F_BACKUP_UPD_MASK;
cp->flags = flags;
...
}
IP_VS_CONN_F_BACKUP_UPD_MASK includes the SEQ bits:
#define IP_VS_CONN_F_BACKUP_UPD_MASK (IP_VS_CONN_F_INACTIVE | \
IP_VS_CONN_F_SEQ_MASK)
ip_vs_proc_sync_conn() masks the wire flags with IP_VS_CONN_F_BACKUP_MASK,
which also keeps the SEQ bits. It does not require an IPVS_OPT_SEQ_DATA
option to be present. So a peer's sync message can set or clear the SEQ
bits on an existing connection.
start_sync_thread() checks the master and backup roles separately and then
does ipvs->sync_state |= state, so one node can run both daemons. On such
a node:
CPU1 (backup kthread)
ip_vs_process_message()
ip_vs_proc_sync_conn()
ip_vs_proc_conn()
cp->flags = flags; /* SEQ bits toggled */
CPU2 (packet path, or ip_vs_conn_expire())
ip_vs_in()
ip_vs_sync_conn()
reads cp->flags to compute len, then again for IPVS_OPT_SEQ_DATA
Before this patch, that double read could size len without the options.
It could then write struct ip_vs_sync_conn_options past buff->end.
Sync traffic is unauthenticated; a later patch in this series calls it
not secure by design. Also, "ipvs: filter some flags received in the
backup server" does not filter the SEQ bits, so this writer is still
live at the end of the series.
Could the commit message drop the "should not be reproducible" claim and
mention the backup update path? The code change itself does look like it
closes the overflow.
> diff --git a/net/netfilter/ipvs/ip_vs_sync.c b/net/netfilter/ipvs/ip_vs_sync.c
> index 5383aeafb0aed..dfa8487ec0c20 100644
> --- a/net/netfilter/ipvs/ip_vs_sync.c
> +++ b/net/netfilter/ipvs/ip_vs_sync.c
[ ... ]
> @@ -647,6 +650,7 @@ void ip_vs_sync_conn(struct netns_ipvs *ipvs, struct ip_vs_conn *cp, int pkts)
> sloop:
> if (!ip_vs_sync_conn_needed(ipvs, cp, pkts))
> goto control;
> + flags = READ_ONCE(cp->flags);
>
> /* Sanity checks */
> pe_name_len = 0;
[ ... ]
> @@ -744,7 +749,7 @@ void ip_vs_sync_conn(struct netns_ipvs *ipvs, struct ip_vs_conn *cp, int pkts)
> s->v4.vaddr = cp->vaddr.ip;
> s->v4.daddr = cp->daddr.ip;
> }
> - if (cp->flags & IP_VS_CONN_F_SEQ_MASK) {
> + if (seq_mask) {
> *(p++) = IPVS_OPT_SEQ_DATA;
> *(p++) = sizeof(struct ip_vs_sync_conn_options);
> hton_seq(&cp->in_seq, (struct ip_vs_seq *)p);
[Severity: Medium]
This is a pre-existing issue, not one introduced by this patch. The flags
are now a consistent snapshot, but the sequence data is still read
without cp->lock. Nothing orders that read against READ_ONCE(cp->flags).
Can the master send stale or half-updated ip_vs_seq values here?
ip_vs_sync_conn() here only holds ipvs->sync_buff_lock. The same applies
to the memcpy() of cp->sync_conn_opt in ip_vs_sync_conn_v0().
On the backup side, ip_vs_proc_conn() sets the SEQ bits in cp->flags under
cp->lock and drops the lock. Only after that does it copy the sequence
data, with no lock held:
net/netfilter/ipvs/ip_vs_sync.c:ip_vs_proc_conn() {
...
cp->flags = flags;
spin_unlock_bh(&cp->lock);
...
if (opt) {
cp->in_seq = opt->in_seq;
cp->out_seq = opt->out_seq;
}
...
}
On a node running both the master and backup daemons, ip_vs_sync_conn()
could see seq_mask set and then send old or torn init_seq, delta and
previous_delta values. A peer with an app bound to that connection would
then apply the wrong TCP seq/ack adjustment after failover.
vs_seq_update() in ip_vs_app.c is another writer. It updates the sequence
data and flags under cp->lock, but the sync reader never takes that lock.
In-tree it is only reached with diff != 0, which ip_vs_ftp never
produces.
This does not corrupt memory, because the buffer size and the option
emission now both come from the same snapshot.
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260927220816.268206-2-pablo%40netfilter.org
^ permalink raw reply [flat|nested] 27+ messages in thread* Re: [PATCH net 02/11] ipvs: fix buffer overflow when sending sync messages
2026-09-28 23:55 ` netdev-bot+sashiko
@ 2026-09-29 4:06 ` Julian Anastasov
2026-09-29 8:19 ` Paolo Abeni
0 siblings, 1 reply; 27+ messages in thread
From: Julian Anastasov @ 2026-09-29 4:06 UTC (permalink / raw)
To: netdev-bot+sashiko
Cc: pablo, netfilter-devel, davem, netdev, kuba, pabeni, edumazet,
horms, fw
[-- Attachment #1: Type: text/plain, Size: 6474 bytes --]
Hello,
On Mon, 28 Sep 2026, netdev-bot+sashiko@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 1 potential
> issue(s) to consider.
>
> Critical: 0 · High: 0 · Medium: 1 (1) · Low: 0
>
> If you believe none of the issues are legitimate, please reply to each
> issue of Medium severity or higher and explain why the AI is wrong.
> If any of them are legitimate and you plan to respin, please let patchwork
> know by including "pw-bot: cr" as a separate line at the end of your reply
> (one such reply per series is enough).
>
> - [Medium] The commit message for "ipvs: fix buffer overflow when sending
> sync messages" says the SEQ_MASK race "should not be reproducible",…
>
> Pre-existing issues:
> - [Medium] After the patch, the flags are a consistent snapshot, but the
> sequence data is still read without locking and without ordering…
>
> --
>
> ipvs: fix buffer overflow when sending sync messages
>
> This commit changes ip_vs_sync_conn_v0() and ip_vs_sync_conn() to read
> cp->flags once with READ_ONCE(). The same value is then used to size the
> sync entry and to decide whether to emit the sequence options. This
> prevents a heap overflow when another CPU sets IP_VS_CONN_F_SEQ_MASK
> between the two reads.
>
> Link: https://sashiko.dev/#/patchset/20260903004149.1037028-1-pablo%40netfilter.org
> Link: https://sashiko.dev/#/patchset/20260909111338.44357-1-ja%40ssi.bg
>
> > Note that IPVS does not set the SEQ_MASK anymore for the
> > ip_vs_ftp.c helper starting from commit 7f1c40757951
> > ("IPVS: make FTP work with full NAT support") (2.6.36+),
> > so the problem should not be reproducible.
>
> [Severity: Medium]
> Is the "should not be reproducible" part accurate?
>
> The ip_vs_ftp part is true. ip_vs_ftp_out() and ip_vs_ftp_in() always set
> *diff = 0, so vs_seq_update() is never reached. However, the SEQ bits
> still seem to have another writer, and received sync messages drive it.
>
> In ip_vs_proc_conn(), an existing connection has its flags updated under
> cp->lock:
>
> net/netfilter/ipvs/ip_vs_sync.c:ip_vs_proc_conn() {
> ...
> flags &= IP_VS_CONN_F_BACKUP_UPD_MASK;
> flags |= cp->flags & ~IP_VS_CONN_F_BACKUP_UPD_MASK;
> cp->flags = flags;
> ...
> }
>
> IP_VS_CONN_F_BACKUP_UPD_MASK includes the SEQ bits:
>
> #define IP_VS_CONN_F_BACKUP_UPD_MASK (IP_VS_CONN_F_INACTIVE | \
> IP_VS_CONN_F_SEQ_MASK)
>
> ip_vs_proc_sync_conn() masks the wire flags with IP_VS_CONN_F_BACKUP_MASK,
> which also keeps the SEQ bits. It does not require an IPVS_OPT_SEQ_DATA
> option to be present. So a peer's sync message can set or clear the SEQ
> bits on an existing connection.
>
> start_sync_thread() checks the master and backup roles separately and then
> does ipvs->sync_state |= state, so one node can run both daemons. On such
> a node:
>
> CPU1 (backup kthread)
> ip_vs_process_message()
> ip_vs_proc_sync_conn()
> ip_vs_proc_conn()
> cp->flags = flags; /* SEQ bits toggled */
>
> CPU2 (packet path, or ip_vs_conn_expire())
> ip_vs_in()
> ip_vs_sync_conn()
> reads cp->flags to compute len, then again for IPVS_OPT_SEQ_DATA
>
> Before this patch, that double read could size len without the options.
> It could then write struct ip_vs_sync_conn_options past buff->end.
>
> Sync traffic is unauthenticated; a later patch in this series calls it
> not secure by design. Also, "ipvs: filter some flags received in the
> backup server" does not filter the SEQ bits, so this writer is still
> live at the end of the series.
>
> Could the commit message drop the "should not be reproducible" claim and
> mention the backup update path? The code change itself does look like it
> closes the overflow.
>
> > diff --git a/net/netfilter/ipvs/ip_vs_sync.c b/net/netfilter/ipvs/ip_vs_sync.c
> > index 5383aeafb0aed..dfa8487ec0c20 100644
> > --- a/net/netfilter/ipvs/ip_vs_sync.c
> > +++ b/net/netfilter/ipvs/ip_vs_sync.c
>
> [ ... ]
>
> > @@ -647,6 +650,7 @@ void ip_vs_sync_conn(struct netns_ipvs *ipvs, struct ip_vs_conn *cp, int pkts)
> > sloop:
> > if (!ip_vs_sync_conn_needed(ipvs, cp, pkts))
> > goto control;
> > + flags = READ_ONCE(cp->flags);
> >
> > /* Sanity checks */
> > pe_name_len = 0;
>
> [ ... ]
>
> > @@ -744,7 +749,7 @@ void ip_vs_sync_conn(struct netns_ipvs *ipvs, struct ip_vs_conn *cp, int pkts)
> > s->v4.vaddr = cp->vaddr.ip;
> > s->v4.daddr = cp->daddr.ip;
> > }
> > - if (cp->flags & IP_VS_CONN_F_SEQ_MASK) {
> > + if (seq_mask) {
> > *(p++) = IPVS_OPT_SEQ_DATA;
> > *(p++) = sizeof(struct ip_vs_sync_conn_options);
> > hton_seq(&cp->in_seq, (struct ip_vs_seq *)p);
>
> [Severity: Medium]
> This is a pre-existing issue, not one introduced by this patch. The flags
> are now a consistent snapshot, but the sequence data is still read
> without cp->lock. Nothing orders that read against READ_ONCE(cp->flags).
> Can the master send stale or half-updated ip_vs_seq values here?
>
> ip_vs_sync_conn() here only holds ipvs->sync_buff_lock. The same applies
> to the memcpy() of cp->sync_conn_opt in ip_vs_sync_conn_v0().
>
> On the backup side, ip_vs_proc_conn() sets the SEQ bits in cp->flags under
> cp->lock and drops the lock. Only after that does it copy the sequence
> data, with no lock held:
>
> net/netfilter/ipvs/ip_vs_sync.c:ip_vs_proc_conn() {
> ...
> cp->flags = flags;
> spin_unlock_bh(&cp->lock);
> ...
> if (opt) {
> cp->in_seq = opt->in_seq;
> cp->out_seq = opt->out_seq;
> }
> ...
> }
>
> On a node running both the master and backup daemons, ip_vs_sync_conn()
> could see seq_mask set and then send old or torn init_seq, delta and
> previous_delta values. A peer with an app bound to that connection would
> then apply the wrong TCP seq/ack adjustment after failover.
Agreed, this can be improved to take a snapshot of
flags and seqs together under lock. I'll request to drop
this version, it is not urgent to apply it.
> vs_seq_update() in ip_vs_app.c is another writer. It updates the sequence
> data and flags under cp->lock, but the sync reader never takes that lock.
> In-tree it is only reached with diff != 0, which ip_vs_ftp never
> produces.
>
> This does not corrupt memory, because the buffer size and the option
> emission now both come from the same snapshot.
>
> --
> Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260927220816.268206-2-pablo%40netfilter.org
Regards
--
Julian Anastasov <ja@ssi.bg>
^ permalink raw reply [flat|nested] 27+ messages in thread* Re: [PATCH net 02/11] ipvs: fix buffer overflow when sending sync messages
2026-09-29 4:06 ` Julian Anastasov
@ 2026-09-29 8:19 ` Paolo Abeni
2026-09-29 9:43 ` Pablo Neira Ayuso
0 siblings, 1 reply; 27+ messages in thread
From: Paolo Abeni @ 2026-09-29 8:19 UTC (permalink / raw)
To: Julian Anastasov, netdev-bot+sashiko
Cc: pablo, netfilter-devel, davem, netdev, kuba, edumazet, horms, fw
On 9/29/26 06:06, Julian Anastasov wrote:
>>> @@ -744,7 +749,7 @@ void ip_vs_sync_conn(struct netns_ipvs *ipvs, struct ip_vs_conn *cp, int pkts)
>>> s->v4.vaddr = cp->vaddr.ip;
>>> s->v4.daddr = cp->daddr.ip;
>>> }
>>> - if (cp->flags & IP_VS_CONN_F_SEQ_MASK) {
>>> + if (seq_mask) {
>>> *(p++) = IPVS_OPT_SEQ_DATA;
>>> *(p++) = sizeof(struct ip_vs_sync_conn_options);
>>> hton_seq(&cp->in_seq, (struct ip_vs_seq *)p);
>>
>> [Severity: Medium]
>> This is a pre-existing issue, not one introduced by this patch. The flags
>> are now a consistent snapshot, but the sequence data is still read
>> without cp->lock. Nothing orders that read against READ_ONCE(cp->flags).
>> Can the master send stale or half-updated ip_vs_seq values here?
>>
>> ip_vs_sync_conn() here only holds ipvs->sync_buff_lock. The same applies
>> to the memcpy() of cp->sync_conn_opt in ip_vs_sync_conn_v0().
>>
>> On the backup side, ip_vs_proc_conn() sets the SEQ bits in cp->flags under
>> cp->lock and drops the lock. Only after that does it copy the sequence
>> data, with no lock held:
>>
>> net/netfilter/ipvs/ip_vs_sync.c:ip_vs_proc_conn() {
>> ...
>> cp->flags = flags;
>> spin_unlock_bh(&cp->lock);
>> ...
>> if (opt) {
>> cp->in_seq = opt->in_seq;
>> cp->out_seq = opt->out_seq;
>> }
>> ...
>> }
>>
>> On a node running both the master and backup daemons, ip_vs_sync_conn()
>> could see seq_mask set and then send old or torn init_seq, delta and
>> previous_delta values. A peer with an app bound to that connection would
>> then apply the wrong TCP seq/ack adjustment after failover.
>
> Agreed, this can be improved to take a snapshot of
> flags and seqs together under lock. I'll request to drop
> this version, it is not urgent to apply it.
It looks like a respin of the PR is needed, I'll drop the revision from PW.
/P
^ permalink raw reply [flat|nested] 27+ messages in thread* Re: [PATCH net 02/11] ipvs: fix buffer overflow when sending sync messages
2026-09-29 8:19 ` Paolo Abeni
@ 2026-09-29 9:43 ` Pablo Neira Ayuso
2026-09-29 9:55 ` Paolo Abeni
0 siblings, 1 reply; 27+ messages in thread
From: Pablo Neira Ayuso @ 2026-09-29 9:43 UTC (permalink / raw)
To: Paolo Abeni
Cc: Julian Anastasov, netdev-bot+sashiko, netfilter-devel, davem,
netdev, kuba, edumazet, horms, fw
Hi Paolo,
On Tue, Sep 29, 2026 at 10:19:55AM +0200, Paolo Abeni wrote:
> On 9/29/26 06:06, Julian Anastasov wrote:
> > > > @@ -744,7 +749,7 @@ void ip_vs_sync_conn(struct netns_ipvs *ipvs, struct ip_vs_conn *cp, int pkts)
> > > > s->v4.vaddr = cp->vaddr.ip;
> > > > s->v4.daddr = cp->daddr.ip;
> > > > }
> > > > - if (cp->flags & IP_VS_CONN_F_SEQ_MASK) {
> > > > + if (seq_mask) {
> > > > *(p++) = IPVS_OPT_SEQ_DATA;
> > > > *(p++) = sizeof(struct ip_vs_sync_conn_options);
> > > > hton_seq(&cp->in_seq, (struct ip_vs_seq *)p);
> > >
> > > [Severity: Medium]
> > > This is a pre-existing issue, not one introduced by this patch. The flags
> > > are now a consistent snapshot, but the sequence data is still read
> > > without cp->lock. Nothing orders that read against READ_ONCE(cp->flags).
> > > Can the master send stale or half-updated ip_vs_seq values here?
> > >
> > > ip_vs_sync_conn() here only holds ipvs->sync_buff_lock. The same applies
> > > to the memcpy() of cp->sync_conn_opt in ip_vs_sync_conn_v0().
> > >
> > > On the backup side, ip_vs_proc_conn() sets the SEQ bits in cp->flags under
> > > cp->lock and drops the lock. Only after that does it copy the sequence
> > > data, with no lock held:
> > >
> > > net/netfilter/ipvs/ip_vs_sync.c:ip_vs_proc_conn() {
> > > ...
> > > cp->flags = flags;
> > > spin_unlock_bh(&cp->lock);
> > > ...
> > > if (opt) {
> > > cp->in_seq = opt->in_seq;
> > > cp->out_seq = opt->out_seq;
> > > }
> > > ...
> > > }
> > >
> > > On a node running both the master and backup daemons, ip_vs_sync_conn()
> > > could see seq_mask set and then send old or torn init_seq, delta and
> > > previous_delta values. A peer with an app bound to that connection would
> > > then apply the wrong TCP seq/ack adjustment after failover.
> >
> > Agreed, this can be improved to take a snapshot of
> > flags and seqs together under lock. I'll request to drop
> > this version, it is not urgent to apply it.
>
> It looks like a respin of the PR is needed, I'll drop the revision from PW.
Just wrote to Jakub with a summary on the LLM report.
I would take this PR as is if it is still possible.
If you feel strong about to need to respin this PR, that's also fine
with me, just let confirm where to go.
Thanks.
^ permalink raw reply [flat|nested] 27+ messages in thread* Re: [PATCH net 02/11] ipvs: fix buffer overflow when sending sync messages
2026-09-29 9:43 ` Pablo Neira Ayuso
@ 2026-09-29 9:55 ` Paolo Abeni
2026-09-29 10:27 ` Pablo Neira Ayuso
0 siblings, 1 reply; 27+ messages in thread
From: Paolo Abeni @ 2026-09-29 9:55 UTC (permalink / raw)
To: Pablo Neira Ayuso
Cc: Julian Anastasov, netdev-bot+sashiko, netfilter-devel, davem,
netdev, kuba, edumazet, horms, fw
On 9/29/26 11:43, Pablo Neira Ayuso wrote:
> Hi Paolo,
>
> On Tue, Sep 29, 2026 at 10:19:55AM +0200, Paolo Abeni wrote:
>> On 9/29/26 06:06, Julian Anastasov wrote:
>>>>> @@ -744,7 +749,7 @@ void ip_vs_sync_conn(struct netns_ipvs *ipvs, struct ip_vs_conn *cp, int pkts)
>>>>> s->v4.vaddr = cp->vaddr.ip;
>>>>> s->v4.daddr = cp->daddr.ip;
>>>>> }
>>>>> - if (cp->flags & IP_VS_CONN_F_SEQ_MASK) {
>>>>> + if (seq_mask) {
>>>>> *(p++) = IPVS_OPT_SEQ_DATA;
>>>>> *(p++) = sizeof(struct ip_vs_sync_conn_options);
>>>>> hton_seq(&cp->in_seq, (struct ip_vs_seq *)p);
>>>>
>>>> [Severity: Medium]
>>>> This is a pre-existing issue, not one introduced by this patch. The flags
>>>> are now a consistent snapshot, but the sequence data is still read
>>>> without cp->lock. Nothing orders that read against READ_ONCE(cp->flags).
>>>> Can the master send stale or half-updated ip_vs_seq values here?
>>>>
>>>> ip_vs_sync_conn() here only holds ipvs->sync_buff_lock. The same applies
>>>> to the memcpy() of cp->sync_conn_opt in ip_vs_sync_conn_v0().
>>>>
>>>> On the backup side, ip_vs_proc_conn() sets the SEQ bits in cp->flags under
>>>> cp->lock and drops the lock. Only after that does it copy the sequence
>>>> data, with no lock held:
>>>>
>>>> net/netfilter/ipvs/ip_vs_sync.c:ip_vs_proc_conn() {
>>>> ...
>>>> cp->flags = flags;
>>>> spin_unlock_bh(&cp->lock);
>>>> ...
>>>> if (opt) {
>>>> cp->in_seq = opt->in_seq;
>>>> cp->out_seq = opt->out_seq;
>>>> }
>>>> ...
>>>> }
>>>>
>>>> On a node running both the master and backup daemons, ip_vs_sync_conn()
>>>> could see seq_mask set and then send old or torn init_seq, delta and
>>>> previous_delta values. A peer with an app bound to that connection would
>>>> then apply the wrong TCP seq/ack adjustment after failover.
>>>
>>> Agreed, this can be improved to take a snapshot of
>>> flags and seqs together under lock. I'll request to drop
>>> this version, it is not urgent to apply it.
>>
>> It looks like a respin of the PR is needed, I'll drop the revision from PW.
>
> Just wrote to Jakub with a summary on the LLM report.
>
> I would take this PR as is if it is still possible.
>
> If you feel strong about to need to respin this PR, that's also fine
> with me, just let confirm where to go.
Uhm... I must admit that given the pressure we received from Linus on
shrinking/prevent from increasing the net PR size I think we are better
off dropping patch 2/11 entirely.
I'm sorry for the extra work, but the messaging from Linus was pretty
strong:
https://lore.kernel.org/netdev/CAHk-=wiSnTE9vBZ=5_v+3EEkdazCCbBM5YABRzRHUAeRdyd4Xw@mail.gmail.com/
Thanks,
Paolo
^ permalink raw reply [flat|nested] 27+ messages in thread* Re: [PATCH net 02/11] ipvs: fix buffer overflow when sending sync messages
2026-09-29 9:55 ` Paolo Abeni
@ 2026-09-29 10:27 ` Pablo Neira Ayuso
0 siblings, 0 replies; 27+ messages in thread
From: Pablo Neira Ayuso @ 2026-09-29 10:27 UTC (permalink / raw)
To: Paolo Abeni
Cc: Julian Anastasov, netdev-bot+sashiko, netfilter-devel, davem,
netdev, kuba, edumazet, horms, fw
On Tue, Sep 29, 2026 at 11:55:11AM +0200, Paolo Abeni wrote:
> On 9/29/26 11:43, Pablo Neira Ayuso wrote:
> > Hi Paolo,
> >
> > On Tue, Sep 29, 2026 at 10:19:55AM +0200, Paolo Abeni wrote:
> > > On 9/29/26 06:06, Julian Anastasov wrote:
> > > > > > @@ -744,7 +749,7 @@ void ip_vs_sync_conn(struct netns_ipvs *ipvs, struct ip_vs_conn *cp, int pkts)
> > > > > > s->v4.vaddr = cp->vaddr.ip;
> > > > > > s->v4.daddr = cp->daddr.ip;
> > > > > > }
> > > > > > - if (cp->flags & IP_VS_CONN_F_SEQ_MASK) {
> > > > > > + if (seq_mask) {
> > > > > > *(p++) = IPVS_OPT_SEQ_DATA;
> > > > > > *(p++) = sizeof(struct ip_vs_sync_conn_options);
> > > > > > hton_seq(&cp->in_seq, (struct ip_vs_seq *)p);
> > > > >
> > > > > [Severity: Medium]
> > > > > This is a pre-existing issue, not one introduced by this patch. The flags
> > > > > are now a consistent snapshot, but the sequence data is still read
> > > > > without cp->lock. Nothing orders that read against READ_ONCE(cp->flags).
> > > > > Can the master send stale or half-updated ip_vs_seq values here?
> > > > >
> > > > > ip_vs_sync_conn() here only holds ipvs->sync_buff_lock. The same applies
> > > > > to the memcpy() of cp->sync_conn_opt in ip_vs_sync_conn_v0().
> > > > >
> > > > > On the backup side, ip_vs_proc_conn() sets the SEQ bits in cp->flags under
> > > > > cp->lock and drops the lock. Only after that does it copy the sequence
> > > > > data, with no lock held:
> > > > >
> > > > > net/netfilter/ipvs/ip_vs_sync.c:ip_vs_proc_conn() {
> > > > > ...
> > > > > cp->flags = flags;
> > > > > spin_unlock_bh(&cp->lock);
> > > > > ...
> > > > > if (opt) {
> > > > > cp->in_seq = opt->in_seq;
> > > > > cp->out_seq = opt->out_seq;
> > > > > }
> > > > > ...
> > > > > }
> > > > >
> > > > > On a node running both the master and backup daemons, ip_vs_sync_conn()
> > > > > could see seq_mask set and then send old or torn init_seq, delta and
> > > > > previous_delta values. A peer with an app bound to that connection would
> > > > > then apply the wrong TCP seq/ack adjustment after failover.
> > > >
> > > > Agreed, this can be improved to take a snapshot of
> > > > flags and seqs together under lock. I'll request to drop
> > > > this version, it is not urgent to apply it.
> > >
> > > It looks like a respin of the PR is needed, I'll drop the revision from PW.
> >
> > Just wrote to Jakub with a summary on the LLM report.
> >
> > I would take this PR as is if it is still possible.
> >
> > If you feel strong about to need to respin this PR, that's also fine
> > with me, just let confirm where to go.
>
> Uhm... I must admit that given the pressure we received from Linus on
> shrinking/prevent from increasing the net PR size I think we are better
> off dropping patch 2/11 entirely.
>
> I'm sorry for the extra work, but the messaging from Linus was pretty
> strong:
>
> https://lore.kernel.org/netdev/CAHk-=wiSnTE9vBZ=5_v+3EEkdazCCbBM5YABRzRHUAeRdyd4Xw@mail.gmail.com/
Yes, I read that. I tried to move what is less relevant to nf-next (if
you look at my nf-next PR, it's contains not so relevant fixes too).
include/linux/netdevice.h | 3 ++
include/net/netfilter/nf_flow_table.h | 2 +-
net/mac80211/iface.c | 7 +++++
net/netfilter/ipset/ip_set_bitmap_gen.h | 2 +-
net/netfilter/ipvs/ip_vs_conn.c | 3 ++
net/netfilter/ipvs/ip_vs_lblc.c | 4 +++
net/netfilter/ipvs/ip_vs_lblcr.c | 3 ++
net/netfilter/ipvs/ip_vs_sync.c | 56 ++++++++++++++++++++++++++-------
net/netfilter/nf_flow_table_core.c | 7 ++++-
net/netfilter/nf_flow_table_offload.c | 14 +++------
net/netfilter/nf_flow_table_path.c | 3 ++
net/netfilter/nf_nat_bpf.c | 3 ++
net/netfilter/nf_nat_core.c | 5 +--
net/netfilter/nft_flow_offload.c | 7 ++++-
net/netfilter/nft_set_rbtree.c | 2 ++
net/sched/act_ct.c | 2 +-
16 files changed, 95 insertions(+), 28 deletions(-)
Yes, this patch is larger in the diffstat. I can just move it to
nf-next if you prefer.
I will respin and send v2 today without this.
Thanks.
^ permalink raw reply [flat|nested] 27+ messages in thread
* [PATCH net 03/11] netfilter: nft_flow_offload: drop flowtable reference on init error path
2026-09-27 22:08 [PATCH net 00/11] Netfilter/IPVS fixes for net Pablo Neira Ayuso
2026-09-27 22:08 ` [PATCH net 01/11] netfilter: ipset: do not update comments from kernel-side adds Pablo Neira Ayuso
2026-09-27 22:08 ` [PATCH net 02/11] ipvs: fix buffer overflow when sending sync messages Pablo Neira Ayuso
@ 2026-09-27 22:08 ` Pablo Neira Ayuso
2026-09-28 23:55 ` netdev-bot+sashiko
2026-09-27 22:08 ` [PATCH net 04/11] ipvs: fix missing counter decrement in lblc Pablo Neira Ayuso
` (8 subsequent siblings)
11 siblings, 1 reply; 27+ messages in thread
From: Pablo Neira Ayuso @ 2026-09-27 22:08 UTC (permalink / raw)
To: netfilter-devel; +Cc: davem, netdev, kuba, pabeni, edumazet, horms, fw, ja
From: Aohan Mei <henrymei@tencent.com>
nft_flow_offload_init() bumps the flowtable use count with
nft_use_inc() before calling nf_ct_netns_get(). When the latter
fails, the error is returned as-is and the reference is leaked.
The upper layers do not balance it either: nf_tables_newexpr()
clears expr->ops when the expression init callback fails, so the
nft_expr_more() iteration in nft_rule_expr_deactivate() and
nf_tables_rule_destroy() stops right before the failed expression
and its ->destroy callback, which would drop the reference, never
runs.
Each failed rule addition therefore leaks one flowtable reference
and the flowtable can no longer be removed: NFT_MSG_DELFLOWTABLE
keeps reporting -EBUSY even though no rule references it.
Save the nf_ct_netns_get() return value and undo the nft_use_inc()
when it fails, restoring the inc/dec pairing within
nft_flow_offload_init() itself.
Fixes: a3c90f7a2323 ("netfilter: nf_tables: flow offload expression")
Reported-by: TencentOS Corvus AI <corvus@tencent.com>
Cc: stable@vger.kernel.org
Assisted-by: CodeBuddy:Kimi-K3
Signed-off-by: Aohan Mei <henrymei@tencent.com>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
---
net/netfilter/nft_flow_offload.c | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/net/netfilter/nft_flow_offload.c b/net/netfilter/nft_flow_offload.c
index 32b4281038dd..d3c5651dd699 100644
--- a/net/netfilter/nft_flow_offload.c
+++ b/net/netfilter/nft_flow_offload.c
@@ -160,6 +160,7 @@ static int nft_flow_offload_init(const struct nft_ctx *ctx,
struct nft_flow_offload *priv = nft_expr_priv(expr);
u8 genmask = nft_genmask_next(ctx->net);
struct nft_flowtable *flowtable;
+ int err;
if (!tb[NFTA_FLOW_TABLE_NAME])
return -EINVAL;
@@ -174,7 +175,11 @@ static int nft_flow_offload_init(const struct nft_ctx *ctx,
priv->flowtable = flowtable;
- return nf_ct_netns_get(ctx->net, ctx->family);
+ err = nf_ct_netns_get(ctx->net, ctx->family);
+ if (err < 0)
+ nft_use_dec(&flowtable->use);
+
+ return err;
}
static void nft_flow_offload_deactivate(const struct nft_ctx *ctx,
--
2.47.3
^ permalink raw reply related [flat|nested] 27+ messages in thread* Re: [PATCH net 03/11] netfilter: nft_flow_offload: drop flowtable reference on init error path
2026-09-27 22:08 ` [PATCH net 03/11] netfilter: nft_flow_offload: drop flowtable reference on init error path Pablo Neira Ayuso
@ 2026-09-28 23:55 ` netdev-bot+sashiko
0 siblings, 0 replies; 27+ messages in thread
From: netdev-bot+sashiko @ 2026-09-28 23:55 UTC (permalink / raw)
To: pablo; +Cc: netfilter-devel, davem, netdev, kuba, pabeni, edumazet, horms, fw,
ja
Thank you for your contribution! Sashiko AI review found more severe
issue(s) in other patches of this series. This mail is only an FYI, in case
you want to address the nit picks below when you respin.
Low priority / nit picks for your consideration:
- [Low] One sentence in the commit message names the wrong callback.
--
netfilter: nft_flow_offload: drop flowtable reference on init error path
In nft_flow_offload_init(), nft_use_inc() raises the flowtable use count
before nf_ct_netns_get() is called. If nf_ct_netns_get() fails, that count
is never dropped. The patch saves the return value of nf_ct_netns_get() and
calls nft_use_dec() on failure, so the inc/dec pairing is restored inside
the init callback.
> The upper layers do not balance it either: nf_tables_newexpr()
> clears expr->ops when the expression init callback fails, so the
> nft_expr_more() iteration in nft_rule_expr_deactivate() and
> nf_tables_rule_destroy() stops right before the failed expression
> and its ->destroy callback, which would drop the reference, never
> runs.
[Severity: Low]
This isn't a bug, but does this paragraph name the wrong callback? In
net/netfilter/nft_flow_offload.c, the ->destroy callback only drops the
conntrack netns reference:
static void nft_flow_offload_destroy(const struct nft_ctx *ctx,
const struct nft_expr *expr)
{
nf_ct_netns_put(ctx->net, ctx->family);
}
The flowtable use count is dropped through ->deactivate instead.
nft_flow_offload_deactivate() calls nf_tables_deactivate_flowtable(), and
that function does the decrement:
net/netfilter/nf_tables_api.c:nf_tables_deactivate_flowtable() {
switch (phase) {
case NFT_TRANS_PREPARE_ERROR:
case NFT_TRANS_PREPARE:
case NFT_TRANS_ABORT:
case NFT_TRANS_RELEASE:
nft_use_dec(&flowtable->use);
...
}
On the nf_tables_newrule() error path, the callback that gets skipped for
the failed expression is this one:
nft_rule_expr_deactivate(&ctx, rule, NFT_TRANS_PREPARE_ERROR);
It is skipped because nf_tables_newexpr() cleared expr->ops, which ends the
nft_expr_more() walk.
The code change looks correct. Could the commit message say that the
skipped ->deactivate callback is what would drop the reference, rather than
->destroy? That would help anyone checking the fix against
nft_flow_offload_destroy(), for example during a stable backport.
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260927220816.268206-2-pablo%40netfilter.org
^ permalink raw reply [flat|nested] 27+ messages in thread
* [PATCH net 04/11] ipvs: fix missing counter decrement in lblc
2026-09-27 22:08 [PATCH net 00/11] Netfilter/IPVS fixes for net Pablo Neira Ayuso
` (2 preceding siblings ...)
2026-09-27 22:08 ` [PATCH net 03/11] netfilter: nft_flow_offload: drop flowtable reference on init error path Pablo Neira Ayuso
@ 2026-09-27 22:08 ` Pablo Neira Ayuso
2026-09-27 22:08 ` [PATCH net 05/11] ipvs: bound LBLCR and LBLC cache growth Pablo Neira Ayuso
` (7 subsequent siblings)
11 siblings, 0 replies; 27+ messages in thread
From: Pablo Neira Ayuso @ 2026-09-27 22:08 UTC (permalink / raw)
To: netfilter-devel; +Cc: davem, netdev, kuba, pabeni, edumazet, horms, fw, ja
From: Julian Anastasov <ja@ssi.bg>
LBLC may delete cache entries for destinations that are
removed or overloaded and replace them with available ones.
But ip_vs_lblc_new() forgets to decrement the tbl->entries
counter after calling ip_vs_lblc_del(). This can lead to
increased shrinking of the cache with every new garbage
collection.
Fixes: 2f3d771a35fe ("ipvs: do not use dest after ip_vs_dest_put in LBLC")
Link: https://sashiko.dev/#/patchset/0bdd5abe9968ded7ca2b9cb6844ba83d94cc8d53.1787318053.git.zhilinz%40nebusec.ai
Signed-off-by: Julian Anastasov <ja@ssi.bg>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
---
net/netfilter/ipvs/ip_vs_lblc.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/net/netfilter/ipvs/ip_vs_lblc.c b/net/netfilter/ipvs/ip_vs_lblc.c
index 693bcc82ccb7..a2b574904f13 100644
--- a/net/netfilter/ipvs/ip_vs_lblc.c
+++ b/net/netfilter/ipvs/ip_vs_lblc.c
@@ -203,6 +203,7 @@ ip_vs_lblc_new(struct ip_vs_lblc_table *tbl, const union nf_inet_addr *daddr,
if (en->dest == dest)
return en;
ip_vs_lblc_del(en);
+ atomic_dec(&tbl->entries);
}
en = kmalloc_obj(*en, GFP_ATOMIC);
if (!en)
--
2.47.3
^ permalink raw reply related [flat|nested] 27+ messages in thread* [PATCH net 05/11] ipvs: bound LBLCR and LBLC cache growth
2026-09-27 22:08 [PATCH net 00/11] Netfilter/IPVS fixes for net Pablo Neira Ayuso
` (3 preceding siblings ...)
2026-09-27 22:08 ` [PATCH net 04/11] ipvs: fix missing counter decrement in lblc Pablo Neira Ayuso
@ 2026-09-27 22:08 ` Pablo Neira Ayuso
2026-09-27 22:08 ` [PATCH net 06/11] ipvs: do not create invisible templates Pablo Neira Ayuso
` (6 subsequent siblings)
11 siblings, 0 replies; 27+ messages in thread
From: Pablo Neira Ayuso @ 2026-09-27 22:08 UTC (permalink / raw)
To: netfilter-devel; +Cc: davem, netdev, kuba, pabeni, edumazet, horms, fw, ja
From: Zhiling Zou <zhilinz@nebusec.ai>
ip_vs_lblcr_new() and ip_vs_lblc_new() create cache entries for
every previously unseen destination address. The table max_size only
tells the periodic collector to reclaim entries after the cache has
already exceeded the limit. It does not reclaim entries that the
attacker continues to use.
Reject new cache entries once either table reaches max_size * 3 / 2.
The extra headroom lets the periodic collector catch up while the
existing scheduler fallback continues to use the selected destination
when cache creation fails. New traffic therefore stays serviceable
without growing the tables further.
Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Cc: stable@vger.kernel.org
Reported-by: Vega <vega@nebusec.ai>
Suggested-by: Julian Anastasov <ja@ssi.bg>
Signed-off-by: Zhiling Zou <zhilinz@nebusec.ai>
Acked-by: Julian Anastasov <ja@ssi.bg>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
---
net/netfilter/ipvs/ip_vs_lblc.c | 3 +++
net/netfilter/ipvs/ip_vs_lblcr.c | 3 +++
2 files changed, 6 insertions(+)
diff --git a/net/netfilter/ipvs/ip_vs_lblc.c b/net/netfilter/ipvs/ip_vs_lblc.c
index a2b574904f13..55af77a56929 100644
--- a/net/netfilter/ipvs/ip_vs_lblc.c
+++ b/net/netfilter/ipvs/ip_vs_lblc.c
@@ -205,6 +205,9 @@ ip_vs_lblc_new(struct ip_vs_lblc_table *tbl, const union nf_inet_addr *daddr,
ip_vs_lblc_del(en);
atomic_dec(&tbl->entries);
}
+ if (atomic_read(&tbl->entries) >= tbl->max_size * 3 / 2)
+ return NULL;
+
en = kmalloc_obj(*en, GFP_ATOMIC);
if (!en)
return NULL;
diff --git a/net/netfilter/ipvs/ip_vs_lblcr.c b/net/netfilter/ipvs/ip_vs_lblcr.c
index f53f05ceea36..858393b1d2d1 100644
--- a/net/netfilter/ipvs/ip_vs_lblcr.c
+++ b/net/netfilter/ipvs/ip_vs_lblcr.c
@@ -363,6 +363,9 @@ ip_vs_lblcr_new(struct ip_vs_lblcr_table *tbl, const union nf_inet_addr *daddr,
en = ip_vs_lblcr_get(af, tbl, daddr);
if (!en) {
+ if (atomic_read(&tbl->entries) >= tbl->max_size * 3 / 2)
+ return NULL;
+
en = kmalloc_obj(*en, GFP_ATOMIC);
if (!en)
return NULL;
--
2.47.3
^ permalink raw reply related [flat|nested] 27+ messages in thread* [PATCH net 06/11] ipvs: do not create invisible templates
2026-09-27 22:08 [PATCH net 00/11] Netfilter/IPVS fixes for net Pablo Neira Ayuso
` (4 preceding siblings ...)
2026-09-27 22:08 ` [PATCH net 05/11] ipvs: bound LBLCR and LBLC cache growth Pablo Neira Ayuso
@ 2026-09-27 22:08 ` Pablo Neira Ayuso
2026-09-27 22:08 ` [PATCH net 07/11] ipvs: filter some flags received in the backup server Pablo Neira Ayuso
` (5 subsequent siblings)
11 siblings, 0 replies; 27+ messages in thread
From: Pablo Neira Ayuso @ 2026-09-27 22:08 UTC (permalink / raw)
To: netfilter-devel; +Cc: davem, netdev, kuba, pabeni, edumazet, horms, fw, ja
From: Julian Anastasov <ja@ssi.bg>
The IP_VS_CONN_F_ONE_PACKET flag was implemented for normal
connections. When conn template inherits this flag from
dest->conn_flags it will not be hashed. As result, we will
create new template for every new normal connection.
Fix it to allow one template to be used by many normal
connections.
Fixes: 26ec037f9841 ("IPVS: one-packet scheduling")
Link: https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260916231652.127456-1-pablo%40netfilter.org
Signed-off-by: Julian Anastasov <ja@ssi.bg>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
---
net/netfilter/ipvs/ip_vs_conn.c | 3 +++
1 file changed, 3 insertions(+)
diff --git a/net/netfilter/ipvs/ip_vs_conn.c b/net/netfilter/ipvs/ip_vs_conn.c
index 6fa3e1dc534c..cb009208826f 100644
--- a/net/netfilter/ipvs/ip_vs_conn.c
+++ b/net/netfilter/ipvs/ip_vs_conn.c
@@ -1102,6 +1102,9 @@ ip_vs_bind_dest(struct ip_vs_conn *cp, struct ip_vs_dest *dest)
if (cp->protocol != IPPROTO_UDP)
conn_flags &= ~IP_VS_CONN_F_ONE_PACKET;
flags = cp->flags;
+ /* Only visible templates can control multiple connections */
+ if (flags & IP_VS_CONN_F_TEMPLATE)
+ conn_flags &= ~IP_VS_CONN_F_ONE_PACKET;
/* Bind with the destination and its corresponding transmitter */
if (flags & IP_VS_CONN_F_SYNC) {
/* Synced conns are hashed, so they can not get this flag */
--
2.47.3
^ permalink raw reply related [flat|nested] 27+ messages in thread* [PATCH net 07/11] ipvs: filter some flags received in the backup server
2026-09-27 22:08 [PATCH net 00/11] Netfilter/IPVS fixes for net Pablo Neira Ayuso
` (5 preceding siblings ...)
2026-09-27 22:08 ` [PATCH net 06/11] ipvs: do not create invisible templates Pablo Neira Ayuso
@ 2026-09-27 22:08 ` Pablo Neira Ayuso
2026-09-28 23:55 ` netdev-bot+sashiko
2026-09-27 22:08 ` [PATCH net 08/11] netfilter: nft_set_rbtree: skip transaction elements during GC Pablo Neira Ayuso
` (4 subsequent siblings)
11 siblings, 1 reply; 27+ messages in thread
From: Pablo Neira Ayuso @ 2026-09-27 22:08 UTC (permalink / raw)
To: netfilter-devel; +Cc: davem, netdev, kuba, pabeni, edumazet, horms, fw, ja
From: Julian Anastasov <ja@ssi.bg>
While the IPVS SYNC protocol is not secure by design
we can still protect the backup server from messages that
can wreak havoc.
This commit addresses problems from received connection flags
or their combinations. We now drop messages as follows:
1. the NO_CPORT+TEMPLATE combination allows lookups for normal
connections to hit template which can break in many ways.
While the master does not sync connections with NO_CPORT flag,
i.e. before they are established, we still accept NO_CPORT
without TEMPLATE.
2. ONE_PACKET: it is not sent by master, so we do not
expect it in backup. Before now it was ignored by
IP_VS_CONN_F_BACKUP_MASK for protocol v1 while protocol
v0 created connections that are not hashed and dropped
immediately. Better to apply the IP_VS_CONN_F_BACKUP_MASK
also to the flags from v0 messages for consistency with v1.
Fixes: 87375ab47cd0 ("[IPVS]: ip_vs_ftp breaks connections using persistence")
Signed-off-by: Julian Anastasov <ja@ssi.bg>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
---
net/netfilter/ipvs/ip_vs_sync.c | 33 ++++++++++++++++++++++++++++++---
1 file changed, 30 insertions(+), 3 deletions(-)
diff --git a/net/netfilter/ipvs/ip_vs_sync.c b/net/netfilter/ipvs/ip_vs_sync.c
index dfa8487ec0c2..1a30817fbbaf 100644
--- a/net/netfilter/ipvs/ip_vs_sync.c
+++ b/net/netfilter/ipvs/ip_vs_sync.c
@@ -954,6 +954,21 @@ static void ip_vs_proc_conn(struct netns_ipvs *ipvs, struct ip_vs_conn_param *pa
ip_vs_conn_put(cp);
}
+/* Check for incompatible flags */
+static bool ip_vs_sync_validate_flags(u32 flags)
+{
+ /* We do not expect NO_CPORT, especially to allow lookups
+ * to hit templates
+ */
+ if (flags & IP_VS_CONN_F_NO_CPORT) {
+ if (flags & IP_VS_CONN_F_TEMPLATE)
+ return false;
+ }
+ if (flags & IP_VS_CONN_F_ONE_PACKET)
+ return false;
+ return true;
+}
+
/*
* Process received multicast message for Version 0
*/
@@ -977,8 +992,7 @@ static void ip_vs_process_message_v0(struct netns_ipvs *ipvs, const char *buffer
return;
}
s = (struct ip_vs_sync_conn_v0 *) p;
- flags = ntohs(s->flags) | IP_VS_CONN_F_SYNC;
- flags &= ~IP_VS_CONN_F_HASHED;
+ flags = ntohs(s->flags);
if (flags & IP_VS_CONN_F_SEQ_MASK) {
opt = (struct ip_vs_sync_conn_options *)&s[1];
p += FULL_CONN_SIZE;
@@ -991,6 +1005,13 @@ static void ip_vs_process_message_v0(struct netns_ipvs *ipvs, const char *buffer
p += SIMPLE_CONN_SIZE;
}
+ if (!ip_vs_sync_validate_flags(flags)) {
+ IP_VS_DBG(2, "BACKUP v0, Invalid flags 0x%X\n", flags);
+ continue;
+ }
+ flags &= IP_VS_CONN_F_BACKUP_MASK;
+ flags |= IP_VS_CONN_F_SYNC;
+
state = ntohs(s->state);
if (!(flags & IP_VS_CONN_F_TEMPLATE)) {
pp = ip_vs_proto_get(s->protocol);
@@ -1146,7 +1167,13 @@ static inline int ip_vs_proc_sync_conn(struct netns_ipvs *ipvs, __u8 *p, __u8 *m
}
/* Get flags and Mask off unsupported */
- flags = ntohl(s->v4.flags) & IP_VS_CONN_F_BACKUP_MASK;
+ flags = ntohl(s->v4.flags);
+ if (!ip_vs_sync_validate_flags(flags)) {
+ IP_VS_DBG(3, "BACKUP, Invalid flags 0x%X\n", flags);
+ retc = 25;
+ goto out;
+ }
+ flags &= IP_VS_CONN_F_BACKUP_MASK;
flags |= IP_VS_CONN_F_SYNC;
state = ntohs(s->v4.state);
--
2.47.3
^ permalink raw reply related [flat|nested] 27+ messages in thread* Re: [PATCH net 07/11] ipvs: filter some flags received in the backup server
2026-09-27 22:08 ` [PATCH net 07/11] ipvs: filter some flags received in the backup server Pablo Neira Ayuso
@ 2026-09-28 23:55 ` netdev-bot+sashiko
2026-09-29 4:17 ` Julian Anastasov
0 siblings, 1 reply; 27+ messages in thread
From: netdev-bot+sashiko @ 2026-09-28 23:55 UTC (permalink / raw)
To: pablo; +Cc: netfilter-devel, davem, netdev, kuba, pabeni, edumazet, horms, fw,
ja
Thank you for your contribution! Sashiko AI review found 1 potential
issue(s) to consider.
Critical: 0 · High: 1 · Medium: 0 · Low: 0 (1)
If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).
- [High] The patch does not stop forged sync messages from creating
templates that normal-connection lookups can hit.
Pre-existing issues:
- [Low] This problem wasn't introduced by this patch, but
ip_vs_sync_validate_flags() still accepts NO_CPORT on non-template…
--
ipvs: filter some flags received in the backup server
This commit adds ip_vs_sync_validate_flags() so the backup drops sync
entries that have both NO_CPORT and TEMPLATE set, or that have ONE_PACKET
set. It also applies IP_VS_CONN_F_BACKUP_MASK to v0 message flags, to
match the existing v1 handling.
> diff --git a/net/netfilter/ipvs/ip_vs_sync.c b/net/netfilter/ipvs/ip_vs_sync.c
> index dfa8487ec0c20..1a30817fbbafe 100644
> --- a/net/netfilter/ipvs/ip_vs_sync.c
> +++ b/net/netfilter/ipvs/ip_vs_sync.c
> @@ -954,6 +954,21 @@ static void ip_vs_proc_conn(struct netns_ipvs *ipvs, struct ip_vs_conn_param *pa
> ip_vs_conn_put(cp);
> }
>
> +/* Check for incompatible flags */
> +static bool ip_vs_sync_validate_flags(u32 flags)
> +{
> + /* We do not expect NO_CPORT, especially to allow lookups
> + * to hit templates
> + */
> + if (flags & IP_VS_CONN_F_NO_CPORT) {
> + if (flags & IP_VS_CONN_F_TEMPLATE)
> + return false;
> + }
[Severity: High]
Does this fully stop sync messages from creating templates that
normal-connection lookups can hit?
Take a v0 or v1 entry with TEMPLATE set, NO_CPORT clear and a non-zero
cport. It passes this check and survives IP_VS_CONN_F_BACKUP_MASK. Then
ip_vs_proc_conn()->ip_vs_conn_new() creates it with the cport from the
message: ip_vs_conn_fill_param_sync() for v1, s->cport for v0.
With no PE data, the template is hashed on the same (proto, caddr, cport,
vaddr, vport) key a normal connection uses. __ip_vs_conn_in_get() does
not check IP_VS_CONN_F_TEMPLATE:
net/netfilter/ipvs/ip_vs_conn.c:__ip_vs_conn_in_get() {
...
if (p->cport == cp->cport &&
p->vport == cp->vport && cp->af == p->af &&
...
(!p->cport ^
(!(cp->flags & IP_VS_CONN_F_NO_CPORT))) &&
p->protocol == cp->protocol) {
...
}
When p->cport == cp->cport != 0 and NO_CPORT is clear, the XOR term is 1,
so the lookup returns the template.
A later non-template sync for that tuple then gets the template back from
ip_vs_conn_in_get() in ip_vs_proc_conn(). It either expires the template,
or it toggles dest->activeconns on the template's dest and overwrites
cp->flags, state, timeout and seq:
net/netfilter/ipvs/ip_vs_sync.c:ip_vs_proc_conn() {
...
if ((cp->flags ^ flags) & IP_VS_CONN_F_INACTIVE &&
!(flags & IP_VS_CONN_F_TEMPLATE) && dest) {
if (flags & IP_VS_CONN_F_INACTIVE)
atomic_dec(&dest->activeconns);
else
atomic_inc(&dest->activeconns);
}
...
}
For templates, ip_vs_unbind_dest() only decrements persistconns, so the
activeconns change is never undone. After failover, real client packets
for that tuple would also be handled by the template in the ip_vs_in()
path.
The master always builds templates with cport 0. Both
ip_vs_conn_fill_param_persist() callers in ip_vs_core.c pass a literal 0.
Should the backup also reject TEMPLATE entries with a non-zero cport when
there is no PE data?
[Severity: Low]
This is a pre-existing issue, not one introduced by this patch. NO_CPORT
without TEMPLATE is still accepted here, and nothing checks that the
synced cport is 0. Is that intended?
Other code assumes NO_CPORT means cport == 0. ip_vs_ftp creates NO_CPORT
connections with cport 0, and ip_vs_conn_fill_cport() uses !cp->cport to
mean the cport has not been filled in yet:
if (cp->flags & IP_VS_CONN_F_NO_CPORT && (!cp->cport || by_me)) {
ip_vs_conn_new() creates a NO_CPORT entry with cport X != 0 and
increments ipvs->no_cport_conns[af]. ip_vs_conn_in_get() can never find
that entry. The exact lookup fails the XOR term in __ip_vs_conn_in_get(),
and the cport_zero_p retry fails the cport comparison.
So each refresh in ip_vs_proc_conn() would create another duplicate entry,
and each one lasts until its timeout. no_cport_conns also stays non-zero,
which forces the second lookup on every in-direction miss. The entry can
still be matched in the reply direction, because ip_vs_conn_out_get()
does not check NO_CPORT.
Would it make sense to also require cport == 0 when NO_CPORT is accepted
on a non-template entry?
> + if (flags & IP_VS_CONN_F_ONE_PACKET)
> + return false;
> + return true;
> +}
> +
[ ... ]
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260927220816.268206-2-pablo%40netfilter.org
^ permalink raw reply [flat|nested] 27+ messages in thread* Re: [PATCH net 07/11] ipvs: filter some flags received in the backup server
2026-09-28 23:55 ` netdev-bot+sashiko
@ 2026-09-29 4:17 ` Julian Anastasov
0 siblings, 0 replies; 27+ messages in thread
From: Julian Anastasov @ 2026-09-29 4:17 UTC (permalink / raw)
To: netdev-bot+sashiko
Cc: pablo, netfilter-devel, davem, netdev, kuba, pabeni, edumazet,
horms, fw
[-- Attachment #1: Type: text/plain, Size: 5674 bytes --]
Hello,
On Mon, 28 Sep 2026, netdev-bot+sashiko@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 1 potential
> issue(s) to consider.
>
> Critical: 0 · High: 1 · Medium: 0 · Low: 0 (1)
This patch is fine, see below. It is another patch
by another author that adds the missing checks.
> If you believe none of the issues are legitimate, please reply to each
> issue of Medium severity or higher and explain why the AI is wrong.
> If any of them are legitimate and you plan to respin, please let patchwork
> know by including "pw-bot: cr" as a separate line at the end of your reply
> (one such reply per series is enough).
>
> - [High] The patch does not stop forged sync messages from creating
> templates that normal-connection lookups can hit.
>
> Pre-existing issues:
> - [Low] This problem wasn't introduced by this patch, but
> ip_vs_sync_validate_flags() still accepts NO_CPORT on non-template…
>
> --
>
> ipvs: filter some flags received in the backup server
>
> This commit adds ip_vs_sync_validate_flags() so the backup drops sync
> entries that have both NO_CPORT and TEMPLATE set, or that have ONE_PACKET
> set. It also applies IP_VS_CONN_F_BACKUP_MASK to v0 message flags, to
> match the existing v1 handling.
>
> > diff --git a/net/netfilter/ipvs/ip_vs_sync.c b/net/netfilter/ipvs/ip_vs_sync.c
> > index dfa8487ec0c20..1a30817fbbafe 100644
> > --- a/net/netfilter/ipvs/ip_vs_sync.c
> > +++ b/net/netfilter/ipvs/ip_vs_sync.c
> > @@ -954,6 +954,21 @@ static void ip_vs_proc_conn(struct netns_ipvs *ipvs, struct ip_vs_conn_param *pa
> > ip_vs_conn_put(cp);
> > }
> >
> > +/* Check for incompatible flags */
> > +static bool ip_vs_sync_validate_flags(u32 flags)
> > +{
> > + /* We do not expect NO_CPORT, especially to allow lookups
> > + * to hit templates
> > + */
> > + if (flags & IP_VS_CONN_F_NO_CPORT) {
> > + if (flags & IP_VS_CONN_F_TEMPLATE)
> > + return false;
> > + }
>
> [Severity: High]
> Does this fully stop sync messages from creating templates that
> normal-connection lookups can hit?
>
> Take a v0 or v1 entry with TEMPLATE set, NO_CPORT clear and a non-zero
> cport. It passes this check and survives IP_VS_CONN_F_BACKUP_MASK. Then
> ip_vs_proc_conn()->ip_vs_conn_new() creates it with the cport from the
> message: ip_vs_conn_fill_param_sync() for v1, s->cport for v0.
>
> With no PE data, the template is hashed on the same (proto, caddr, cport,
> vaddr, vport) key a normal connection uses. __ip_vs_conn_in_get() does
> not check IP_VS_CONN_F_TEMPLATE:
>
> net/netfilter/ipvs/ip_vs_conn.c:__ip_vs_conn_in_get() {
> ...
> if (p->cport == cp->cport &&
> p->vport == cp->vport && cp->af == p->af &&
> ...
> (!p->cport ^
> (!(cp->flags & IP_VS_CONN_F_NO_CPORT))) &&
> p->protocol == cp->protocol) {
> ...
> }
>
> When p->cport == cp->cport != 0 and NO_CPORT is clear, the XOR term is 1,
> so the lookup returns the template.
>
> A later non-template sync for that tuple then gets the template back from
> ip_vs_conn_in_get() in ip_vs_proc_conn(). It either expires the template,
> or it toggles dest->activeconns on the template's dest and overwrites
> cp->flags, state, timeout and seq:
>
> net/netfilter/ipvs/ip_vs_sync.c:ip_vs_proc_conn() {
> ...
> if ((cp->flags ^ flags) & IP_VS_CONN_F_INACTIVE &&
> !(flags & IP_VS_CONN_F_TEMPLATE) && dest) {
> if (flags & IP_VS_CONN_F_INACTIVE)
> atomic_dec(&dest->activeconns);
> else
> atomic_inc(&dest->activeconns);
> }
> ...
> }
>
> For templates, ip_vs_unbind_dest() only decrements persistconns, so the
> activeconns change is never undone. After failover, real client packets
> for that tuple would also be handled by the template in the ip_vs_in()
> path.
>
> The master always builds templates with cport 0. Both
> ip_vs_conn_fill_param_persist() callers in ip_vs_core.c pass a literal 0.
>
> Should the backup also reject TEMPLATE entries with a non-zero cport when
> there is no PE data?
Above is solved with a pending patch
"ipvs: validate cport in received sync records".
>
> [Severity: Low]
> This is a pre-existing issue, not one introduced by this patch. NO_CPORT
> without TEMPLATE is still accepted here, and nothing checks that the
> synced cport is 0. Is that intended?
>
> Other code assumes NO_CPORT means cport == 0. ip_vs_ftp creates NO_CPORT
> connections with cport 0, and ip_vs_conn_fill_cport() uses !cp->cport to
> mean the cport has not been filled in yet:
>
> if (cp->flags & IP_VS_CONN_F_NO_CPORT && (!cp->cport || by_me)) {
>
> ip_vs_conn_new() creates a NO_CPORT entry with cport X != 0 and
> increments ipvs->no_cport_conns[af]. ip_vs_conn_in_get() can never find
> that entry. The exact lookup fails the XOR term in __ip_vs_conn_in_get(),
> and the cport_zero_p retry fails the cport comparison.
>
> So each refresh in ip_vs_proc_conn() would create another duplicate entry,
> and each one lasts until its timeout. no_cport_conns also stays non-zero,
> which forces the second lookup on every in-direction miss. The entry can
> still be matched in the reply direction, because ip_vs_conn_out_get()
> does not check NO_CPORT.
>
> Would it make sense to also require cport == 0 when NO_CPORT is accepted
> on a non-template entry?
Above is solved with a pending patch
"ipvs: validate cport in received sync records".
>
> > + if (flags & IP_VS_CONN_F_ONE_PACKET)
> > + return false;
> > + return true;
> > +}
> > +
>
> [ ... ]
>
> --
> Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260927220816.268206-2-pablo%40netfilter.org
>
Regards
--
Julian Anastasov <ja@ssi.bg>
^ permalink raw reply [flat|nested] 27+ messages in thread
* [PATCH net 08/11] netfilter: nft_set_rbtree: skip transaction elements during GC
2026-09-27 22:08 [PATCH net 00/11] Netfilter/IPVS fixes for net Pablo Neira Ayuso
` (6 preceding siblings ...)
2026-09-27 22:08 ` [PATCH net 07/11] ipvs: filter some flags received in the backup server Pablo Neira Ayuso
@ 2026-09-27 22:08 ` Pablo Neira Ayuso
2026-09-28 23:55 ` netdev-bot+sashiko
2026-09-27 22:08 ` [PATCH net 09/11] netfilter: bpf: reject invalid NAT manipulation types Pablo Neira Ayuso
` (3 subsequent siblings)
11 siblings, 1 reply; 27+ messages in thread
From: Pablo Neira Ayuso @ 2026-09-27 22:08 UTC (permalink / raw)
To: netfilter-devel; +Cc: davem, netdev, kuba, pabeni, edumazet, horms, fw, ja
From: Weiming Shi <bestswngs@gmail.com>
Since nft_set_commit_update() runs set commit callbacks before processing
NEWSETELEM transactions, nft_rbtree_gc_scan() can observe elements added by
the transaction being committed.
The scan records an interval end in rbe_end without checking the element's
transaction state. A later, unrelated expired start then moves both
elements to the expired list. The synchronous GC queue can free the new end
element before the transaction subsequently activates it, causing a
use-after-free.
Only consider elements that are fully active in both generations. This
keeps transaction-state elements out of the GC scan and preserves interval
pairing across skipped elements.
KASAN reports:
BUG: KASAN: slab-use-after-free in nft_setelem_activate
nft_setelem_activate net/netfilter/nf_tables_api.c:7047
nf_tables_commit net/netfilter/nf_tables_api.c:11137
Allocated by task 130:
nft_set_elem_init net/netfilter/nf_tables_api.c:6794
nft_add_set_elem net/netfilter/nf_tables_api.c:7523
Freed by task 130:
nft_trans_gc_trans_free net/netfilter/nf_tables_api.c:10506
rcu_core kernel/rcu/tree.c:2919
Fixes: 1e3b9e1c77fe ("netfilter: nf_tables: call set ops .commit when building new ruleset blob")
Reported-by: <co+ee5e50ef2670e5f4@bugs.sh>
Assisted-by: LLM
Signed-off-by: Weiming Shi <bestswngs@gmail.com>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
---
net/netfilter/nft_set_rbtree.c | 2 ++
1 file changed, 2 insertions(+)
diff --git a/net/netfilter/nft_set_rbtree.c b/net/netfilter/nft_set_rbtree.c
index 9894832281c4..12431b55752f 100644
--- a/net/netfilter/nft_set_rbtree.c
+++ b/net/netfilter/nft_set_rbtree.c
@@ -900,6 +900,8 @@ static void nft_rbtree_gc_scan(struct nft_set *set)
next = rb_next(node);
rbe = rb_entry(node, struct nft_rbtree_elem, node);
+ if (!nft_set_elem_active(&rbe->ext, NFT_GENMASK_ANY))
+ continue;
/* elements are reversed in the rbtree for historical reasons,
* from highest to lowest value, that is why end element is
--
2.47.3
^ permalink raw reply related [flat|nested] 27+ messages in thread* Re: [PATCH net 08/11] netfilter: nft_set_rbtree: skip transaction elements during GC
2026-09-27 22:08 ` [PATCH net 08/11] netfilter: nft_set_rbtree: skip transaction elements during GC Pablo Neira Ayuso
@ 2026-09-28 23:55 ` netdev-bot+sashiko
0 siblings, 0 replies; 27+ messages in thread
From: netdev-bot+sashiko @ 2026-09-28 23:55 UTC (permalink / raw)
To: pablo; +Cc: netfilter-devel, davem, netdev, kuba, pabeni, edumazet, horms, fw,
ja
Thank you for your contribution! Sashiko AI review found 1 potential
issue(s) to consider.
Critical: 0 · High: 1 (1) · Medium: 0 · Low: 0
If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).
- [High] The patch does not fix the stale end pairing in
nft_rbtree_gc_scan() (net/netfilter/nft_set_rbtree.c).
Pre-existing issues:
- [High] This problem wasn't introduced by this patch, but the insertion-
time GC path has the same wrong-end pairing, and this commit leaves it…
--
netfilter: nft_set_rbtree: skip transaction elements during GC
This patch makes nft_rbtree_gc_scan() skip any element that is not fully
active in both generations. The goal is to keep elements added by the
transaction being committed out of the scan. Without that, a new end
element could be paired with an unrelated expired start and freed before
it is activated.
> diff --git a/net/netfilter/nft_set_rbtree.c b/net/netfilter/nft_set_rbtree.c
> index 9894832281c4b..12431b55752fb 100644
> --- a/net/netfilter/nft_set_rbtree.c
> +++ b/net/netfilter/nft_set_rbtree.c
> @@ -900,6 +900,8 @@ static void nft_rbtree_gc_scan(struct nft_set *set)
> next = rb_next(node);
>
> rbe = rb_entry(node, struct nft_rbtree_elem, node);
> + if (!nft_set_elem_active(&rbe->ext, NFT_GENMASK_ANY))
> + continue;
[Severity: High]
The commit message says this change "preserves interval pairing across
skipped elements". Is that the case in nft_rbtree_gc_scan()?
rbe_end is only cleared after a GC move. When the scan reaches a fully
active start that has not expired, rbe_end still points at that start's
own end:
if (nft_rbtree_interval_end(rbe)) {
rbe_end = rbe;
continue;
}
if (!__nft_set_elem_expired(&rbe->ext, tstamp))
continue;
The new skip also leaves rbe_end unchanged. Suppose the scan then reaches
an expired start with no end directly above it. This code then moves the
other interval's live end to priv->expired, and nft_rbtree_gc_queue()
frees it later:
if (rbe_end) {
nft_rbtree_gc_elem_move(net, set, priv, rbe_end);
rbe_end = NULL;
}
Take the reported trigger shape: a live interval above, the new elements
skipped, then an expired start without an end. Here the patch seems to
change only which end gets taken. Before, the new end element was taken,
which caused the UAF. Now it is the older, fully active end of the
interval above.
Can an orphaned expired start like this be created with the stock nft
tool? Running nft destroy element on an interval whose start has expired
seems to be enough:
nf_tables_delsetelem()
nft_rbtree_deactivate() on the start
-> returns NULL for the expired start, and start_rbe_cookie is
reset to 0
-> the -ENOENT is ignored for NFT_MSG_DESTROYSETELEM
nft_rbtree_deactivate() on the end
-> nft_rbtree_deactivate_same_interval() returns true because
start_rbe_cookie is 0, so the end is deactivated
nft_set_commit_update() then removes the end from the tree with
nft_trans_elems_remove(). That happens before set->ops->commit() runs
nft_rbtree_gc_scan(). A crafted start-only NEWSETELEM placed in a gap is
also accepted by __nft_rbtree_insert() and gives the same shape.
In that case the higher interval [S_hi, E_hi) loses E_hi.
nft_rbtree_commit() then rebuilds it as [S_hi, next start). If it is the
topmost interval, it becomes open-ended (to == NULL).
Once S_hi expires, the next scan takes the end of the next live interval
above it. A later nft delete element on [S_hi, E_hi) also fails with
ENOENT because E_hi is gone.
Would it make sense to also clear rbe_end when a fully active start has
not expired?
if (!__nft_set_elem_expired(&rbe->ext, tstamp)) {
rbe_end = NULL;
continue;
}
[ ... ]
[Severity: High]
This is a pre-existing issue, not one introduced by this patch. The
insertion-time GC path appears to have the same wrong-end pairing, and
this patch leaves it unchanged.
__nft_rbtree_insert() calls nft_rbtree_gc_elem() when it finds an
expired start that is active in the current generation. The rb_prev()
walk stops at the first fully active end, whatever interval that end
belongs to:
net/netfilter/nft_set_rbtree.c:nft_rbtree_gc_elem() {
...
while (prev) {
rbe_prev = rb_entry(prev, struct nft_rbtree_elem, node);
if (nft_rbtree_interval_end(rbe_prev) &&
nft_set_elem_active(&rbe_prev->ext, NFT_GENMASK_ANY))
break;
prev = rb_prev(prev);
}
rbe_prev = NULL;
if (prev) {
rbe_prev = rb_entry(prev, struct nft_rbtree_elem, node);
nft_rbtree_gc_elem_move(net, set, priv, rbe_prev);
}
...
}
The comment says end intervals "are coupled with the interval start
element", but nothing checks that coupling.
Consider a tree that, walked from high to low keys, holds:
E_w (active), S_w (live), E_x, S_x (expired)
E_x is either pending deletion in the same batch or already removed by an
earlier nft destroy element. When an insertion walk reaches S_x, can the
rb_prev() walk skip E_x and S_w and pick E_w?
If so, nft_rbtree_gc_elem_move() erases E_w, the live end of
[S_w, E_w), and queues it on priv->expired. The next commit frees it and
rebuilds S_w as a wider or open-ended interval.
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260927220816.268206-2-pablo%40netfilter.org
^ permalink raw reply [flat|nested] 27+ messages in thread
* [PATCH net 09/11] netfilter: bpf: reject invalid NAT manipulation types
2026-09-27 22:08 [PATCH net 00/11] Netfilter/IPVS fixes for net Pablo Neira Ayuso
` (7 preceding siblings ...)
2026-09-27 22:08 ` [PATCH net 08/11] netfilter: nft_set_rbtree: skip transaction elements during GC Pablo Neira Ayuso
@ 2026-09-27 22:08 ` Pablo Neira Ayuso
2026-09-27 22:08 ` [PATCH net 10/11] netfilter: flowtable: generalize pending status bit Pablo Neira Ayuso
` (2 subsequent siblings)
11 siblings, 0 replies; 27+ messages in thread
From: Pablo Neira Ayuso @ 2026-09-27 22:08 UTC (permalink / raw)
To: netfilter-devel; +Cc: davem, netdev, kuba, pabeni, edumazet, horms, fw, ja
From: Fernando Fernandez Mancera <fmancera@suse.de>
As bpf_ct_set_nat_info() is not validating the NAT manipulation type a
wrong value can be passed directly to nf_nat_setup_info(). This triggers
the WARN_ON() at nf_nat_setup_info() and if panic_on_warn isn't set,
then IPS_SRC_NAT_DONE is set without adding nat_bysource and conntrack
cleanup tries to unlink an uninitialized hlist node.
Fix this by checking that NAT manipulation type is correct before
calling nf_nat_setup_info(). In addition, if the WARN_ON is hit, return
NF_DROP instead of continuing with the processing to avoid similar
situations in the future.
Reported-by: VEGA <vega@nebusec.ai>
Fixes: 0fabd2aa199f ("net: netfilter: add bpf_ct_set_nat_info kfunc helper")
Signed-off-by: Fernando Fernandez Mancera <fmancera@suse.de>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
---
net/netfilter/nf_nat_bpf.c | 3 +++
net/netfilter/nf_nat_core.c | 5 +++--
2 files changed, 6 insertions(+), 2 deletions(-)
diff --git a/net/netfilter/nf_nat_bpf.c b/net/netfilter/nf_nat_bpf.c
index f9dd85ccea01..7572b58c448b 100644
--- a/net/netfilter/nf_nat_bpf.c
+++ b/net/netfilter/nf_nat_bpf.c
@@ -39,6 +39,9 @@ __bpf_kfunc int bpf_ct_set_nat_info(struct nf_conn___init *nfct,
if (proto != NFPROTO_IPV4 && proto != NFPROTO_IPV6)
return -EINVAL;
+ if (manip != NF_NAT_MANIP_SRC && manip != NF_NAT_MANIP_DST)
+ return -EINVAL;
+
memset(&range, 0, sizeof(struct nf_nat_range2));
range.flags = NF_NAT_RANGE_MAP_IPS;
range.min_addr = *addr;
diff --git a/net/netfilter/nf_nat_core.c b/net/netfilter/nf_nat_core.c
index a4858c2b2d65..cc8e1e81006d 100644
--- a/net/netfilter/nf_nat_core.c
+++ b/net/netfilter/nf_nat_core.c
@@ -767,8 +767,9 @@ nf_nat_setup_info(struct nf_conn *ct,
if (nf_ct_is_confirmed(ct))
return NF_ACCEPT;
- WARN_ON(maniptype != NF_NAT_MANIP_SRC &&
- maniptype != NF_NAT_MANIP_DST);
+ if (WARN_ON(maniptype != NF_NAT_MANIP_SRC &&
+ maniptype != NF_NAT_MANIP_DST))
+ return NF_DROP;
if (WARN_ON(nf_nat_initialized(ct, maniptype)))
return NF_DROP;
--
2.47.3
^ permalink raw reply related [flat|nested] 27+ messages in thread* [PATCH net 10/11] netfilter: flowtable: generalize pending status bit
2026-09-27 22:08 [PATCH net 00/11] Netfilter/IPVS fixes for net Pablo Neira Ayuso
` (8 preceding siblings ...)
2026-09-27 22:08 ` [PATCH net 09/11] netfilter: bpf: reject invalid NAT manipulation types Pablo Neira Ayuso
@ 2026-09-27 22:08 ` Pablo Neira Ayuso
2026-09-28 23:55 ` netdev-bot+sashiko
2026-09-27 22:08 ` [PATCH net 11/11] netfilter: flowtable: restore ieee80211 forward path Pablo Neira Ayuso
2026-09-29 2:11 ` [PATCH net 00/11] Netfilter/IPVS fixes for net Jakub Kicinski
11 siblings, 1 reply; 27+ messages in thread
From: Pablo Neira Ayuso @ 2026-09-27 22:08 UTC (permalink / raw)
To: netfilter-devel; +Cc: davem, netdev, kuba, pabeni, edumazet, horms, fw, ja
Rename NF_FLOW_HW_PENDING to NF_FLOW_PENDING and use it to inhibit the
flowtable GC worker until pending hw offload work has been completed.
Apparently, nf_flow_offload_stats() can schedule work to retrieve stats
while the flow is being removed by GC.
And this bit can also be used in a follow up patch to disable GC until
the flow has been fully added in both directions.
Revert the reordering done in commit d644b23afe1e ("netfilter:
flowtable: publish HW_DEAD after worker is done") to prevent a race
between GC and hw offload handler.
Fixes: 2c8897953f3b ("netfilter: flowtable: Add pending bit for offload work")
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
---
include/net/netfilter/nf_flow_table.h | 2 +-
net/netfilter/nf_flow_table_core.c | 7 ++++++-
net/netfilter/nf_flow_table_offload.c | 14 +++++---------
net/sched/act_ct.c | 2 +-
4 files changed, 13 insertions(+), 12 deletions(-)
diff --git a/include/net/netfilter/nf_flow_table.h b/include/net/netfilter/nf_flow_table.h
index f2e2771f188f..5b611efaa3cd 100644
--- a/include/net/netfilter/nf_flow_table.h
+++ b/include/net/netfilter/nf_flow_table.h
@@ -183,10 +183,10 @@ enum nf_flow_flags {
NF_FLOW_DNAT,
NF_FLOW_CLOSING,
NF_FLOW_TEARDOWN,
+ NF_FLOW_PENDING,
NF_FLOW_HW,
NF_FLOW_HW_DYING,
NF_FLOW_HW_DEAD,
- NF_FLOW_HW_PENDING,
NF_FLOW_HW_BIDIRECTIONAL,
NF_FLOW_HW_ESTABLISHED,
};
diff --git a/net/netfilter/nf_flow_table_core.c b/net/netfilter/nf_flow_table_core.c
index 934c6151f558..36bbc7be2f74 100644
--- a/net/netfilter/nf_flow_table_core.c
+++ b/net/netfilter/nf_flow_table_core.c
@@ -575,7 +575,12 @@ static void nf_flow_table_extend_ct_timeout(struct nf_conn *ct)
static void nf_flow_offload_gc_step(struct nf_flowtable *flow_table,
struct flow_offload *flow, void *data)
{
- bool teardown = test_bit(NF_FLOW_TEARDOWN, &flow->flags);
+ bool teardown;
+
+ if (test_bit(NF_FLOW_PENDING, &flow->flags))
+ return;
+
+ teardown = test_bit(NF_FLOW_TEARDOWN, &flow->flags);
if (nf_flow_has_expired(flow) ||
nf_ct_is_dying(flow->ct) ||
diff --git a/net/netfilter/nf_flow_table_offload.c b/net/netfilter/nf_flow_table_offload.c
index 6757fd89c1f1..4365859220e6 100644
--- a/net/netfilter/nf_flow_table_offload.c
+++ b/net/netfilter/nf_flow_table_offload.c
@@ -995,6 +995,7 @@ static void flow_offload_work_del(struct flow_offload_work *offload)
flow_offload_tuple_del(offload, FLOW_OFFLOAD_DIR_ORIGINAL);
if (test_bit(NF_FLOW_HW_BIDIRECTIONAL, &offload->flow->flags))
flow_offload_tuple_del(offload, FLOW_OFFLOAD_DIR_REPLY);
+ set_bit(NF_FLOW_HW_DEAD, &offload->flow->flags);
}
static void flow_offload_tuple_stats(struct flow_offload_work *offload,
@@ -1056,13 +1057,8 @@ static void flow_offload_work_handler(struct work_struct *work)
default:
WARN_ON_ONCE(1);
}
-
- clear_bit(NF_FLOW_HW_PENDING, &offload->flow->flags);
- if (offload->cmd == FLOW_CLS_DESTROY) {
- /* Publish after the worker's last flow access. */
- smp_mb__before_atomic();
- set_bit(NF_FLOW_HW_DEAD, &offload->flow->flags);
- }
+ smp_mb__before_atomic();
+ clear_bit(NF_FLOW_PENDING, &offload->flow->flags);
kfree(offload);
}
@@ -1089,12 +1085,12 @@ nf_flow_offload_work_alloc(struct nf_flowtable *flowtable,
{
struct flow_offload_work *offload;
- if (test_and_set_bit(NF_FLOW_HW_PENDING, &flow->flags))
+ if (test_and_set_bit(NF_FLOW_PENDING, &flow->flags))
return NULL;
offload = kmalloc_obj(struct flow_offload_work, GFP_ATOMIC);
if (!offload) {
- clear_bit(NF_FLOW_HW_PENDING, &flow->flags);
+ clear_bit(NF_FLOW_PENDING, &flow->flags);
return NULL;
}
diff --git a/net/sched/act_ct.c b/net/sched/act_ct.c
index 55f3521edb4c..626c9a5af0ef 100644
--- a/net/sched/act_ct.c
+++ b/net/sched/act_ct.c
@@ -289,7 +289,7 @@ static bool tcf_ct_flow_is_outdated(const struct flow_offload *flow)
{
return test_bit(IPS_SEEN_REPLY_BIT, &flow->ct->status) &&
test_bit(IPS_HW_OFFLOAD_BIT, &flow->ct->status) &&
- !test_bit(NF_FLOW_HW_PENDING, &flow->flags) &&
+ !test_bit(NF_FLOW_PENDING, &flow->flags) &&
!test_bit(NF_FLOW_HW_ESTABLISHED, &flow->flags);
}
--
2.47.3
^ permalink raw reply related [flat|nested] 27+ messages in thread* Re: [PATCH net 10/11] netfilter: flowtable: generalize pending status bit
2026-09-27 22:08 ` [PATCH net 10/11] netfilter: flowtable: generalize pending status bit Pablo Neira Ayuso
@ 2026-09-28 23:55 ` netdev-bot+sashiko
0 siblings, 0 replies; 27+ messages in thread
From: netdev-bot+sashiko @ 2026-09-28 23:55 UTC (permalink / raw)
To: pablo; +Cc: netfilter-devel, davem, netdev, kuba, pabeni, edumazet, horms, fw,
ja
Thank you for your contribution! Sashiko AI review found 2 potential
issue(s) to consider.
Critical: 0 · High: 1 (1) · Medium: 0 (1) · Low: 1 (1)
If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).
- [High] The new NF_FLOW_PENDING gate in nf_flow_offload_gc_step()
(nf_flow_table_core.c:580) is a single relaxed test_bit() snapshot.
- [Low] flow_offload_work_handler() now releases the flow with
smp_mb__before_atomic() followed by clear_bit(NF_FLOW_PENDING)…
Pre-existing issues:
- [High] nf_flow_table_free()
(net/netfilter/nf_flow_table_core.c:775-788) does not wait for DESTROY…
- [Medium] The datapath can re-offload a flow that GC has already torn
down and removed from hardware, and that rule is never deleted.
- [Low] In nf_flow_offload_gc_step()
(net/netfilter/nf_flow_table_core.c:609-610), the last branch `else if…
--
netfilter: flowtable: generalize pending status bit
This renames NF_FLOW_HW_PENDING to NF_FLOW_PENDING. nf_flow_offload_gc_step()
now skips any flow that has hardware offload work pending. It also moves the
setting of NF_FLOW_HW_DEAD back into flow_offload_work_del(), reverting
d644b23afe1e, and clears the pending bit after a barrier at the end of
flow_offload_work_handler().
> diff --git a/net/netfilter/nf_flow_table_core.c b/net/netfilter/nf_flow_table_core.c
> index 934c6151f558b..36bbc7be2f745 100644
> --- a/net/netfilter/nf_flow_table_core.c
> +++ b/net/netfilter/nf_flow_table_core.c
> @@ -575,7 +575,12 @@ static void nf_flow_table_extend_ct_timeout(struct nf_conn *ct)
> static void nf_flow_offload_gc_step(struct nf_flowtable *flow_table,
> struct flow_offload *flow, void *data)
> {
> - bool teardown = test_bit(NF_FLOW_TEARDOWN, &flow->flags);
> + bool teardown;
> +
> + if (test_bit(NF_FLOW_PENDING, &flow->flags))
> + return;
[Severity: High]
Does this check actually stop GC from freeing a flow while offload work is
pending? The test_bit() takes one snapshot, and nothing ties it to the
flow_offload_del() call later in this function.
The datapath can still claim NF_FLOW_PENDING after this check.
flow_offload_refresh() is reached from nf_flow_offload_forward() and
tcf_ct_flow_table_lookup() on a flow that was looked up under RCU. It only
checks NF_FLOW_CLOSING and NF_FLOW_HW:
net/netfilter/nf_flow_table_core.c:flow_offload_refresh() {
...
if (likely(!nf_flowtable_hw_offload(flow_table)) ||
test_bit(NF_FLOW_CLOSING, &flow->flags))
return;
if (test_bit(NF_FLOW_HW, &flow->flags))
nf_flow_offload_refresh(flow_table, flow);
}
nf_flow_offload_refresh() then calls nf_flow_offload_work_alloc() with
FLOW_CLS_REPLACE. Its test_and_set_bit(NF_FLOW_PENDING) succeeds as soon as
the DESTROY worker has cleared the bit. TEARDOWN, HW_DYING and HW_DEAD are
never checked on this path.
Could this sequence happen?
CPU0 (datapath) GC / offload workqueue
flow lookup finds F
gc_step(): teardown F
nf_flow_offload_del(), queue DESTROY
DESTROY worker:
set_bit(NF_FLOW_HW_DEAD)
clear_bit(NF_FLOW_PENDING)
later gc_step():
test_bit(NF_FLOW_PENDING) == 0
flow_offload_refresh(F)
test_and_set_bit(PENDING)
queue FLOW_CLS_REPLACE
HW_DEAD set -> flow_offload_del()
call_rcu(flow_offload_free_rcu)
leaves RCU read section
flow_offload_free_rcu():
nf_ct_put(flow->ct); kfree(flow);
REPLACE worker:
flow_offload_work_add() on freed F
clear_bit(NF_FLOW_PENDING) on freed F
call_rcu() only waits for CPU0 to leave its RCU read section. It does not
wait for the work that CPU0 queued.
GC passes can run back to back through
nf_flow_table_cleanup()->flush_delayed_work() on successive NETDEV_DOWN
events. On PREEMPT_RT the reader can also be preempted for a long time.
The commit message says the bit is used "to inhibit the flowtable GC worker
until pending hw offload work has been completed" and "to prevent a race
between GC and hw offload handler". Is a re-check of TEARDOWN or HW_DYING
needed after the fully ordered test_and_set_bit() in
nf_flow_offload_work_alloc() (or in nf_flow_offload_refresh()) to close
this?
[Severity: Low]
Should this be test_bit_acquire()? flow_offload_work_handler() now does
smp_mb__before_atomic() followed by clear_bit(NF_FLOW_PENDING) as the
release side. The reader here is a relaxed test_bit(), though, and the
if/return control dependency only orders later stores, not later loads.
On weakly ordered CPUs such as arm64, GC can see PENDING already cleared by
a FLOW_CLS_STATS work and still read an old flow->timeout in
nf_flow_has_expired(). That would be the value from before
flow_offload_work_stats() stored:
offload->flow->timeout = max_t(u64, offload->flow->timeout,
lastused + flow_offload_get_timeout(offload->flow));
In that case, could GC tear down a flow that the hardware has just reported
as active? The HW_DEAD read is in the same word as PENDING, so it is not
affected.
> +
> + teardown = test_bit(NF_FLOW_TEARDOWN, &flow->flags);
>
> if (nf_flow_has_expired(flow) ||
> nf_ct_is_dying(flow->ct) ||
[Severity: Low]
This is a pre-existing issue, but the last branch of
nf_flow_offload_gc_step() still queues stats work without checking
NF_FLOW_HW_DYING:
} else if (test_bit(NF_FLOW_HW, &flow->flags)) {
nf_flow_offload_stats(flow_table, flow);
}
Take a CLOSING flow whose hardware rule has already been destroyed
(HW_DYING and HW_DEAD set, TEARDOWN not set). Will GC keep queueing
FLOW_CLS_STATS work for a cookie the driver has already removed?
With this patch each of those works also sets NF_FLOW_PENDING, so GC skips
the flow until the work finishes. The stats result itself is harmless, but
it adds redundant driver calls and can delay teardown of the flow by up to
about one GC period. This behaviour dates back to commit fdbaf5163331.
> diff --git a/net/netfilter/nf_flow_table_offload.c b/net/netfilter/nf_flow_table_offload.c
> index 6757fd89c1f1a..4365859220e6a 100644
> --- a/net/netfilter/nf_flow_table_offload.c
> +++ b/net/netfilter/nf_flow_table_offload.c
> @@ -995,6 +995,7 @@ static void flow_offload_work_del(struct flow_offload_work *offload)
> flow_offload_tuple_del(offload, FLOW_OFFLOAD_DIR_ORIGINAL);
> if (test_bit(NF_FLOW_HW_BIDIRECTIONAL, &offload->flow->flags))
> flow_offload_tuple_del(offload, FLOW_OFFLOAD_DIR_REPLY);
> + set_bit(NF_FLOW_HW_DEAD, &offload->flow->flags);
> }
[Severity: Medium]
This isn't a bug introduced by this patch, but can the datapath re-offload a
flow after this DESTROY work has removed it from hardware?
flow_offload_lookup() filters out TEARDOWN only at lookup time.
flow_offload_refresh() then checks only NF_FLOW_CLOSING and NF_FLOW_HW.
flow_offload_work_add() does not re-check TEARDOWN, HW_DYING or HW_DEAD
before it does this:
err = flow_offload_rule_add(offload, flow_rule);
if (err < 0)
goto out;
set_bit(IPS_HW_OFFLOAD_BIT, &offload->flow->ct->status);
Suppose a reader looks up the flow before GC sets TEARDOWN, and calls
refresh after this worker has set HW_DEAD and cleared PENDING. The REPLACE
work then re-installs the hardware rule.
Once the REPLACE work clears PENDING, the next nf_flow_offload_gc_step()
sees TEARDOWN, HW_DYING and HW_DEAD all set. It calls flow_offload_del()
without issuing another DESTROY.
Doesn't that leave a hardware rule for the 5-tuple, keyed by a cookie that
points into the freed flow_offload? That rule would keep forwarding traffic
outside conntrack and the ruleset until the flowtable or block is torn
down.
[ ... ]
> @@ -1089,12 +1085,12 @@ nf_flow_offload_work_alloc(struct nf_flowtable *flowtable,
> {
> struct flow_offload_work *offload;
>
> - if (test_and_set_bit(NF_FLOW_HW_PENDING, &flow->flags))
> + if (test_and_set_bit(NF_FLOW_PENDING, &flow->flags))
> return NULL;
>
> offload = kmalloc_obj(struct flow_offload_work, GFP_ATOMIC);
> if (!offload) {
> - clear_bit(NF_FLOW_HW_PENDING, &flow->flags);
> + clear_bit(NF_FLOW_PENDING, &flow->flags);
> return NULL;
> }
[Severity: High]
This is a pre-existing issue, but what happens during flowtable teardown if
this GFP_ATOMIC allocation fails for a DESTROY request?
If it fails, nf_flow_offload_del() returns without setting NF_FLOW_HW_DYING
and nothing is queued. nf_flow_table_free() then continues with:
nf_flow_table_gc_run(flow_table);
nf_flow_table_offload_flush_cleanup(flow_table);
rhashtable_destroy(&flow_table->rhashtable);
and nf_flow_table_offload_flush_cleanup() does:
if (nf_flowtable_hw_offload(flowtable)) {
flush_workqueue(nf_flow_offload_del_wq);
nf_flow_table_gc_run(flowtable);
}
The second gc_run now queues the DESTROY work on nf_flow_offload_del_wq,
and nothing flushes that workqueue afterwards. rhashtable_destroy() does not
free the entries, so the flow_offload and its ct and dst references are
leaked.
The callers free the flowtable straight away: kfree(flowtable) in
nf_tables_flowtable_destroy() and kfree(ct_ft) in
tcf_ct_flow_table_cleanup_work(). The queued work then runs:
flow_offload_work_handler()
read_pnet(&offload->flowtable->net)
flow_offload_work_del()
flow_offload_tuple_del()
nf_flow_offload_tuple()
down_read(&flowtable->flow_block_lock)
list_for_each_entry(block_cb, block_cb_list, list)
Isn't this a use-after-free of the nf_flowtable? If the allocation fails
again in the second pass, the flow is simply leaked.
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260927220816.268206-2-pablo%40netfilter.org
^ permalink raw reply [flat|nested] 27+ messages in thread
* [PATCH net 11/11] netfilter: flowtable: restore ieee80211 forward path
2026-09-27 22:08 [PATCH net 00/11] Netfilter/IPVS fixes for net Pablo Neira Ayuso
` (9 preceding siblings ...)
2026-09-27 22:08 ` [PATCH net 10/11] netfilter: flowtable: generalize pending status bit Pablo Neira Ayuso
@ 2026-09-27 22:08 ` Pablo Neira Ayuso
2026-09-29 2:11 ` [PATCH net 00/11] Netfilter/IPVS fixes for net Jakub Kicinski
11 siblings, 0 replies; 27+ messages in thread
From: Pablo Neira Ayuso @ 2026-09-27 22:08 UTC (permalink / raw)
To: netfilter-devel; +Cc: davem, netdev, kuba, pabeni, edumazet, horms, fw, ja
Before commit 871df5007eda ("netfilter: flowtable: bail out if forward
path cannot be discovered"), there was a fallback to set up a forward
path in case .ndo_fill_forward_path fails or DEV_PATH_MTK_WDMA was used.
Such fallback was used by commit d787a3e38f01 ("mac80211: add support
for .ndo_fill_forward_path").
One possibility is to handle DEV_PATH_MTK_WDMA from the flowtable
forward path discovery. However, this is only used internally by drivers
to retrieve mtk_wdma information to set up hardware offload. Felix
decided to use the .fill_forward_path interface for this purpose due to
the lack of a better interface at that time.
Add a new DEV_PATH_IEEE80211 path which is offered if the new ieee80211
flag is set on in the struct net_device_path_ctx to restore the
flowtable with a ieee80211 netdevice. Handle this new DEV_PATH_IEEE80211
path just like DEV_PATH_ETHERNET and DEV_PATH_DSA, ie. this is the last
netdevice in the stack.
This new ieee80211 flag is implicitly unset for mtk_ppe and airoha which
call dev_fill_forward_path() to retrieve a DEV_PATH_MTK_WDMA path.
Fixes: 871df5007eda ("netfilter: flowtable: bail out if forward path cannot be discovered")
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
---
include/linux/netdevice.h | 3 +++
net/mac80211/iface.c | 7 +++++++
net/netfilter/nf_flow_table_path.c | 3 +++
3 files changed, 13 insertions(+)
diff --git a/include/linux/netdevice.h b/include/linux/netdevice.h
index 87cafc932e9e..3cff2174dc03 100644
--- a/include/linux/netdevice.h
+++ b/include/linux/netdevice.h
@@ -887,6 +887,7 @@ enum net_device_path_type {
DEV_PATH_DSA,
DEV_PATH_MTK_WDMA,
DEV_PATH_TUN,
+ DEV_PATH_IEEE80211,
};
struct net_device_path {
@@ -953,6 +954,8 @@ struct net_device_path_ctx {
u16 id;
__be16 proto;
} vlan[NET_DEVICE_PATH_VLAN_MAX];
+
+ bool ieee80211;
};
enum tc_setup_type {
diff --git a/net/mac80211/iface.c b/net/mac80211/iface.c
index 889c32fd8de1..c5584435fde9 100644
--- a/net/mac80211/iface.c
+++ b/net/mac80211/iface.c
@@ -1023,6 +1023,13 @@ static int ieee80211_netdev_fill_forward_path(struct net_device_path_ctx *ctx,
struct sta_info *sta;
int ret = -ENOENT;
+ if (ctx->ieee80211) {
+ path->type = DEV_PATH_IEEE80211;
+ path->dev = ctx->dev;
+ ctx->dev = NULL;
+ return 0;
+ }
+
sdata = IEEE80211_DEV_TO_SUB_IF(ctx->dev);
local = sdata->local;
diff --git a/net/netfilter/nf_flow_table_path.c b/net/netfilter/nf_flow_table_path.c
index 1e55644f2edb..d90013685bf1 100644
--- a/net/netfilter/nf_flow_table_path.c
+++ b/net/netfilter/nf_flow_table_path.c
@@ -53,6 +53,7 @@ static int nft_dev_fill_forward_path(const struct dst_entry *dst_cache,
struct net_device_path_ctx ctx = {
.dev = dev,
.ether_type = ether_type,
+ .ieee80211 = true,
};
struct neighbour *n;
u8 nud_state;
@@ -114,6 +115,7 @@ static int nft_dev_path_info(struct net_device_path_stack *stack,
path = &stack->path[i];
switch (path->type) {
case DEV_PATH_ETHERNET:
+ case DEV_PATH_IEEE80211:
case DEV_PATH_DSA:
case DEV_PATH_VLAN:
case DEV_PATH_PPPOE:
@@ -123,6 +125,7 @@ static int nft_dev_path_info(struct net_device_path_stack *stack,
memcpy(info->h_source, path->dev->dev_addr, ETH_ALEN);
if (path->type == DEV_PATH_ETHERNET ||
+ path->type == DEV_PATH_IEEE80211 ||
path->type == DEV_PATH_DSA)
break;
--
2.47.3
^ permalink raw reply related [flat|nested] 27+ messages in thread* Re: [PATCH net 00/11] Netfilter/IPVS fixes for net
2026-09-27 22:08 [PATCH net 00/11] Netfilter/IPVS fixes for net Pablo Neira Ayuso
` (10 preceding siblings ...)
2026-09-27 22:08 ` [PATCH net 11/11] netfilter: flowtable: restore ieee80211 forward path Pablo Neira Ayuso
@ 2026-09-29 2:11 ` Jakub Kicinski
2026-09-29 9:41 ` Pablo Neira Ayuso
11 siblings, 1 reply; 27+ messages in thread
From: Jakub Kicinski @ 2026-09-29 2:11 UTC (permalink / raw)
To: Pablo Neira Ayuso
Cc: netfilter-devel, davem, netdev, pabeni, edumazet, horms, fw, ja
On Mon, 28 Sep 2026 00:08:05 +0200 Pablo Neira Ayuso wrote:
> The following batch contains Netfilter fixes for net:
I didn't spot anything obviously needing a respin in the AI feedback,
could you confirm that it's good as is? If you have to respin it'd be
good to remove the claim that patch 2 is a nop, Linus is onto us for
sending too many LLM-induced, low impact fixes.
^ permalink raw reply [flat|nested] 27+ messages in thread* Re: [PATCH net 00/11] Netfilter/IPVS fixes for net
2026-09-29 2:11 ` [PATCH net 00/11] Netfilter/IPVS fixes for net Jakub Kicinski
@ 2026-09-29 9:41 ` Pablo Neira Ayuso
2026-09-29 14:36 ` Julian Anastasov
0 siblings, 1 reply; 27+ messages in thread
From: Pablo Neira Ayuso @ 2026-09-29 9:41 UTC (permalink / raw)
To: Jakub Kicinski
Cc: netfilter-devel, davem, netdev, pabeni, edumazet, horms, fw, ja
Hi Jakub, Paolo,
On Mon, Sep 28, 2026 at 07:11:20PM -0700, Jakub Kicinski wrote:
> On Mon, 28 Sep 2026 00:08:05 +0200 Pablo Neira Ayuso wrote:
> > The following batch contains Netfilter fixes for net:
>
> I didn't spot anything obviously needing a respin in the AI feedback,
> could you confirm that it's good as is? If you have to respin it'd be
> good to remove the claim that patch 2 is a nop, Linus is onto us for
> sending too many LLM-induced, low impact fixes.
I would say yes too. LLM comments say:
- Patch 2/11 (ipvs): commit description could be improved (yes, there
is always room for improvement in that regard but I think description
is fair fine enough). There is a report on a pre-existing issue, but
I think that can be addressed as a follow up.
- Patch 3/11 (netfilter): commit description could be improved again,
but patch is good IMO.
- Patch 7/11 (ipvs): there's seem to be another path to abuse this
code LLM found, I would address this as a follow up.
- Patch 8/11 (netfilter): refers to a pre-existing issue.
It also refers to issues with reordering elements of the range,
but this API really need elements in order to work fine, otherwise
overlap detection will likely fire.
- Patch 10/11 (netfilter): refers to a pre-existing issues.
nf_flow_offload_refresh() also needs to be disabled in pending
work is enqueued. Also disable stats fetching for dying hw entries.
In particular, I am observing IPVS patches are getting stuck because
of reports of pre-existing issues. Sometimes you find two or three
things that need an adjustment, and you can start tackling one of the
aspects at a time (because addressing them all at once it not easy).
I think it will help Julian if he has a chance to address issues as
follow up, unless LLM reports something really sound and compelling
that can be classified as a blocker.
In that regard, my impression is that LLMs are a bit overwhelming
because they complain about one aspect that still needs to be
addressed. Not coming in this series, but I can see this is happening
too with Florian when he has been addressing some of the existing
issues with ipset hashtable resizing.
Oh well, and me, because most of the reports here seem to be like
pre-existing issues.
Just my two cents here, these folks are doing very useful work and
they (and me too) will just follow up on pre-existing issues.
^ permalink raw reply [flat|nested] 27+ messages in thread
* Re: [PATCH net 00/11] Netfilter/IPVS fixes for net
2026-09-29 9:41 ` Pablo Neira Ayuso
@ 2026-09-29 14:36 ` Julian Anastasov
0 siblings, 0 replies; 27+ messages in thread
From: Julian Anastasov @ 2026-09-29 14:36 UTC (permalink / raw)
To: Pablo Neira Ayuso
Cc: Jakub Kicinski, netfilter-devel, davem, netdev, pabeni, edumazet,
horms, fw
Hello,
On Tue, 29 Sep 2026, Pablo Neira Ayuso wrote:
> Hi Jakub, Paolo,
>
> On Mon, Sep 28, 2026 at 07:11:20PM -0700, Jakub Kicinski wrote:
> > On Mon, 28 Sep 2026 00:08:05 +0200 Pablo Neira Ayuso wrote:
> > > The following batch contains Netfilter fixes for net:
> >
> > I didn't spot anything obviously needing a respin in the AI feedback,
> > could you confirm that it's good as is? If you have to respin it'd be
> > good to remove the claim that patch 2 is a nop, Linus is onto us for
> > sending too many LLM-induced, low impact fixes.
>
> I would say yes too. LLM comments say:
>
> - Patch 2/11 (ipvs): commit description could be improved (yes, there
> is always room for improvement in that regard but I think description
> is fair fine enough). There is a report on a pre-existing issue, but
> I think that can be addressed as a follow up.
I already have v2 for 2/11, now as 2 patches:
https://sashiko.dev/#/patchset/20260929113436.44306-1-ja%40ssi.bg
But better to leave it for next week, it is a low
priority fix. As usually, if we stop 1 patch, it is replaced
by 2 for the next round :)
> - Patch 3/11 (netfilter): commit description could be improved again,
> but patch is good IMO.
>
> - Patch 7/11 (ipvs): there's seem to be another path to abuse this
> code LLM found, I would address this as a follow up.
>
> - Patch 8/11 (netfilter): refers to a pre-existing issue.
> It also refers to issues with reordering elements of the range,
> but this API really need elements in order to work fine, otherwise
> overlap detection will likely fire.
>
> - Patch 10/11 (netfilter): refers to a pre-existing issues.
> nf_flow_offload_refresh() also needs to be disabled in pending
> work is enqueued. Also disable stats fetching for dying hw entries.
>
> In particular, I am observing IPVS patches are getting stuck because
> of reports of pre-existing issues. Sometimes you find two or three
> things that need an adjustment, and you can start tackling one of the
> aspects at a time (because addressing them all at once it not easy).
> I think it will help Julian if he has a chance to address issues as
> follow up, unless LLM reports something really sound and compelling
> that can be classified as a blocker.
I don't find the LLM reviews wrong, so I don't
complain :) I'll note if a followup is preferred but in
most of the time the patches need to be corrected. I try
to post different patches for the different problems but
this is also a load for the other maintainers. I hope
I'll improve with the time but it is true that we are
overloaded. The only hope is that the bug-report rate will
slowdown with the time...
Regards
--
Julian Anastasov <ja@ssi.bg>
^ permalink raw reply [flat|nested] 27+ messages in thread