* [PATCH net 00/10] Netfilter/IPVS fixes for net
@ 2026-10-19 15:02 Pablo Neira Ayuso
2026-10-19 15:02 ` [PATCH net 01/10] netfilter: nf_conntrack: avoid truncating IPv6 nexthdr offset Pablo Neira Ayuso
` (9 more replies)
0 siblings, 10 replies; 18+ messages in thread
From: Pablo Neira Ayuso @ 2026-10-19 15:02 UTC (permalink / raw)
To: netfilter-devel; +Cc: davem, netdev, kuba, pabeni, edumazet, horms, fw, ja
Hi,
The following patchset contains Netfilter/IPVS for net:
1) Fix IPv6 nexthdr offset truncation in fragment reassembly,
this has been uncovered by LLM, from Jeremy Jean.
2) Restrict checksums in nfqueue to known protocols and offset,
after this patch the plan is to re-enable nfqueue in userns,
this came via LLM report. from Florian Westhphal.
3) Fix races when handling overload flag in IPVS, this patch
is not perfect but Julian considers this improves the situation.
This was uncovered by LLM. From Julian Anastasov.
4) Error handling for IPVS sync_threshold sysctl, from Joel Granados.
5) Use skb_vlan_tag_get_id() to retrieve the VLAN id, discard tci
bits, when handling VLAN in the flowtable, from Shuangfeng He.
6) Initialize IPCB for inet/ingress reject, uncovered by LLM.
7) Restrict completion of binary search array to anonymous set.
8) Restrict DCCP option matching to avoid out-of-bound read,
from Sung Byeongchan.
9) Reset transport header offset in flowtable after tunnel
decapsulation to fix issue with qdisc xmit path,
from Lorenzo Bianconi.
10) Fix inverted matching in ctnetlink dump filtering,
from Liu Chao.
Please, pull these changes from:
git://git.kernel.org/pub/scm/linux/kernel/git/netfilter/nf.git nf-26-10-19
Thanks.
----------------------------------------------------------------
The following changes since commit ab699d296e066eb64f51d2a8a1d42237a3b186c8:
Merge branch 'net-always-dissect-gso-packets-in-__virtio_net_hdr_to_skb' (2026-10-06 15:35:42 -0700)
are available in the Git repository at:
git://git.kernel.org/pub/scm/linux/kernel/git/netfilter/nf.git nf-26-10-19
for you to fetch changes up to cd37ab22a74e05aae5c16c67295cbf5188b16268:
netfilter: nf_conntrack_netlink: fix inverted IPv6 tuple filter (2026-10-19 14:14:03 +0200)
----------------------------------------------------------------
netfilter pull request 26-10-19
----------------------------------------------------------------
Florian Westphal (1):
netfilter: nft_payload: restrict checksum offsets to known values
Joel Granados (1):
ipvs: skip committing sync_threshold when proc_dointvec fails
Julian Anastasov (1):
ipvs: fix more races around the overload flag
Jérémy Jean (1):
netfilter: nf_conntrack: avoid truncating IPv6 nexthdr offset
Kyle Zeng (1):
netfilter: nf_reject: initialize IPCB at inet ingress
Liu Chao (1):
netfilter: nf_conntrack_netlink: fix inverted IPv6 tuple filter
Lorenzo Bianconi (1):
netfilter: flowtable: reset transport header after nf_flow_encap_pop()
Pablo Neira Ayuso (1):
netfilter: nft_set_rbtree: narrow down range completion to anonymous sets
Shuangfeng He (1):
netfilter: flowtable: use the vlan id, not the full tci, in the tuple key
Sung Byeongchan (1):
netfilter: xt_dccp: handle reserved options as single-byte
include/net/ip_vs.h | 50 +++++++++++++++++++++------------
net/ipv4/netfilter/nf_reject_ipv4.c | 5 ++++
net/ipv6/netfilter/nf_conntrack_reasm.c | 2 +-
net/ipv6/netfilter/nf_reject_ipv6.c | 5 ++++
net/netfilter/ipvs/ip_vs_conn.c | 8 +++---
net/netfilter/ipvs/ip_vs_ctl.c | 41 ++++++++++++++-------------
net/netfilter/ipvs/ip_vs_dh.c | 16 ++---------
net/netfilter/ipvs/ip_vs_fo.c | 2 +-
net/netfilter/ipvs/ip_vs_lblc.c | 4 +--
net/netfilter/ipvs/ip_vs_lblcr.c | 8 +++---
net/netfilter/ipvs/ip_vs_lc.c | 2 +-
net/netfilter/ipvs/ip_vs_mh.c | 2 +-
net/netfilter/ipvs/ip_vs_nq.c | 2 +-
net/netfilter/ipvs/ip_vs_ovf.c | 2 +-
net/netfilter/ipvs/ip_vs_rr.c | 2 +-
net/netfilter/ipvs/ip_vs_sed.c | 4 +--
net/netfilter/ipvs/ip_vs_sh.c | 2 +-
net/netfilter/ipvs/ip_vs_twos.c | 4 +--
net/netfilter/ipvs/ip_vs_wlc.c | 4 +--
net/netfilter/ipvs/ip_vs_wrr.c | 2 +-
net/netfilter/nf_conntrack_netlink.c | 8 +++---
net/netfilter/nf_flow_table_ip.c | 6 ++--
net/netfilter/nft_payload.c | 40 +++++++++++++++++++++-----
net/netfilter/nft_set_rbtree.c | 3 +-
net/netfilter/xt_dccp.c | 2 +-
25 files changed, 134 insertions(+), 92 deletions(-)
^ permalink raw reply [flat|nested] 18+ messages in thread
* [PATCH net 01/10] netfilter: nf_conntrack: avoid truncating IPv6 nexthdr offset
2026-10-19 15:02 [PATCH net 00/10] Netfilter/IPVS fixes for net Pablo Neira Ayuso
@ 2026-10-19 15:02 ` Pablo Neira Ayuso
2026-10-19 15:55 ` netdev-bot+sinfo
2026-10-19 15:02 ` [PATCH net 02/10] netfilter: nft_payload: restrict checksum offsets to known values Pablo Neira Ayuso
` (8 subsequent siblings)
9 siblings, 1 reply; 18+ messages in thread
From: Pablo Neira Ayuso @ 2026-10-19 15:02 UTC (permalink / raw)
To: netfilter-devel; +Cc: davem, netdev, kuba, pabeni, edumazet, horms, fw, ja
From: Jérémy Jean <Jeremy.Jean@oss.cyber.gouv.fr>
find_prev_fhdr() returns the offset of the Next Header field before an
IPv6 Fragment header through an int pointer, but stores the offset in a
u8 local. An extension header chain can put this field beyond byte 255,
causing the offset to wrap before it is saved in the fragment queue.
Reassembly later uses the wrapped value to update the preceding Next
Header field. This overwrites another byte in the IPv6 header chain and
can make conntrack parse a different transport tuple from the one used
by IPv6 local delivery. A two-fragment packet can, for example, be
recorded as UDP destination port 53 while its payload is delivered to
UDP port 9999.
Use int for the working offset, matching the output argument and the
other offset variables in this path.
Fixes: 9fb9cbb1082d ("[NETFILTER]: Add nf_conntrack subsystem.")
Assisted-by: Codex:gpt-5
Signed-off-by: Jérémy Jean <Jeremy.Jean@oss.cyber.gouv.fr>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
---
net/ipv6/netfilter/nf_conntrack_reasm.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/net/ipv6/netfilter/nf_conntrack_reasm.c b/net/ipv6/netfilter/nf_conntrack_reasm.c
index 599c49bf0a0a..be72c4346f8b 100644
--- a/net/ipv6/netfilter/nf_conntrack_reasm.c
+++ b/net/ipv6/netfilter/nf_conntrack_reasm.c
@@ -398,7 +398,7 @@ find_prev_fhdr(struct sk_buff *skb, u8 *prevhdrp, int *prevhoff, int *fhoff)
{
u8 nexthdr = ipv6_hdr(skb)->nexthdr;
const int netoff = skb_network_offset(skb);
- u8 prev_nhoff = netoff + offsetof(struct ipv6hdr, nexthdr);
+ int prev_nhoff = netoff + offsetof(struct ipv6hdr, nexthdr);
int start = netoff + sizeof(struct ipv6hdr);
int len = skb->len - start;
u8 prevhdr = NEXTHDR_IPV6;
--
2.47.3
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH net 02/10] netfilter: nft_payload: restrict checksum offsets to known values
2026-10-19 15:02 [PATCH net 00/10] Netfilter/IPVS fixes for net Pablo Neira Ayuso
2026-10-19 15:02 ` [PATCH net 01/10] netfilter: nf_conntrack: avoid truncating IPv6 nexthdr offset Pablo Neira Ayuso
@ 2026-10-19 15:02 ` Pablo Neira Ayuso
2026-10-20 15:36 ` netdev-bot+sashiko
2026-10-19 15:02 ` [PATCH net 03/10] ipvs: fix more races around the overload flag Pablo Neira Ayuso
` (7 subsequent siblings)
9 siblings, 1 reply; 18+ messages in thread
From: Pablo Neira Ayuso @ 2026-10-19 15:02 UTC (permalink / raw)
To: netfilter-devel; +Cc: davem, netdev, kuba, pabeni, edumazet, horms, fw, ja
From: Florian Westphal <fw@strlen.de>
We need to prevent userspace from corrupting e.g. tcp->doff, because
many locations in conntrack and conntrack helpers rely on
nf_conntrack_in() having validated the packet headers.
nft_payload allows to alter headers later which invalidates this
assumption.
The 'Fixes' commit restricts writes to safe fields, but there is
another side channel: the checksum location.
Restrict this too. Reported via sashiko/gemini.
Fixes: 112e447d17f7 ("netfilter: validate L4 headers after userspace packet writes")
Assisted-by: Claude:claude-opus-4-6
Signed-off-by: Florian Westphal <fw@strlen.de>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
---
net/netfilter/nft_payload.c | 40 ++++++++++++++++++++++++++++++-------
1 file changed, 33 insertions(+), 7 deletions(-)
diff --git a/net/netfilter/nft_payload.c b/net/netfilter/nft_payload.c
index e315d35f73d4..5b76af68b704 100644
--- a/net/netfilter/nft_payload.c
+++ b/net/netfilter/nft_payload.c
@@ -20,6 +20,7 @@
#include <linux/tcp.h>
#include <linux/udp.h>
#include <net/gre.h>
+#include <linux/icmp.h>
#include <linux/icmpv6.h>
#include <linux/ip.h>
#include <linux/ipv6.h>
@@ -1008,11 +1009,14 @@ static bool nft_payload_validate_inet_csum_offset(const struct nft_ctx *ctx,
if (priv->csum_flags) /* makes no sense, asks for "re-update" of L4 checksum */
return false;
- /* no further check here; offset can't be negative so bogus
- * offsets can corrupt L4 or payload but not l3 headers.
- * We already allow arbitrary l4/inner payload writes.
- */
- return true;
+ /* Validate csum_offset is one of the supported transport header checksums */
+ if (priv->csum_offset == offsetof(struct tcphdr, check) ||
+ priv->csum_offset == offsetof(struct udphdr, check) ||
+ priv->csum_offset == offsetof(struct icmphdr, checksum) ||
+ priv->csum_offset == offsetof(struct icmp6hdr, icmp6_cksum))
+ return true;
+
+ return false;
case NFT_PAYLOAD_INNER_HEADER:
return true;
case NFT_PAYLOAD_TUN_HEADER:
@@ -1046,6 +1050,27 @@ static bool nft_payload_csum_nh_write_ok(const struct nft_payload_set *priv,
return false;
}
+static bool nft_payload_csum_th_write_ok(const struct nft_payload_set *priv,
+ const struct nft_pktinfo *pkt)
+{
+ if (!(pkt->flags & NFT_PKTINFO_L4PROTO))
+ return false;
+
+ switch (pkt->tprot) {
+ case IPPROTO_TCP:
+ return priv->csum_offset == offsetof(struct tcphdr, check);
+ case IPPROTO_UDP:
+ case IPPROTO_UDPLITE:
+ return priv->csum_offset == offsetof(struct udphdr, check);
+ case IPPROTO_ICMPV6:
+ return priv->csum_offset == offsetof(struct icmp6hdr, icmp6_cksum);
+ case IPPROTO_ICMP:
+ return priv->csum_offset == offsetof(struct icmphdr, checksum);
+ }
+
+ return false;
+}
+
static bool nft_payload_csum_write_ok(const struct nft_pktinfo *pkt,
const struct nft_payload_set *priv)
{
@@ -1055,9 +1080,10 @@ static bool nft_payload_csum_write_ok(const struct nft_pktinfo *pkt,
case NFT_PAYLOAD_NETWORK_HEADER:
return nft_payload_csum_nh_write_ok(priv, pkt);
case NFT_PAYLOAD_TRANSPORT_HEADER:
+ return nft_payload_csum_th_write_ok(priv, pkt);
case NFT_PAYLOAD_INNER_HEADER:
- /* neither offsets are validated, offsets cannot be
- * negative so real l3 headers cannot be mangled.
+ /* offset is not validated, offset cannot be
+ * negative so real l3/l4 headers cannot be mangled.
*/
return true;
case NFT_PAYLOAD_TUN_HEADER:
--
2.47.3
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH net 03/10] ipvs: fix more races around the overload flag
2026-10-19 15:02 [PATCH net 00/10] Netfilter/IPVS fixes for net Pablo Neira Ayuso
2026-10-19 15:02 ` [PATCH net 01/10] netfilter: nf_conntrack: avoid truncating IPv6 nexthdr offset Pablo Neira Ayuso
2026-10-19 15:02 ` [PATCH net 02/10] netfilter: nft_payload: restrict checksum offsets to known values Pablo Neira Ayuso
@ 2026-10-19 15:02 ` Pablo Neira Ayuso
2026-10-20 15:36 ` netdev-bot+sashiko
2026-10-19 15:02 ` [PATCH net 04/10] ipvs: skip committing sync_threshold when proc_dointvec fails Pablo Neira Ayuso
` (6 subsequent siblings)
9 siblings, 1 reply; 18+ messages in thread
From: Pablo Neira Ayuso @ 2026-10-19 15:02 UTC (permalink / raw)
To: netfilter-devel; +Cc: davem, netdev, kuba, pabeni, edumazet, horms, fw, ja
From: Julian Anastasov <ja@ssi.bg>
Sashiko warns that we still have races when the dest overload
thresholds are updated. If we narrow the range between
thresholds the lockless readers (ip_vs_bind_dest and
ip_vs_unbind_dest) may use old threshold values and
as result to skip the required flag change.
To solve the problem ip_vs_bind_dest() and ip_vs_unbind_dest()
will have the chance as before to change the overload
state not only at the exact threshold positions but
at any conn count below the lower and above the upper thresholds.
To make it work without entering the locked region for
every connection, we introduce l_threshold_val. Together with
u_threshold_val we will switch between two states:
1. not overloaded: where l_threshold_val is set to 0
to close the door for more ip_vs_unbind_dest() calls
to clear the overload state.
2. overloaded: where u_threshold_val is set to INT_MAX
to close the door for more ip_vs_bind_dest() calls
to set the overload state.
If a race occurs, multiple CPUs can repeat the state change
but this should be for short time window.
With the introduction of l_threshold_val the
IP_VS_DEST_F_OVERLOAD flag and the 'flags' field are not
needed anymore. We will use the ip_vs_dest_is_overloaded()
helper for all places that read the overload state.
Do not check IP_VS_DEST_CF_AVAILABLE in ip_vs_dh.c because
we do not hold reference to dest after it is removed and
rely on RCU to access the available dests.
To reduce the cacheline accesses while selecting destination,
keep the mostly used fields (weight and l_threshold_val)
at the first read-mostly cacheline together with the hash
nodes.
Fixes: 8f843441c4e7 ("ipvs: properly update the overload flag on dest edit")
Link: https://sashiko.dev/#/patchset/20260810190621.894119-1-pablo%40netfilter.org
Signed-off-by: Julian Anastasov <ja@ssi.bg>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
---
include/net/ip_vs.h | 50 ++++++++++++++++++++------------
net/netfilter/ipvs/ip_vs_conn.c | 8 ++---
net/netfilter/ipvs/ip_vs_ctl.c | 38 ++++++++++++------------
net/netfilter/ipvs/ip_vs_dh.c | 16 ++--------
net/netfilter/ipvs/ip_vs_fo.c | 2 +-
net/netfilter/ipvs/ip_vs_lblc.c | 4 +--
net/netfilter/ipvs/ip_vs_lblcr.c | 8 ++---
net/netfilter/ipvs/ip_vs_lc.c | 2 +-
net/netfilter/ipvs/ip_vs_mh.c | 2 +-
net/netfilter/ipvs/ip_vs_nq.c | 2 +-
net/netfilter/ipvs/ip_vs_ovf.c | 2 +-
net/netfilter/ipvs/ip_vs_rr.c | 2 +-
net/netfilter/ipvs/ip_vs_sed.c | 4 +--
net/netfilter/ipvs/ip_vs_sh.c | 2 +-
net/netfilter/ipvs/ip_vs_twos.c | 4 +--
net/netfilter/ipvs/ip_vs_wlc.c | 4 +--
net/netfilter/ipvs/ip_vs_wrr.c | 2 +-
17 files changed, 76 insertions(+), 76 deletions(-)
diff --git a/include/net/ip_vs.h b/include/net/ip_vs.h
index 32fde731bceb..9a83bff32569 100644
--- a/include/net/ip_vs.h
+++ b/include/net/ip_vs.h
@@ -36,9 +36,6 @@
#define IP_VS_HDR_INVERSE 1
#define IP_VS_HDR_ICMP 2
-/* Destination Server Flags */
-#define IP_VS_DEST_F_OVERLOAD 0x0002 /* server is overloaded */
-
/* Destination Server Config Flags */
#define IP_VS_DEST_CF_AVAILABLE 0x0001 /* server is available */
@@ -973,32 +970,43 @@ struct ip_vs_dest_dst {
* and so on.
*/
struct ip_vs_dest {
+ /* Cacheline for hash table nodes - read-mostly */
+
struct list_head n_list; /* for the dests in the service */
struct hlist_node d_list; /* for table with all the dests */
- u16 af; /* address family */
- __be16 port; /* port number of the server */
- union nf_inet_addr addr; /* IP address of the server */
- volatile unsigned int flags; /* dest status flags */
- atomic_t conn_flags; /* flags to copy to conn */
atomic_t weight; /* server weight */
- unsigned long cflags; /* config flags */
atomic_t last_weight; /* server latest weight */
+
+ /* connection thresholds */
+ u32 l_threshold_val;/* used lower threshold */
+ u32 u_threshold_val;/* used upper threshold */
+ /* 32/48 */
+ u32 l_threshold; /* lower threshold */
+ u32 u_threshold; /* upper threshold */
+
+ unsigned long cflags; /* config flags */
+
+ /* 44/64 */
+ atomic_t conn_flags; /* flags to copy to conn */
+
__u16 tun_type; /* tunnel type */
__be16 tun_port; /* tunnel port */
__u16 tun_flags; /* tunnel flags */
- refcount_t refcnt; /* reference counter */
- struct ip_vs_stats stats; /* statistics */
- unsigned long idle_start; /* start time, jiffies */
+ u16 af; /* address family */
+ __be16 port; /* port number of the server */
+ /* 60/80 */
+ union nf_inet_addr addr; /* IP address of the server */
- /* connection counters and thresholds */
- atomic_t activeconns; /* active connections */
+ /* connection counters */
atomic_t totalconns; /* total connections */
+ atomic_t activeconns; /* active connections */
atomic_t persistconns; /* persistent connections */
- __u32 u_threshold; /* upper threshold */
- __u32 l_threshold; /* lower threshold */
- __u32 l_threshold_val;/* used lower threshold */
+
+ refcount_t refcnt; /* reference counter */
+ struct ip_vs_stats stats; /* statistics */
+ unsigned long idle_start; /* start time, jiffies */
/* for destination cache */
spinlock_t dst_lock; /* lock of dst_cache */
@@ -1915,7 +1923,13 @@ static inline void ip_vs_dest_put_and_free(struct ip_vs_dest *dest)
kfree(dest);
}
-void ip_vs_dest_update_overload(struct ip_vs_dest *dest, int mode);
+void ip_vs_dest_update_overload(struct ip_vs_dest *dest, bool overload);
+
+/* Check if dest is in overloaded state */
+static inline bool ip_vs_dest_is_overloaded(const struct ip_vs_dest *dest)
+{
+ return READ_ONCE(dest->l_threshold_val);
+}
/* IPVS sync daemon data and function prototypes
* (from ip_vs_sync.c)
diff --git a/net/netfilter/ipvs/ip_vs_conn.c b/net/netfilter/ipvs/ip_vs_conn.c
index cb009208826f..94f25fb7d87e 100644
--- a/net/netfilter/ipvs/ip_vs_conn.c
+++ b/net/netfilter/ipvs/ip_vs_conn.c
@@ -1153,8 +1153,8 @@ ip_vs_bind_dest(struct ip_vs_conn *cp, struct ip_vs_dest *dest)
if (!(flags & IP_VS_CONN_F_INACTIVE))
atomic_inc(&dest->activeconns);
tc = atomic_inc_return(&dest->totalconns);
- if (tc == READ_ONCE(dest->u_threshold))
- ip_vs_dest_update_overload(dest, 1);
+ if (tc >= READ_ONCE(dest->u_threshold_val))
+ ip_vs_dest_update_overload(dest, true);
} else {
/* It is a persistent connection/template, so increase
the persistent connection counter */
@@ -1246,8 +1246,8 @@ static inline void ip_vs_unbind_dest(struct ip_vs_conn *cp)
if (!(cp->flags & IP_VS_CONN_F_INACTIVE))
atomic_dec(&dest->activeconns);
tc = atomic_fetch_dec(&dest->totalconns);
- if (tc == READ_ONCE(dest->l_threshold_val))
- ip_vs_dest_update_overload(dest, -1);
+ if (tc <= READ_ONCE(dest->l_threshold_val))
+ ip_vs_dest_update_overload(dest, false);
} else {
/* It is a persistent connection/template, so decrease
the persistent connection counter */
diff --git a/net/netfilter/ipvs/ip_vs_ctl.c b/net/netfilter/ipvs/ip_vs_ctl.c
index 4c1c739446b7..3003547dcabe 100644
--- a/net/netfilter/ipvs/ip_vs_ctl.c
+++ b/net/netfilter/ipvs/ip_vs_ctl.c
@@ -1309,32 +1309,32 @@ void ip_vs_stats_free(struct ip_vs_stats *stats)
* - conns reach u_threshold and exceed it: set the flag
* - conns go below l_threshold (or 75% of u_threshold): clear the flag
*/
-static void __ip_vs_dest_update_overload(struct ip_vs_dest *dest, int mode)
+static void __ip_vs_dest_update_overload(struct ip_vs_dest *dest, bool overload)
{
int conns;
u32 l, u;
lockdep_assert_held(&dest->dst_lock);
u = READ_ONCE(dest->u_threshold);
- if (!u)
- goto unset;
- l = READ_ONCE(dest->l_threshold_val);
- conns = atomic_read(&dest->totalconns);
- if (conns >= (mode > 0 ? l : u)) {
- dest->flags |= IP_VS_DEST_F_OVERLOAD;
- return;
+ if (u) {
+ /* Low threshold defaults to 75% of upper threshold */
+ l = READ_ONCE(dest->l_threshold) ? : (u - (u >> 2));
+ conns = atomic_read(&dest->totalconns);
+ if (conns >= (overload ? l : u)) {
+ WRITE_ONCE(dest->u_threshold_val, INT_MAX);
+ WRITE_ONCE(dest->l_threshold_val, l);
+ return;
+ }
}
- if (conns >= (mode < 0 ? u : l))
- return;
-unset:
- dest->flags &= ~IP_VS_DEST_F_OVERLOAD;
+ WRITE_ONCE(dest->u_threshold_val, u ? : INT_MAX);
+ WRITE_ONCE(dest->l_threshold_val, 0);
}
-void ip_vs_dest_update_overload(struct ip_vs_dest *dest, int mode)
+void ip_vs_dest_update_overload(struct ip_vs_dest *dest, bool overload)
{
spin_lock_bh(&dest->dst_lock);
- __ip_vs_dest_update_overload(dest, mode);
+ __ip_vs_dest_update_overload(dest, overload);
spin_unlock_bh(&dest->dst_lock);
}
@@ -1406,15 +1406,13 @@ __ip_vs_update_dest(struct ip_vs_service *svc, struct ip_vs_dest *dest,
if (READ_ONCE(dest->u_threshold) != udest->u_threshold ||
READ_ONCE(dest->l_threshold) != udest->l_threshold) {
+ bool overload;
+
spin_lock_bh(&dest->dst_lock);
WRITE_ONCE(dest->u_threshold, udest->u_threshold);
WRITE_ONCE(dest->l_threshold, udest->l_threshold);
- /* Low threshold defaults to 75% of upper threshold */
- WRITE_ONCE(dest->l_threshold_val,
- udest->l_threshold ? :
- (udest->u_threshold -
- (udest->u_threshold >> 2)));
- __ip_vs_dest_update_overload(dest, 0);
+ overload = ip_vs_dest_is_overloaded(dest);
+ __ip_vs_dest_update_overload(dest, overload);
spin_unlock_bh(&dest->dst_lock);
}
diff --git a/net/netfilter/ipvs/ip_vs_dh.c b/net/netfilter/ipvs/ip_vs_dh.c
index 43abed7a26a6..95e52950adbb 100644
--- a/net/netfilter/ipvs/ip_vs_dh.c
+++ b/net/netfilter/ipvs/ip_vs_dh.c
@@ -195,16 +195,6 @@ static int ip_vs_dh_dest_changed(struct ip_vs_service *svc,
}
-/*
- * If the dest flags is set with IP_VS_DEST_F_OVERLOAD,
- * consider that the server is overloaded here.
- */
-static inline int is_overloaded(struct ip_vs_dest *dest)
-{
- return dest->flags & IP_VS_DEST_F_OVERLOAD;
-}
-
-
/*
* Destination hashing scheduling
*/
@@ -219,10 +209,8 @@ ip_vs_dh_schedule(struct ip_vs_service *svc, const struct sk_buff *skb,
s = (struct ip_vs_dh_state *) svc->sched_data;
dest = ip_vs_dh_get(svc->af, s, &iph->daddr);
- if (!dest ||
- !(dest->cflags & IP_VS_DEST_CF_AVAILABLE)
- || atomic_read(&dest->weight) <= 0
- || is_overloaded(dest)) {
+ if (!dest || atomic_read(&dest->weight) <= 0 ||
+ ip_vs_dest_is_overloaded(dest)) {
ip_vs_scheduler_err(svc, "no destination available");
return NULL;
}
diff --git a/net/netfilter/ipvs/ip_vs_fo.c b/net/netfilter/ipvs/ip_vs_fo.c
index d657b47c6511..e07fa33f6d52 100644
--- a/net/netfilter/ipvs/ip_vs_fo.c
+++ b/net/netfilter/ipvs/ip_vs_fo.c
@@ -29,7 +29,7 @@ ip_vs_fo_schedule(struct ip_vs_service *svc, const struct sk_buff *skb,
* Find virtual server with highest weight and send it traffic
*/
list_for_each_entry_rcu(dest, &svc->destinations, n_list) {
- if (!(dest->flags & IP_VS_DEST_F_OVERLOAD) &&
+ if (!ip_vs_dest_is_overloaded(dest) &&
atomic_read(&dest->weight) > hw) {
hweight = dest;
hw = atomic_read(&dest->weight);
diff --git a/net/netfilter/ipvs/ip_vs_lblc.c b/net/netfilter/ipvs/ip_vs_lblc.c
index 55af77a56929..e1111c3b2721 100644
--- a/net/netfilter/ipvs/ip_vs_lblc.c
+++ b/net/netfilter/ipvs/ip_vs_lblc.c
@@ -418,7 +418,7 @@ __ip_vs_lblc_schedule(struct ip_vs_service *svc)
* new connection.
*/
list_for_each_entry_rcu(dest, &svc->destinations, n_list) {
- if (dest->flags & IP_VS_DEST_F_OVERLOAD)
+ if (ip_vs_dest_is_overloaded(dest))
continue;
if (atomic_read(&dest->weight) > 0) {
least = dest;
@@ -433,7 +433,7 @@ __ip_vs_lblc_schedule(struct ip_vs_service *svc)
*/
nextstage:
list_for_each_entry_continue_rcu(dest, &svc->destinations, n_list) {
- if (dest->flags & IP_VS_DEST_F_OVERLOAD)
+ if (ip_vs_dest_is_overloaded(dest))
continue;
doh = ip_vs_dest_conn_overhead(dest);
diff --git a/net/netfilter/ipvs/ip_vs_lblcr.c b/net/netfilter/ipvs/ip_vs_lblcr.c
index 858393b1d2d1..dfb5bf83750d 100644
--- a/net/netfilter/ipvs/ip_vs_lblcr.c
+++ b/net/netfilter/ipvs/ip_vs_lblcr.c
@@ -166,7 +166,7 @@ static inline struct ip_vs_dest *ip_vs_dest_set_min(struct ip_vs_dest_set *set)
/* select the first destination server, whose weight > 0 */
list_for_each_entry_rcu(e, &set->list, list) {
least = e->dest;
- if (least->flags & IP_VS_DEST_F_OVERLOAD)
+ if (ip_vs_dest_is_overloaded(least))
continue;
if ((atomic_read(&least->weight) > 0) &&
@@ -181,7 +181,7 @@ static inline struct ip_vs_dest *ip_vs_dest_set_min(struct ip_vs_dest_set *set)
nextstage:
list_for_each_entry_continue_rcu(e, &set->list, list) {
dest = e->dest;
- if (dest->flags & IP_VS_DEST_F_OVERLOAD)
+ if (ip_vs_dest_is_overloaded(dest))
continue;
doh = ip_vs_dest_conn_overhead(dest);
@@ -580,7 +580,7 @@ __ip_vs_lblcr_schedule(struct ip_vs_service *svc)
* new connection.
*/
list_for_each_entry_rcu(dest, &svc->destinations, n_list) {
- if (dest->flags & IP_VS_DEST_F_OVERLOAD)
+ if (ip_vs_dest_is_overloaded(dest))
continue;
if (atomic_read(&dest->weight) > 0) {
@@ -596,7 +596,7 @@ __ip_vs_lblcr_schedule(struct ip_vs_service *svc)
*/
nextstage:
list_for_each_entry_continue_rcu(dest, &svc->destinations, n_list) {
- if (dest->flags & IP_VS_DEST_F_OVERLOAD)
+ if (ip_vs_dest_is_overloaded(dest))
continue;
doh = ip_vs_dest_conn_overhead(dest);
diff --git a/net/netfilter/ipvs/ip_vs_lc.c b/net/netfilter/ipvs/ip_vs_lc.c
index 18b976a7c4d2..9002f491bff6 100644
--- a/net/netfilter/ipvs/ip_vs_lc.c
+++ b/net/netfilter/ipvs/ip_vs_lc.c
@@ -38,7 +38,7 @@ ip_vs_lc_schedule(struct ip_vs_service *svc, const struct sk_buff *skb,
*/
list_for_each_entry_rcu(dest, &svc->destinations, n_list) {
- if ((dest->flags & IP_VS_DEST_F_OVERLOAD) ||
+ if (ip_vs_dest_is_overloaded(dest) ||
atomic_read(&dest->weight) == 0)
continue;
doh = ip_vs_dest_conn_overhead(dest);
diff --git a/net/netfilter/ipvs/ip_vs_mh.c b/net/netfilter/ipvs/ip_vs_mh.c
index 020863047562..d70b23aec5fe 100644
--- a/net/netfilter/ipvs/ip_vs_mh.c
+++ b/net/netfilter/ipvs/ip_vs_mh.c
@@ -80,7 +80,7 @@ static inline void generate_hash_secret(hsiphash_key_t *hash1,
static inline bool is_unavailable(struct ip_vs_dest *dest)
{
return atomic_read(&dest->weight) <= 0 ||
- dest->flags & IP_VS_DEST_F_OVERLOAD;
+ ip_vs_dest_is_overloaded(dest);
}
/* Returns hash value for IPVS MH entry */
diff --git a/net/netfilter/ipvs/ip_vs_nq.c b/net/netfilter/ipvs/ip_vs_nq.c
index ada158c610ce..7cc28902086e 100644
--- a/net/netfilter/ipvs/ip_vs_nq.c
+++ b/net/netfilter/ipvs/ip_vs_nq.c
@@ -72,7 +72,7 @@ ip_vs_nq_schedule(struct ip_vs_service *svc, const struct sk_buff *skb,
list_for_each_entry_rcu(dest, &svc->destinations, n_list) {
- if (dest->flags & IP_VS_DEST_F_OVERLOAD ||
+ if (ip_vs_dest_is_overloaded(dest) ||
!atomic_read(&dest->weight))
continue;
diff --git a/net/netfilter/ipvs/ip_vs_ovf.c b/net/netfilter/ipvs/ip_vs_ovf.c
index c5c67df80a0b..eaa3a7ae6efa 100644
--- a/net/netfilter/ipvs/ip_vs_ovf.c
+++ b/net/netfilter/ipvs/ip_vs_ovf.c
@@ -33,7 +33,7 @@ ip_vs_ovf_schedule(struct ip_vs_service *svc, const struct sk_buff *skb,
*/
list_for_each_entry_rcu(dest, &svc->destinations, n_list) {
w = atomic_read(&dest->weight);
- if ((dest->flags & IP_VS_DEST_F_OVERLOAD) ||
+ if (ip_vs_dest_is_overloaded(dest) ||
atomic_read(&dest->activeconns) > w ||
w == 0)
continue;
diff --git a/net/netfilter/ipvs/ip_vs_rr.c b/net/netfilter/ipvs/ip_vs_rr.c
index 4125ee561cdc..bd17cdd092f5 100644
--- a/net/netfilter/ipvs/ip_vs_rr.c
+++ b/net/netfilter/ipvs/ip_vs_rr.c
@@ -66,7 +66,7 @@ ip_vs_rr_schedule(struct ip_vs_service *svc, const struct sk_buff *skb,
list_for_each_entry_continue_rcu(dest,
&svc->destinations,
n_list) {
- if (!(dest->flags & IP_VS_DEST_F_OVERLOAD) &&
+ if (!ip_vs_dest_is_overloaded(dest) &&
atomic_read(&dest->weight) > 0)
/* HIT */
goto out;
diff --git a/net/netfilter/ipvs/ip_vs_sed.c b/net/netfilter/ipvs/ip_vs_sed.c
index 245a323c84cd..7925f4d28fce 100644
--- a/net/netfilter/ipvs/ip_vs_sed.c
+++ b/net/netfilter/ipvs/ip_vs_sed.c
@@ -75,7 +75,7 @@ ip_vs_sed_schedule(struct ip_vs_service *svc, const struct sk_buff *skb,
*/
list_for_each_entry_rcu(dest, &svc->destinations, n_list) {
- if (!(dest->flags & IP_VS_DEST_F_OVERLOAD) &&
+ if (!ip_vs_dest_is_overloaded(dest) &&
atomic_read(&dest->weight) > 0) {
least = dest;
loh = ip_vs_sed_dest_overhead(least);
@@ -90,7 +90,7 @@ ip_vs_sed_schedule(struct ip_vs_service *svc, const struct sk_buff *skb,
*/
nextstage:
list_for_each_entry_continue_rcu(dest, &svc->destinations, n_list) {
- if (dest->flags & IP_VS_DEST_F_OVERLOAD)
+ if (ip_vs_dest_is_overloaded(dest))
continue;
doh = ip_vs_sed_dest_overhead(dest);
if ((__s64)loh * atomic_read(&dest->weight) >
diff --git a/net/netfilter/ipvs/ip_vs_sh.c b/net/netfilter/ipvs/ip_vs_sh.c
index cd67066e3b26..c24dd5cb7e47 100644
--- a/net/netfilter/ipvs/ip_vs_sh.c
+++ b/net/netfilter/ipvs/ip_vs_sh.c
@@ -73,7 +73,7 @@ struct ip_vs_sh_state {
static inline bool is_unavailable(struct ip_vs_dest *dest)
{
return atomic_read(&dest->weight) <= 0 ||
- dest->flags & IP_VS_DEST_F_OVERLOAD;
+ ip_vs_dest_is_overloaded(dest);
}
/*
diff --git a/net/netfilter/ipvs/ip_vs_twos.c b/net/netfilter/ipvs/ip_vs_twos.c
index dbb7f5fd4688..86186122a1f0 100644
--- a/net/netfilter/ipvs/ip_vs_twos.c
+++ b/net/netfilter/ipvs/ip_vs_twos.c
@@ -52,7 +52,7 @@ static struct ip_vs_dest *ip_vs_twos_schedule(struct ip_vs_service *svc,
/* Generate a random weight between [0,sum of all weights) */
list_for_each_entry_rcu(dest, &svc->destinations, n_list) {
- if (!(dest->flags & IP_VS_DEST_F_OVERLOAD)) {
+ if (!ip_vs_dest_is_overloaded(dest)) {
weight = atomic_read(&dest->weight);
if (weight > 0) {
total_weight += weight;
@@ -75,7 +75,7 @@ static struct ip_vs_dest *ip_vs_twos_schedule(struct ip_vs_service *svc,
/* Pick two weighted servers */
list_for_each_entry_rcu(dest, &svc->destinations, n_list) {
- if (dest->flags & IP_VS_DEST_F_OVERLOAD)
+ if (ip_vs_dest_is_overloaded(dest))
continue;
weight = atomic_read(&dest->weight);
diff --git a/net/netfilter/ipvs/ip_vs_wlc.c b/net/netfilter/ipvs/ip_vs_wlc.c
index 9da445ca09a1..08e9e22f72bf 100644
--- a/net/netfilter/ipvs/ip_vs_wlc.c
+++ b/net/netfilter/ipvs/ip_vs_wlc.c
@@ -47,7 +47,7 @@ ip_vs_wlc_schedule(struct ip_vs_service *svc, const struct sk_buff *skb,
*/
list_for_each_entry_rcu(dest, &svc->destinations, n_list) {
- if (!(dest->flags & IP_VS_DEST_F_OVERLOAD) &&
+ if (!ip_vs_dest_is_overloaded(dest) &&
atomic_read(&dest->weight) > 0) {
least = dest;
loh = ip_vs_dest_conn_overhead(least);
@@ -62,7 +62,7 @@ ip_vs_wlc_schedule(struct ip_vs_service *svc, const struct sk_buff *skb,
*/
nextstage:
list_for_each_entry_continue_rcu(dest, &svc->destinations, n_list) {
- if (dest->flags & IP_VS_DEST_F_OVERLOAD)
+ if (ip_vs_dest_is_overloaded(dest))
continue;
doh = ip_vs_dest_conn_overhead(dest);
if ((__s64)loh * atomic_read(&dest->weight) >
diff --git a/net/netfilter/ipvs/ip_vs_wrr.c b/net/netfilter/ipvs/ip_vs_wrr.c
index 2dcff1040da5..fad69bae5a22 100644
--- a/net/netfilter/ipvs/ip_vs_wrr.c
+++ b/net/netfilter/ipvs/ip_vs_wrr.c
@@ -176,7 +176,7 @@ ip_vs_wrr_schedule(struct ip_vs_service *svc, const struct sk_buff *skb,
list_for_each_entry_continue_rcu(dest,
&svc->destinations,
n_list) {
- if (!(dest->flags & IP_VS_DEST_F_OVERLOAD) &&
+ if (!ip_vs_dest_is_overloaded(dest) &&
atomic_read(&dest->weight) >= mark->cw)
goto found;
if (dest == stop)
--
2.47.3
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH net 04/10] ipvs: skip committing sync_threshold when proc_dointvec fails
2026-10-19 15:02 [PATCH net 00/10] Netfilter/IPVS fixes for net Pablo Neira Ayuso
` (2 preceding siblings ...)
2026-10-19 15:02 ` [PATCH net 03/10] ipvs: fix more races around the overload flag Pablo Neira Ayuso
@ 2026-10-19 15:02 ` Pablo Neira Ayuso
2026-10-20 15:37 ` netdev-bot+sashiko
2026-10-19 15:02 ` [PATCH net 05/10] netfilter: flowtable: use the vlan id, not the full tci, in the tuple key Pablo Neira Ayuso
` (5 subsequent siblings)
9 siblings, 1 reply; 18+ messages in thread
From: Pablo Neira Ayuso @ 2026-10-19 15:02 UTC (permalink / raw)
To: netfilter-devel; +Cc: davem, netdev, kuba, pabeni, edumazet, horms, fw, ja
From: Joel Granados <joel.granados@kernel.org>
Forward rc value and skip updating sysctl_sync_threshold when
proc_dointvec fails. This targets a write such as "5 x" which would fail
with -EINVAL but would store the first element anyway.
Signed-off-by: Joel Granados <joel.granados@kernel.org>
Acked-by: Julian Anastasov <ja@ssi.bg>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
---
net/netfilter/ipvs/ip_vs_ctl.c | 3 +++
1 file changed, 3 insertions(+)
diff --git a/net/netfilter/ipvs/ip_vs_ctl.c b/net/netfilter/ipvs/ip_vs_ctl.c
index 3003547dcabe..ce9fe20d6106 100644
--- a/net/netfilter/ipvs/ip_vs_ctl.c
+++ b/net/netfilter/ipvs/ip_vs_ctl.c
@@ -2452,6 +2452,8 @@ proc_do_sync_threshold(const struct ctl_table *table, int write,
mutex_lock(&ipvs->sync_mutex);
memcpy(val, valp, sizeof(val));
rc = proc_dointvec(&tmp, write, buffer, lenp, ppos);
+ if (rc)
+ goto out;
if (write) {
if (val[0] < 0 || val[1] < 0 ||
(val[0] >= val[1] && val[1]))
@@ -2459,6 +2461,7 @@ proc_do_sync_threshold(const struct ctl_table *table, int write,
else
memcpy(valp, val, sizeof(val));
}
+out:
mutex_unlock(&ipvs->sync_mutex);
return rc;
}
--
2.47.3
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH net 05/10] netfilter: flowtable: use the vlan id, not the full tci, in the tuple key
2026-10-19 15:02 [PATCH net 00/10] Netfilter/IPVS fixes for net Pablo Neira Ayuso
` (3 preceding siblings ...)
2026-10-19 15:02 ` [PATCH net 04/10] ipvs: skip committing sync_threshold when proc_dointvec fails Pablo Neira Ayuso
@ 2026-10-19 15:02 ` Pablo Neira Ayuso
2026-10-19 15:02 ` [PATCH net 06/10] netfilter: nf_reject: initialize IPCB at inet ingress Pablo Neira Ayuso
` (4 subsequent siblings)
9 siblings, 0 replies; 18+ messages in thread
From: Pablo Neira Ayuso @ 2026-10-19 15:02 UTC (permalink / raw)
To: netfilter-devel; +Cc: davem, netdev, kuba, pabeni, edumazet, horms, fw, ja
From: Shuangfeng He <huangya90@gmail.com>
nf_flow_tuple_encap() stores the skb vlan tag in the encap tuple id
using skb_vlan_tag_get(), which returns the unmasked TCI including
the 3 PCP priority bits. The flowtable entry, however, is programmed
by nf_dev_path_info() with vlan_dev_vlan_id(), a plain 12-bit VID.
When a NIC delivers RX vlan offload skbs with a non-zero priority
(an RTL9617C EPON ONU was observed handing over vid 818 skbs with
tci 0x6332, PCP 3), the runtime lookup key never matches the
programmed entry, so every packet misses the flow table and
software flow offload is silently defeated.
Mask both parse sites to the 12-bit VID so the lookup key matches
for any priority. The in-header variant has the same problem for
frames received with PCP != 0 on a non-offload path.
The egress side is unaffected: nf_flow_encap_push() restores tags
from the programmed tuple id, which never carried priority bits.
Measured on an RTL9617C ONU (PPPoE over VLAN 818 WAN, NATed 6-flow
download over a 1G EPON line), before -> after:
download throughput 377 Mbps -> 936 Mbps
CPU0 %soft at that rate 91.5% -> 70.9%
Fixes: 4cd91f7c290f ("netfilter: flowtable: add vlan support")
Signed-off-by: Shuangfeng He <huangya90@gmail.com>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
---
net/netfilter/nf_flow_table_ip.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/net/netfilter/nf_flow_table_ip.c b/net/netfilter/nf_flow_table_ip.c
index c8c29a9a1684..2dec15d402ca 100644
--- a/net/netfilter/nf_flow_table_ip.c
+++ b/net/netfilter/nf_flow_table_ip.c
@@ -170,14 +170,14 @@ static void nf_flow_tuple_encap(struct nf_flowtable_ctx *ctx,
int i = 0;
if (skb_vlan_tag_present(skb)) {
- tuple->encap[i].id = skb_vlan_tag_get(skb);
+ tuple->encap[i].id = skb_vlan_tag_get_id(skb);
tuple->encap[i].proto = skb->vlan_proto;
i++;
}
switch (skb->protocol) {
case htons(ETH_P_8021Q):
veth = (struct vlan_ethhdr *)skb_mac_header(skb);
- tuple->encap[i].id = ntohs(veth->h_vlan_TCI);
+ tuple->encap[i].id = ntohs(veth->h_vlan_TCI) & VLAN_VID_MASK;
tuple->encap[i].proto = skb->protocol;
offset += VLAN_HLEN;
break;
--
2.47.3
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH net 06/10] netfilter: nf_reject: initialize IPCB at inet ingress
2026-10-19 15:02 [PATCH net 00/10] Netfilter/IPVS fixes for net Pablo Neira Ayuso
` (4 preceding siblings ...)
2026-10-19 15:02 ` [PATCH net 05/10] netfilter: flowtable: use the vlan id, not the full tci, in the tuple key Pablo Neira Ayuso
@ 2026-10-19 15:02 ` Pablo Neira Ayuso
2026-10-20 15:37 ` netdev-bot+sashiko
2026-10-19 15:02 ` [PATCH net 07/10] netfilter: nft_set_rbtree: narrow down range completion to anonymous sets Pablo Neira Ayuso
` (3 subsequent siblings)
9 siblings, 1 reply; 18+ messages in thread
From: Pablo Neira Ayuso @ 2026-10-19 15:02 UTC (permalink / raw)
To: netfilter-devel; +Cc: davem, netdev, kuba, pabeni, edumazet, horms, fw, ja
From: Kyle Zeng <kylebot@openai.com>
The inet ingress hook runs before ip_rcv_core() and ip6_rcv_core(), so
neither IPCB nor IP6CB has been initialized when nft_reject_inet passes
a packet to nf_send_unreach() or nf_send_unreach6(). skb->cb can
therefore retain metadata from an earlier protocol layer.
For IPv4, icmp_send() treats IPCB(skb)->opt as parsed IPv4 options.
Stale option offsets can make __ip_options_echo() copy an
attacker-controlled length into its 40-byte reply option buffer and
overflow the stack. On an unpatched v7.2-rc5 kernel, the IPv4
reproducer produced:
BUG: KASAN: stack-out-of-bounds in __ip_options_echo()
Write of size 255
For IPv6, icmpv6_send() consumes IP6CB(skb) as parsed IPv6 metadata. It
always uses iif for source selection and, when MIP6 is enabled, uses
dsthao as an extension-header offset. These fields must not be stale.
For NF_INET_INGRESS, clear the address-family-specific control block and
restore its input interface before constructing the ICMP error. This
matches similar initialization normally performed by ip_rcv_core() and
ip6_rcv_core().
Fixes: 117ca1f8920c ("netfilter: nft_reject_inet: allow to use reject from inet ingress")
Cc: stable@vger.kernel.org
Assisted-by: Codex:gpt-5.6-sol Codex:gpt-5.5-cyber
Signed-off-by: Kyle Zeng <kylebot@openai.com>
Co-developed-by: David Lee <david.lee@trailofbits.com>
Signed-off-by: David Lee <david.lee@trailofbits.com>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
---
net/ipv4/netfilter/nf_reject_ipv4.c | 5 +++++
net/ipv6/netfilter/nf_reject_ipv6.c | 5 +++++
2 files changed, 10 insertions(+)
diff --git a/net/ipv4/netfilter/nf_reject_ipv4.c b/net/ipv4/netfilter/nf_reject_ipv4.c
index 59ec465a9df9..7cb33af671e2 100644
--- a/net/ipv4/netfilter/nf_reject_ipv4.c
+++ b/net/ipv4/netfilter/nf_reject_ipv4.c
@@ -357,6 +357,11 @@ void nf_send_unreach(struct sk_buff *skb_in, int code, int hook)
if (!skb_valid_dst(skb_in) && nf_reject_fill_skb_dst(skb_in) < 0)
return;
+ if (hook == NF_INET_INGRESS) {
+ memset(IPCB(skb_in), 0, sizeof(*IPCB(skb_in)));
+ IPCB(skb_in)->iif = skb_in->skb_iif;
+ }
+
if (skb_csum_unnecessary(skb_in) ||
!nf_reject_verify_csum(skb_in, dataoff, proto)) {
icmp_send(skb_in, ICMP_DEST_UNREACH, code, 0);
diff --git a/net/ipv6/netfilter/nf_reject_ipv6.c b/net/ipv6/netfilter/nf_reject_ipv6.c
index 07cdaa10da0d..5e08d8529cc8 100644
--- a/net/ipv6/netfilter/nf_reject_ipv6.c
+++ b/net/ipv6/netfilter/nf_reject_ipv6.c
@@ -444,6 +444,11 @@ void nf_send_unreach6(struct net *net, struct sk_buff *skb_in,
if (hooknum == NF_INET_LOCAL_OUT && skb_in->dev == NULL)
skb_in->dev = net->loopback_dev;
+ if (hooknum == NF_INET_INGRESS) {
+ memset(IP6CB(skb_in), 0, sizeof(*IP6CB(skb_in)));
+ IP6CB(skb_in)->iif = skb_in->skb_iif;
+ }
+
if (!skb_valid_dst(skb_in) && nf_reject6_fill_skb_dst(skb_in) < 0)
return;
--
2.47.3
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH net 07/10] netfilter: nft_set_rbtree: narrow down range completion to anonymous sets
2026-10-19 15:02 [PATCH net 00/10] Netfilter/IPVS fixes for net Pablo Neira Ayuso
` (5 preceding siblings ...)
2026-10-19 15:02 ` [PATCH net 06/10] netfilter: nf_reject: initialize IPCB at inet ingress Pablo Neira Ayuso
@ 2026-10-19 15:02 ` Pablo Neira Ayuso
2026-10-19 15:02 ` [PATCH net 08/10] netfilter: xt_dccp: handle reserved options as single-byte Pablo Neira Ayuso
` (2 subsequent siblings)
9 siblings, 0 replies; 18+ messages in thread
From: Pablo Neira Ayuso @ 2026-10-19 15:02 UTC (permalink / raw)
To: netfilter-devel; +Cc: davem, netdev, kuba, pabeni, edumazet, horms, fw, ja
For anonymous sets, when adjacent ranges are found, the end element is
not added to the set to pack the set representation. Use next start
element to complete this interval.
Jérémy Jean says:
Consider two ranges in a netfilter verdict map:
* keys 10-19 accept packets, and
* keys 30-39 jump to chain 'victim'.
Now, delete the end marker at 20. The lookup array now uses element 30
as the first range's end, so an INTERVAL_END query for key 15 returns
element 30, including its jump verdict and chain pointer. This can make
KASAN reports a UAF after the GC releases reference chain for 30 that
has expired:
BUG: KASAN: slab-use-after-free in nft_verdict_dump+0xdd/0x110
Read of size 8 at addr ff110000009dfb58 by task repro/50
...
Call Trace:
...
nft_verdict_dump+0xdd/0x110
nft_data_dump+0x4d/0x80
nf_tables_fill_setelem+0x30b/0x8a0
? __asan_memcpy+0x3c/0x60
nf_tables_fill_setelem_info+0x1b0/0x200
nft_get_set_elem+0x3fd/0x4b0
...
Freed by task 24:
...
kfree+0x1a5/0x280
nf_tables_trans_destroy_work+0x2cb/0x520
Fixes: 2aa34191f06f ("netfilter: nft_set_rbtree: use binary search array in get command")
Reported-by: Jérémy Jean <Jeremy.Jean@oss.cyber.gouv.fr>
Link: https://patch.msgid.link/netfilter-devel/20261005200404.3544062-2-Jeremy.Jean@oss.cyber.gouv.fr/
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
---
net/netfilter/nft_set_rbtree.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/net/netfilter/nft_set_rbtree.c b/net/netfilter/nft_set_rbtree.c
index 12431b55752f..fbe1750f1ca2 100644
--- a/net/netfilter/nft_set_rbtree.c
+++ b/net/netfilter/nft_set_rbtree.c
@@ -1091,7 +1091,8 @@ static void nft_rbtree_commit(struct nft_set *set)
* representation. Use next start element to complete this
* interval.
*/
- if (nft_rbtree_interval_start(rbe) &&
+ if (nft_set_is_anonymous(set) &&
+ nft_rbtree_interval_start(rbe) &&
nft_rbtree_interval_start(prev_rbe) &&
priv->array_next->intervals[num_intervals].from)
priv->array_next->intervals[num_intervals++].to = &prev_rbe->ext;
--
2.47.3
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH net 08/10] netfilter: xt_dccp: handle reserved options as single-byte
2026-10-19 15:02 [PATCH net 00/10] Netfilter/IPVS fixes for net Pablo Neira Ayuso
` (6 preceding siblings ...)
2026-10-19 15:02 ` [PATCH net 07/10] netfilter: nft_set_rbtree: narrow down range completion to anonymous sets Pablo Neira Ayuso
@ 2026-10-19 15:02 ` Pablo Neira Ayuso
2026-10-20 15:38 ` netdev-bot+sashiko
2026-10-19 15:02 ` [PATCH net 09/10] netfilter: flowtable: reset transport header after nf_flow_encap_pop() Pablo Neira Ayuso
2026-10-19 15:02 ` [PATCH net 10/10] netfilter: nf_conntrack_netlink: fix inverted IPv6 tuple filter Pablo Neira Ayuso
9 siblings, 1 reply; 18+ messages in thread
From: Pablo Neira Ayuso @ 2026-10-19 15:02 UTC (permalink / raw)
To: netfilter-devel; +Cc: davem, netdev, kuba, pabeni, edumazet, horms, fw, ja
From: Sung Byeongchan <tjdqudcks0424@naver.com>
The DCCP UAPI defines option numbers 3 through 31 as reserved. Like
Padding and Mandatory, these options occupy one byte. dccp_find_option()
only treats values below 2 as single-byte and interprets the byte after a
reserved option as a TLV length.
This can skip a later option and invert an XT_DCCP_OPTION rule result. A
local production-hook fixture installed the same DROP rule for IPv4 and
IPv6. In three fresh boots per kernel, a target-only packet was dropped
20/20 times, while adding reserved option 3 before the same target let
20/20 packets reach the protected receiver. The fixed kernel dropped all
trigger packets in three fresh boots and preserved the normal controls.
Treat every option up to DCCPO_MAX_RESERVED as single-byte, matching the
protocol definition. Triggering requires
CONFIG_NETFILTER_XTABLES_LEGACY, CONFIG_NETFILTER_XT_MATCH_DCCP, an
option-based rule, and remote protocol-33 reachability. The touched source
is unchanged at 0c2669a9f4a1d607e7591ae50ccf3c432a0aff08, where the patch
applies and builds cleanly.
Fixes: 1d3de414eb20 ("[NETFILTER]: New iptables DCCP protocol header match")
Assisted-by: LLM
Signed-off-by: Sung Byeongchan <tjdqudcks0424@naver.com>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
---
net/netfilter/xt_dccp.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/net/netfilter/xt_dccp.c b/net/netfilter/xt_dccp.c
index 3db81e041af9..23a4e3e8df7d 100644
--- a/net/netfilter/xt_dccp.c
+++ b/net/netfilter/xt_dccp.c
@@ -62,7 +62,7 @@ dccp_find_option(u_int8_t option,
return true;
}
- if (op[i] < 2 || i == optlen - 1)
+ if (op[i] <= DCCPO_MAX_RESERVED || i == optlen - 1)
i++;
else
i += op[i + 1] ? : 1;
--
2.47.3
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH net 09/10] netfilter: flowtable: reset transport header after nf_flow_encap_pop()
2026-10-19 15:02 [PATCH net 00/10] Netfilter/IPVS fixes for net Pablo Neira Ayuso
` (7 preceding siblings ...)
2026-10-19 15:02 ` [PATCH net 08/10] netfilter: xt_dccp: handle reserved options as single-byte Pablo Neira Ayuso
@ 2026-10-19 15:02 ` Pablo Neira Ayuso
2026-10-19 15:02 ` [PATCH net 10/10] netfilter: nf_conntrack_netlink: fix inverted IPv6 tuple filter Pablo Neira Ayuso
9 siblings, 0 replies; 18+ messages in thread
From: Pablo Neira Ayuso @ 2026-10-19 15:02 UTC (permalink / raw)
To: netfilter-devel; +Cc: davem, netdev, kuba, pabeni, edumazet, horms, fw, ja
From: Lorenzo Bianconi <lorenzo.bianconi@oss.qualcomm.com>
nf_flow_encap_pop() pulls vlan/pppoe or ip tunnel headers and resets the
network header, but leaves the transport header pointing into the removed
outer header. Reset the transport header to the inner L4 header, otherwise
later consumers (e.g. GSO segmentation) parse a bogus header.
Fixes: 4cd91f7c290f ("netfilter: flowtable: add vlan support")
Signed-off-by: Lorenzo Bianconi <lorenzo.bianconi@oss.qualcomm.com>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
---
net/netfilter/nf_flow_table_ip.c | 2 ++
1 file changed, 2 insertions(+)
diff --git a/net/netfilter/nf_flow_table_ip.c b/net/netfilter/nf_flow_table_ip.c
index 2dec15d402ca..bb894c4f2b4a 100644
--- a/net/netfilter/nf_flow_table_ip.c
+++ b/net/netfilter/nf_flow_table_ip.c
@@ -502,6 +502,7 @@ static int nf_flow_offload_forward(struct nf_flowtable_ctx *ctx,
nf_flow_encap_pop(ctx, skb, tuplehash);
thoff -= ctx->offset;
+ skb_set_transport_header(skb, thoff);
iph = ip_hdr(skb);
nf_flow_nat_ip(flow, skb, thoff, dir, iph);
@@ -1097,6 +1098,7 @@ static int nf_flow_offload_ipv6_forward(struct nf_flowtable_ctx *ctx,
flow_offload_refresh(flow_table, flow, false);
nf_flow_encap_pop(ctx, skb, tuplehash);
+ skb_set_transport_header(skb, sizeof(*ip6h));
ip6h = ipv6_hdr(skb);
nf_flow_nat_ipv6(flow, skb, dir, ip6h);
--
2.47.3
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH net 10/10] netfilter: nf_conntrack_netlink: fix inverted IPv6 tuple filter
2026-10-19 15:02 [PATCH net 00/10] Netfilter/IPVS fixes for net Pablo Neira Ayuso
` (8 preceding siblings ...)
2026-10-19 15:02 ` [PATCH net 09/10] netfilter: flowtable: reset transport header after nf_flow_encap_pop() Pablo Neira Ayuso
@ 2026-10-19 15:02 ` Pablo Neira Ayuso
2026-10-20 15:38 ` netdev-bot+sashiko
9 siblings, 1 reply; 18+ messages in thread
From: Pablo Neira Ayuso @ 2026-10-19 15:02 UTC (permalink / raw)
To: netfilter-devel; +Cc: davem, netdev, kuba, pabeni, edumazet, horms, fw, ja
From: Liu Chao <liuc63@xiaopeng.com>
ctnetlink_filter_match_tuple() returns 0 (no match) when the IPv6
source or destination address differs from the filter, because the
!ipv6_addr_cmp() test is inverted. ipv6_addr_cmp() returns 0 when
the addresses are equal, so the negation makes the branch trigger on
a match instead of a mismatch, which is the opposite of the IPv4
path above.
Drop the '!' so the IPv6 branch returns 0 only when the addresses
differ, matching the IPv4 semantics and the caller's expectation.
Fixes: cb8aa9a3affb ("netfilter: ctnetlink: add kernel side filtering for dump")
Cc: stable@vger.kernel.org
Signed-off-by: Liu Chao <liuc63@xiaopeng.com>
Reviewed-by: Weibin Liu <liuwb@xiaopeng.com>
Reviewed-by: Florian Westphal <fw@strlen.de>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
---
net/netfilter/nf_conntrack_netlink.c | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/net/netfilter/nf_conntrack_netlink.c b/net/netfilter/nf_conntrack_netlink.c
index 4e5d7c701436..cc8695d5356d 100644
--- a/net/netfilter/nf_conntrack_netlink.c
+++ b/net/netfilter/nf_conntrack_netlink.c
@@ -1098,13 +1098,13 @@ static int ctnetlink_filter_match_tuple(struct nf_conntrack_tuple *filter_tuple,
break;
case NFPROTO_IPV6:
if ((flags & CTA_FILTER_FLAG(CTA_IP_SRC)) &&
- !ipv6_addr_cmp(&filter_tuple->src.u3.in6,
- &ct_tuple->src.u3.in6))
+ ipv6_addr_cmp(&filter_tuple->src.u3.in6,
+ &ct_tuple->src.u3.in6))
return 0;
if ((flags & CTA_FILTER_FLAG(CTA_IP_DST)) &&
- !ipv6_addr_cmp(&filter_tuple->dst.u3.in6,
- &ct_tuple->dst.u3.in6))
+ ipv6_addr_cmp(&filter_tuple->dst.u3.in6,
+ &ct_tuple->dst.u3.in6))
return 0;
break;
}
--
2.47.3
^ permalink raw reply related [flat|nested] 18+ messages in thread
* Re: [PATCH net 01/10] netfilter: nf_conntrack: avoid truncating IPv6 nexthdr offset
2026-10-19 15:02 ` [PATCH net 01/10] netfilter: nf_conntrack: avoid truncating IPv6 nexthdr offset Pablo Neira Ayuso
@ 2026-10-19 15:55 ` netdev-bot+sinfo
0 siblings, 0 replies; 18+ messages in thread
From: netdev-bot+sinfo @ 2026-10-19 15:55 UTC (permalink / raw)
To: Pablo Neira Ayuso
Cc: netfilter-devel, davem, netdev, kuba, pabeni, edumazet, horms, fw,
ja
Hi!
This is an automated message. This series looks like a fix, but its
commit messages seem to be missing some information:
- How the issue was discovered, e.g. hit in production, hit during
development, syzbot report, manual code inspection, LLM or static
analysis tool scan.
- Whether the issue was actually triggered, or is only theoretical
(e.g. found by code inspection). If it was triggered please include
the symptoms, like the stack trace or error messages.
Please do not repost the series just to address the above. Instead,
reply to this email with the missing information, so that reviewers
can take it into account. If the series needs another revision for
other reasons, please include the information in the commit messages
then.
The evaluation is done by an LLM so it may be wrong, if you think
that is the case please reply and explain.
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH net 02/10] netfilter: nft_payload: restrict checksum offsets to known values
2026-10-19 15:02 ` [PATCH net 02/10] netfilter: nft_payload: restrict checksum offsets to known values Pablo Neira Ayuso
@ 2026-10-20 15:36 ` netdev-bot+sashiko
0 siblings, 0 replies; 18+ messages in thread
From: netdev-bot+sashiko @ 2026-10-20 15:36 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: 0 · Medium: 1 · Low: 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).
- [Medium] Undisclosed regression: the new per-protocol allow-list in
nft_payload_csum_th_write_ok() (net/netfilter/nft_payload.c:1053-1072)…
- [Low] Inaccurate comment: the patch changed the comment for
NFT_PAYLOAD_INNER_HEADER in nft_payload_csum_write_ok()…
--
netfilter: nft_payload: restrict checksum offsets to known values
This commit restricts the L4 checksum offset used by nft_payload set
expressions with NFT_PAYLOAD_CSUM_INET to the TCP, UDP, ICMP and ICMPv6
checksum locations. The check runs at rule load in
nft_payload_validate_inet_csum_offset() and per protocol at run time in
the new nft_payload_csum_th_write_ok(). The aim is to close a side channel
where a checksum delta could corrupt fields such as tcp->doff after
conntrack has validated the headers.
> diff --git a/net/netfilter/nft_payload.c b/net/netfilter/nft_payload.c
> index e315d35f73d4..5b76af68b704 100644
> --- a/net/netfilter/nft_payload.c
> +++ b/net/netfilter/nft_payload.c
[ ... ]
> @@ -1008,11 +1009,14 @@ static bool nft_payload_validate_inet_csum_offset(const struct nft_ctx *ctx,
> if (priv->csum_flags) /* makes no sense, asks for "re-update" of L4 checksum */
> return false;
>
> - /* no further check here; offset can't be negative so bogus
> - * offsets can corrupt L4 or payload but not l3 headers.
> - * We already allow arbitrary l4/inner payload writes.
> - */
> - return true;
> + /* Validate csum_offset is one of the supported transport header checksums */
> + if (priv->csum_offset == offsetof(struct tcphdr, check) ||
> + priv->csum_offset == offsetof(struct udphdr, check) ||
> + priv->csum_offset == offsetof(struct icmphdr, checksum) ||
> + priv->csum_offset == offsetof(struct icmp6hdr, icmp6_cksum))
> + return true;
> +
> + return false;
> case NFT_PAYLOAD_INNER_HEADER:
> return true;
> case NFT_PAYLOAD_TUN_HEADER:
> @@ -1046,6 +1050,27 @@ static bool nft_payload_csum_nh_write_ok(const struct nft_payload_set *priv,
> return false;
> }
>
> +static bool nft_payload_csum_th_write_ok(const struct nft_payload_set *priv,
> + const struct nft_pktinfo *pkt)
> +{
> + if (!(pkt->flags & NFT_PKTINFO_L4PROTO))
> + return false;
> +
> + switch (pkt->tprot) {
> + case IPPROTO_TCP:
> + return priv->csum_offset == offsetof(struct tcphdr, check);
> + case IPPROTO_UDP:
> + case IPPROTO_UDPLITE:
> + return priv->csum_offset == offsetof(struct udphdr, check);
> + case IPPROTO_ICMPV6:
> + return priv->csum_offset == offsetof(struct icmp6hdr, icmp6_cksum);
> + case IPPROTO_ICMP:
> + return priv->csum_offset == offsetof(struct icmphdr, checksum);
> + }
> +
> + return false;
> +}
[Severity: Medium]
Does this turn off the checksum fixup for every transport protocol except
TCP, UDP/UDPLITE, ICMP and ICMPv6, while still doing the payload write?
Before this patch, NFT_PAYLOAD_TRANSPORT_HEADER fell through to the
INNER_HEADER case and returned true. Now an IGMP packet (csum at offset 2)
or a DCCP packet (dccph_checksum at offset 6) hits the final return false
here.
Those rules still load, because the check in
nft_payload_validate_inet_csum_offset() does not know the protocol and
offsets 2 and 6 are both in its allowed set.
At run time nft_th_write_ok() lets the write through because tprot is not
IPPROTO_TCP. nft_payload_set_eval() then does:
if (priv->csum_type == NFT_PAYLOAD_CSUM_INET &&
nft_payload_csum_write_ok(pkt, priv) &&
nft_payload_csum_inet(skb, src, fsum, tsum, csum_offset))
goto err;
...
if (skb_ensure_writable(skb, max(offset + priv->len, 0)) ||
skb_store_bits(skb, offset, src, priv->len) < 0)
goto err;
When nft_payload_csum_write_ok() returns false, only the checksum update is
skipped. The header bytes are still rewritten, so the packet leaves with a
stale checksum. nftables appears to emit CSUM_INET with offset 2 for
"igmp ... set" rules.
There is also a load-time change. A TRANSPORT_HEADER rule with any other
csum_offset, such as GRE's checksum at offset 4, used to load and now fails
with -EINVAL.
The commit message does not mention either change, and the Fixes tag means
the patch will go to stable.
nft_th_write_ok() already allows arbitrary direct writes to every non-TCP
transport header:
if (pkt->tprot != IPPROTO_TCP)
return true;
Would it be enough to pin csum_offset to offsetof(struct tcphdr, check)
only when tprot is IPPROTO_TCP? Another option is to return NFT_BREAK when
the fixup is refused, so the write is not stored without a checksum update.
> +
> static bool nft_payload_csum_write_ok(const struct nft_pktinfo *pkt,
> const struct nft_payload_set *priv)
> {
> @@ -1055,9 +1080,10 @@ static bool nft_payload_csum_write_ok(const struct nft_pktinfo *pkt,
> case NFT_PAYLOAD_NETWORK_HEADER:
> return nft_payload_csum_nh_write_ok(priv, pkt);
> case NFT_PAYLOAD_TRANSPORT_HEADER:
> + return nft_payload_csum_th_write_ok(priv, pkt);
> case NFT_PAYLOAD_INNER_HEADER:
> - /* neither offsets are validated, offsets cannot be
> - * negative so real l3 headers cannot be mangled.
> + /* offset is not validated, offset cannot be
> + * negative so real l3/l4 headers cannot be mangled.
> */
[Severity: Low]
Is the added "l4" in this comment accurate? For TCP,
__nft_payload_inner_offset() computes:
pkt->inneroff = thoff + __tcp_hdrlen(th);
__tcp_hdrlen() returns th->doff * 4 and nothing checks that doff >= 5. On
a malformed TCP packet with doff < 5, inneroff falls inside the fixed
20-byte TCP header. An unvalidated csum_offset can then reach real L4
fields, including doff.
For IPPROTO_IPIP, inneroff is set to thoff, so the inner base is the same
as the transport header base.
Conntrack's tcp_error() already treats such packets as invalid, and
INNER_HEADER writes are unrestricted anyway, so behaviour does not change.
The comment does claim more than the code guarantees, though.
Could it keep the original "l3" wording, or say that it assumes a
well-formed packet?
> return true;
> case NFT_PAYLOAD_TUN_HEADER:
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261019150249.1879516-2-pablo%40netfilter.org
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH net 03/10] ipvs: fix more races around the overload flag
2026-10-19 15:02 ` [PATCH net 03/10] ipvs: fix more races around the overload flag Pablo Neira Ayuso
@ 2026-10-20 15:36 ` netdev-bot+sashiko
0 siblings, 0 replies; 18+ messages in thread
From: netdev-bot+sashiko @ 2026-10-20 15:36 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 3 potential
issue(s) to consider.
Critical: 0 · High: 1 · Medium: 0 (1) · Low: 2
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] A threshold edit that moves a dest into the overloaded state can
race with the unbind of its last connection, and the clear is lost.
- [Low] A new dest created with no thresholds (u_threshold == 0 and
l_threshold == 0) is published with u_threshold_val == 0 instead of…
- [Low] The commit message and the comments do not match what the code
now does.
Pre-existing issues:
- [Medium] This problem wasn't introduced by this patch, but on a cache
hit ip_vs_lblc_schedule() (net/netfilter/ipvs/ip_vs_lblc.c:509-511)…
--
ipvs: fix more races around the overload flag
This commit replaces the IP_VS_DEST_F_OVERLOAD flag with a two-state
scheme built on l_threshold_val and u_threshold_val. With it,
ip_vs_bind_dest() and ip_vs_unbind_dest() can change the overload state
at any count beyond the thresholds, not only at the exact threshold
values. All schedulers now use the new ip_vs_dest_is_overloaded() helper,
and struct ip_vs_dest is reordered to keep the hot fields in the first
cacheline.
Link: https://sashiko.dev/#/patchset/20260810190621.894119-1-pablo%40netfilter.org
> diff --git a/include/net/ip_vs.h b/include/net/ip_vs.h
> index 32fde731bceb..9a83bff32569 100644
> --- a/include/net/ip_vs.h
> +++ b/include/net/ip_vs.h
[ ... ]
> @@ -973,32 +970,43 @@ struct ip_vs_dest_dst {
> * and so on.
> */
> struct ip_vs_dest {
[ ... ]
> + /* connection thresholds */
> + u32 l_threshold_val;/* used lower threshold */
> + u32 u_threshold_val;/* used upper threshold */
[Severity: Low]
This isn't a bug, but the commit message says:
To make it work without entering the locked region for
every connection, we introduce l_threshold_val.
l_threshold_val already existed. It was added by 8f843441c4e7, and this
hunk removes the old __u32 l_threshold_val declaration further down. The
only new field is u_threshold_val. What changed is the meaning: a
non-zero l_threshold_val now means overloaded, which is what
ip_vs_dest_is_overloaded() returns.
The field comments here still say "used lower threshold" and "used upper
threshold". But l_threshold_val now also encodes the overload state, and
u_threshold_val holds INT_MAX while the dest is overloaded.
The comment above __ip_vs_dest_update_overload() in ip_vs_ctl.c also
still describes setting and clearing the flag that this patch removes:
* - conns reach u_threshold and exceed it: set the flag
* - conns go below l_threshold (or 75% of u_threshold): clear the flag
With overload == true, the state is now kept once conns >= l, not when
conns reach u_threshold.
The new bool overload argument is not documented, and callers use it in
two ways. __ip_vs_update_dest() passes the current state, while
ip_vs_bind_dest() and ip_vs_unbind_dest() pass the direction they want.
Could the commit message and these comments be updated? The Fixes:
target is marked for stable, so this patch will likely be backported
along with it.
[ ... ]
> diff --git a/net/netfilter/ipvs/ip_vs_ctl.c b/net/netfilter/ipvs/ip_vs_ctl.c
> index 4c1c739446b7..3003547dcabe 100644
> --- a/net/netfilter/ipvs/ip_vs_ctl.c
> +++ b/net/netfilter/ipvs/ip_vs_ctl.c
> @@ -1309,32 +1309,32 @@ void ip_vs_stats_free(struct ip_vs_stats *stats)
> * - conns reach u_threshold and exceed it: set the flag
> * - conns go below l_threshold (or 75% of u_threshold): clear the flag
> */
> -static void __ip_vs_dest_update_overload(struct ip_vs_dest *dest, int mode)
> +static void __ip_vs_dest_update_overload(struct ip_vs_dest *dest, bool overload)
> {
> int conns;
> u32 l, u;
>
> lockdep_assert_held(&dest->dst_lock);
> u = READ_ONCE(dest->u_threshold);
> - if (!u)
> - goto unset;
> - l = READ_ONCE(dest->l_threshold_val);
> - conns = atomic_read(&dest->totalconns);
> - if (conns >= (mode > 0 ? l : u)) {
> - dest->flags |= IP_VS_DEST_F_OVERLOAD;
> - return;
> + if (u) {
> + /* Low threshold defaults to 75% of upper threshold */
> + l = READ_ONCE(dest->l_threshold) ? : (u - (u >> 2));
> + conns = atomic_read(&dest->totalconns);
> + if (conns >= (overload ? l : u)) {
> + WRITE_ONCE(dest->u_threshold_val, INT_MAX);
> + WRITE_ONCE(dest->l_threshold_val, l);
> + return;
> + }
[Severity: High]
Can a threshold edit race with the unbind of the last connection here,
and leave the dest overloaded with zero connections?
totalconns is read before l_threshold_val is published, and it is not
read again afterwards. Meanwhile ip_vs_unbind_dest() reads
l_threshold_val without dst_lock:
net/netfilter/ipvs/ip_vs_conn.c:ip_vs_unbind_dest() {
...
tc = atomic_fetch_dec(&dest->totalconns);
if (tc <= READ_ONCE(dest->l_threshold_val))
ip_vs_dest_update_overload(dest, false);
...
}
Take a dest with one bound connection. The admin lowers u_threshold to 1
(so l defaults to 1) while that connection expires:
CPU0 (ip_vs_edit_dest) CPU1 (ip_vs_conn_expire)
__ip_vs_update_dest()
__ip_vs_dest_update_overload()
conns = 1, decides overloaded
ip_vs_unbind_dest()
tc = 1, totalconns is now 0
reads l_threshold_val == 0
tc <= 0 is false, skips update
WRITE_ONCE(dest->u_threshold_val, INT_MAX);
WRITE_ONCE(dest->l_threshold_val, l);
After this, ip_vs_dest_is_overloaded() is true while totalconns is 0.
Every scheduler skips the dest, so no new binds happen, and with no
connections left there are no unbinds to clear the state. The bind gate
is also closed, since u_threshold_val == INT_MAX.
Repeating the edit with the same thresholds does not help.
__ip_vs_update_dest() only calls __ip_vs_dest_update_overload() when
u_threshold or l_threshold actually change. The dest stays out of
service until the thresholds change again, or until a template or sync
conn happens to bind and unbind on it.
The old code in __ip_vs_update_dest() wrote l_threshold_val before
totalconns was sampled. The same interleaving then made the unbind take
the lock. This patch moves the gate write after the read.
This looks like the case the commit message describes as lockless
readers that "may use old threshold values and as result to skip the
required flag change". Transitions started from ip_vs_bind_dest() seem
safe, because the connection being bound will unbind later. The admin
path has no such guarantee.
Would it work to publish l_threshold_val and u_threshold_val first, then
smp_mb(), then re-read totalconns and revert if it is below l? That
would pair with the fully ordered atomic_fetch_dec() in
ip_vs_unbind_dest().
[ ... ]
> @@ -1406,15 +1406,13 @@ __ip_vs_update_dest(struct ip_vs_service *svc, struct ip_vs_dest *dest,
>
> if (READ_ONCE(dest->u_threshold) != udest->u_threshold ||
> READ_ONCE(dest->l_threshold) != udest->l_threshold) {
> + bool overload;
> +
> spin_lock_bh(&dest->dst_lock);
> WRITE_ONCE(dest->u_threshold, udest->u_threshold);
> WRITE_ONCE(dest->l_threshold, udest->l_threshold);
> - /* Low threshold defaults to 75% of upper threshold */
> - WRITE_ONCE(dest->l_threshold_val,
> - udest->l_threshold ? :
> - (udest->u_threshold -
> - (udest->u_threshold >> 2)));
> - __ip_vs_dest_update_overload(dest, 0);
> + overload = ip_vs_dest_is_overloaded(dest);
> + __ip_vs_dest_update_overload(dest, overload);
> spin_unlock_bh(&dest->dst_lock);
> }
[Severity: Low]
Is u_threshold_val ever set to INT_MAX for a new dest that has no
thresholds?
ip_vs_new_dest() allocates the dest with kzalloc_obj(), and this block
is the only place that sets u_threshold_val. If ipvsadm -a is run
without -x/-y, udest->u_threshold and udest->l_threshold are both 0. The
condition above is then false, and list_add_rcu() publishes the dest
with u_threshold_val still 0.
So the fast-path check in ip_vs_bind_dest():
if (tc >= READ_ONCE(dest->u_threshold_val))
ip_vs_dest_update_overload(dest, true);
is true for the first connections. Each of them takes dst_lock until
one writes u_threshold_val = INT_MAX. The overload state stays correct
because l_threshold_val stays 0. Before this patch, the
tc == u_threshold check never fired when u_threshold was 0.
Could ip_vs_new_dest() set u_threshold_val to INT_MAX? Or could the
update always run when add is set?
[ ... ]
> diff --git a/net/netfilter/ipvs/ip_vs_lblc.c b/net/netfilter/ipvs/ip_vs_lblc.c
> index 55af77a56929..e1111c3b2721 100644
> --- a/net/netfilter/ipvs/ip_vs_lblc.c
> +++ b/net/netfilter/ipvs/ip_vs_lblc.c
> @@ -418,7 +418,7 @@ __ip_vs_lblc_schedule(struct ip_vs_service *svc)
> * new connection.
> */
> list_for_each_entry_rcu(dest, &svc->destinations, n_list) {
> - if (dest->flags & IP_VS_DEST_F_OVERLOAD)
> + if (ip_vs_dest_is_overloaded(dest))
> continue;
> if (atomic_read(&dest->weight) > 0) {
> least = dest;
[Severity: Medium]
This isn't a bug introduced by this patch, but should the cache hit path
in ip_vs_lblc_schedule() also check ip_vs_dest_is_overloaded()?
Only the fallback __ip_vs_lblc_schedule() checks the threshold-based
state. On a cache hit, the cached dest is returned based on the local
is_overloaded() heuristic, which only compares activeconns with weight:
net/netfilter/ipvs/ip_vs_lblc.c:ip_vs_lblc_schedule() {
...
dest = en->dest;
if ((dest->cflags & IP_VS_DEST_CF_AVAILABLE) &&
atomic_read(&dest->weight) > 0 && !is_overloaded(dest, svc))
goto out;
...
}
So a dest that has reached its u_threshold keeps getting new connections
for cached destination addresses, as long as activeconns <= weight.
The LBLCR cache path goes through ip_vs_dest_set_min(), which does check
the overload state. The same LBLC cache hit condition is in the base
tree.
[ ... ]
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261019150249.1879516-2-pablo%40netfilter.org
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH net 04/10] ipvs: skip committing sync_threshold when proc_dointvec fails
2026-10-19 15:02 ` [PATCH net 04/10] ipvs: skip committing sync_threshold when proc_dointvec fails Pablo Neira Ayuso
@ 2026-10-20 15:37 ` netdev-bot+sashiko
0 siblings, 0 replies; 18+ messages in thread
From: netdev-bot+sashiko @ 2026-10-20 15:37 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] The commit fixes a real bug that users can see, but its message
has no Fixes: tag and no Cc: stable.
Pre-existing issues:
- [Low] Documentation/networking/ipvs-sysctl.rst (sync_threshold entry,
around line 311) says "The range of the threshold is from 0 to…
- [Low] proc_do_sync_threshold() (net/netfilter/ipvs/ip_vs_ctl.c:2462)
updates both elements of ipvs->sysctl_sync_threshold[] with a plain…
--
ipvs: skip committing sync_threshold when proc_dointvec fails
With this patch, proc_do_sync_threshold() returns early when
proc_dointvec() fails. A partly parsed write such as "5 x" now leaves
ipvs->sysctl_sync_threshold[] unchanged. Before, the handler updated
element [0] and still returned -EINVAL.
> diff --git a/net/netfilter/ipvs/ip_vs_ctl.c b/net/netfilter/ipvs/ip_vs_ctl.c
> index 3003547dcabe..ce9fe20d6106 100644
> --- a/net/netfilter/ipvs/ip_vs_ctl.c
> +++ b/net/netfilter/ipvs/ip_vs_ctl.c
> @@ -2452,6 +2452,8 @@ proc_do_sync_threshold(const struct ctl_table *table, int write,
> mutex_lock(&ipvs->sync_mutex);
> memcpy(val, valp, sizeof(val));
> rc = proc_dointvec(&tmp, write, buffer, lenp, ppos);
> + if (rc)
> + goto out;
[Severity: Low]
This fixes a bug that users can see. Should the commit message have a
Fixes: tag, and maybe Cc: stable?
Before this change, writing "5 x" or "5 99999999999" to
/proc/sys/net/ipv4/vs/sync_threshold took this path:
proc_do_sync_threshold()
proc_dointvec()
do_proc_vec()
stores 5 into val[0], then fails on the second element
returns -EINVAL
The old handler then checked the mixed pair (5, old val[1]). If that
pair was valid, it copied it into ipvs->sysctl_sync_threshold[] and
still returned -EINVAL.
The tmp-copy form of the handler comes from commit 1b90af292e71 ("ipvs:
Improve robustness to the ipvs sysctl"), which kept this partial update.
The older direct-write version behaved the same way.
Every other patch in this series has a Fixes: tag, so stable tooling may
not pick this one up. Would a tag like this one fit?
Fixes: 1b90af292e71 ("ipvs: Improve robustness to the ipvs sysctl")
> if (write) {
> if (val[0] < 0 || val[1] < 0 ||
> (val[0] >= val[1] && val[1]))
[Severity: Low]
This isn't a bug introduced by this patch, but the entry for this sysctl
in Documentation/networking/ipvs-sysctl.rst says:
The range of the threshold is from 0 to sync_period.
This check rejects val[0] >= val[1] whenever val[1] is non-zero. So the
upper bound is exclusive, and writing "50 50" fails with -EINVAL.
Could the documentation say that the threshold must be less than
sync_period when sync_period is non-zero? The paragraph after it already
covers the sync_period == 0 case.
> @@ -2459,6 +2461,7 @@ proc_do_sync_threshold(const struct ctl_table *table, int write,
> else
> memcpy(valp, val, sizeof(val));
[Severity: Low]
This is a pre-existing issue, but this memcpy() updates both elements of
ipvs->sysctl_sync_threshold[] while holding ipvs->sync_mutex, and the
packet-path readers never take that mutex:
include/net/ip_vs.h:
static inline int sysctl_sync_threshold(struct netns_ipvs *ipvs)
{
return ipvs->sysctl_sync_threshold[0];
}
static inline int sysctl_sync_period(struct netns_ipvs *ipvs)
{
return READ_ONCE(ipvs->sysctl_sync_threshold[1]);
}
net/netfilter/ipvs/ip_vs_sync.c:ip_vs_sync_conn_needed() {
...
sync_period = sysctl_sync_period(ipvs);
if (sync_period > 0) {
if (!(cp->flags & IP_VS_CONN_F_TEMPLATE) &&
pkts % sync_period != sysctl_sync_threshold(ipvs))
return 0;
...
}
Element [0] is read with a plain load, element [1] with READ_ONCE(), and
the two are loaded separately.
Can a concurrent sysctl write let ip_vs_sync_conn_needed() see the new
threshold with the old period, or the other way round? The
threshold < period rule would then not hold for that packet, and the
connection could miss one sync.
It can't crash, because sync_period is loaded once and checked before
the modulo. KCSAN would probably flag the unannotated plain load and
store of element [0], though.
Would it be worth using WRITE_ONCE() for each element here and
READ_ONCE() in sysctl_sync_threshold()?
[ ... ]
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261019150249.1879516-2-pablo%40netfilter.org
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH net 06/10] netfilter: nf_reject: initialize IPCB at inet ingress
2026-10-19 15:02 ` [PATCH net 06/10] netfilter: nf_reject: initialize IPCB at inet ingress Pablo Neira Ayuso
@ 2026-10-20 15:37 ` netdev-bot+sashiko
0 siblings, 0 replies; 18+ messages in thread
From: netdev-bot+sashiko @ 2026-10-20 15:37 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 · 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).
- [Medium] The new IPv6 initializer in nf_send_unreach6() sets
IP6CB(skb_in)->iif = skb_in->skb_iif unconditionally.
Pre-existing issues:
- [Low] At NF_INET_INGRESS, the reject helpers do not check the L4
checksum of CHECKSUM_COMPLETE packets.
--
netfilter: nf_reject: initialize IPCB at inet ingress
The inet ingress hook runs before ip_rcv_core() and ip6_rcv_core(), so
skb->cb can still hold stale data from an earlier layer. When called from
NF_INET_INGRESS, nf_send_unreach() and nf_send_unreach6() now clear
IPCB/IP6CB and restore iif. This stops stale IPv4 option offsets from
overflowing the stack buffer in __ip_options_echo().
> diff --git a/net/ipv4/netfilter/nf_reject_ipv4.c b/net/ipv4/netfilter/nf_reject_ipv4.c
> index 59ec465a9df9..7cb33af671e2 100644
> --- a/net/ipv4/netfilter/nf_reject_ipv4.c
> +++ b/net/ipv4/netfilter/nf_reject_ipv4.c
> @@ -357,6 +357,11 @@ void nf_send_unreach(struct sk_buff *skb_in, int code, int hook)
> if (!skb_valid_dst(skb_in) && nf_reject_fill_skb_dst(skb_in) < 0)
> return;
>
> + if (hook == NF_INET_INGRESS) {
> + memset(IPCB(skb_in), 0, sizeof(*IPCB(skb_in)));
> + IPCB(skb_in)->iif = skb_in->skb_iif;
> + }
> +
> if (skb_csum_unnecessary(skb_in) ||
> !nf_reject_verify_csum(skb_in, dataoff, proto)) {
> icmp_send(skb_in, ICMP_DEST_UNREACH, code, 0);
[Severity: Low]
This is a pre-existing issue and was not introduced by this patch. Since
this path now has NF_INET_INGRESS handling, though: is the L4 checksum of
a CHECKSUM_COMPLETE packet ever checked at this hook?
Further down, nf_send_unreach() does:
if (nf_ip_checksum(skb_in, hook, dataoff, proto) == 0)
icmp_send(skb_in, ICMP_DEST_UNREACH, code, 0);
nf_ip_checksum() in net/netfilter/utils.c only verifies for PRE_ROUTING
and LOCAL_IN:
case CHECKSUM_COMPLETE:
if (hook != NF_INET_PRE_ROUTING && hook != NF_INET_LOCAL_IN)
break;
With hook == NF_INET_INGRESS, csum stays 0, and the reject code reads that
as a good checksum.
nf_ip6_checksum() has the same check. It affects
reject6_csum_ok()->icmpv6_send() in nf_send_unreach6(), and also the TCP
RST path through nf_reject_ip6_tcphdr_get().
So can an inet ingress reject rule answer a packet with a bad L4 checksum
with an ICMP/ICMPv6 unreachable or a TCP RST? The same packet would get no
reply at PRE_ROUTING, or at netdev ingress (NF_NETDEV_INGRESS has the same
value as NF_INET_PRE_ROUTING).
Adding NF_INET_INGRESS to that hook list may not be enough on its own. At
ingress, skb->len can still include link-layer trailer padding, which
ip_rcv_core()/ip6_rcv_core() would normally trim.
This came in with commit 117ca1f8920c ("netfilter: nft_reject_inet: allow
to use reject from inet ingress").
> diff --git a/net/ipv6/netfilter/nf_reject_ipv6.c b/net/ipv6/netfilter/nf_reject_ipv6.c
> index 07cdaa10da0d..5e08d8529cc8 100644
> --- a/net/ipv6/netfilter/nf_reject_ipv6.c
> +++ b/net/ipv6/netfilter/nf_reject_ipv6.c
> @@ -444,6 +444,11 @@ void nf_send_unreach6(struct net *net, struct sk_buff *skb_in,
> if (hooknum == NF_INET_LOCAL_OUT && skb_in->dev == NULL)
> skb_in->dev = net->loopback_dev;
>
> + if (hooknum == NF_INET_INGRESS) {
> + memset(IP6CB(skb_in), 0, sizeof(*IP6CB(skb_in)));
> + IP6CB(skb_in)->iif = skb_in->skb_iif;
> + }
[Severity: Medium]
The commit message says this matches ip6_rcv_core(). Does that hold for
packets looped back through lo? ip6_rcv_core() chooses iif based on the
dst:
IP6CB(skb)->iif = skb_valid_dst(skb) ?
ip6_dst_idev(skb_dst(skb))->dev->ifindex :
dev->ifindex;
Its comment explains that a locally generated packet sent to a local
address on ethX arrives through lo. For scoping, though, it has to be
treated as arriving on ethX.
loopback_xmit() keeps the dst with skb_dst_force(). Then
__netif_receive_skb_core() sets skb->skb_iif to lo's ifindex before
nf_ingress() runs. For an inet ingress reject rule on lo, this code sets
iif to lo, while ip6_rcv_core() would use the dst's interface.
icmp6_send() uses parm->iif to pick the source address, through
dev_get_by_index() and ipv6_dev_get_saddr(). It also passes parm->iif to
icmp6_ext_append() for the RFC 5837 interface information.
Can the resulting ICMPv6 error get the wrong source address, or report lo
as the incoming interface?
nf_reject6_fill_skb_dst() runs after this block, so the original dst is
still attached here. Would something like this be closer to
ip6_rcv_core()?
IP6CB(skb_in)->iif = skb_valid_dst(skb_in) ?
ip6_dst_idev(skb_dst(skb_in))->dev->ifindex :
skb_in->skb_iif;
> +
> if (!skb_valid_dst(skb_in) && nf_reject6_fill_skb_dst(skb_in) < 0)
> return;
[ ... ]
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261019150249.1879516-2-pablo%40netfilter.org
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH net 08/10] netfilter: xt_dccp: handle reserved options as single-byte
2026-10-19 15:02 ` [PATCH net 08/10] netfilter: xt_dccp: handle reserved options as single-byte Pablo Neira Ayuso
@ 2026-10-20 15:38 ` netdev-bot+sashiko
0 siblings, 0 replies; 18+ messages in thread
From: netdev-bot+sashiko @ 2026-10-20 15:38 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 · 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).
- [Medium] The commit message lists CONFIG_NETFILTER_XTABLES_LEGACY as a
prerequisite for the bug.
Pre-existing issues:
- [Low] This problem wasn't introduced by this patch, but
dccp_find_option() in net/netfilter/xt_dccp.c does not validate multi-…
--
netfilter: xt_dccp: handle reserved options as single-byte
This commit changes dccp_find_option() so that every DCCP option number
up to DCCPO_MAX_RESERVED is treated as a single-byte option. Before, only
Padding and Mandatory were. The old behaviour read the byte after a
reserved option as a TLV length, which could skip a later option and
invert an XT_DCCP_OPTION rule result.
> Treat every option up to DCCPO_MAX_RESERVED as single-byte, matching the
> protocol definition. Triggering requires
> CONFIG_NETFILTER_XTABLES_LEGACY, CONFIG_NETFILTER_XT_MATCH_DCCP, an
> option-based rule, and remote protocol-33 reachability.
[Severity: Medium]
Is CONFIG_NETFILTER_XTABLES_LEGACY actually needed to trigger this?
In net/netfilter/Kconfig, NETFILTER_XT_MATCH_DCCP sits inside the
if NETFILTER_XTABLES block and depends only on NETFILTER_ADVANCED:
config NETFILTER_XT_MATCH_DCCP
tristate '"dccp" protocol match support (DEPRECATED)'
depends on NETFILTER_ADVANCED
NFT_COMPAT depends only on NETFILTER_XTABLES:
config NFT_COMPAT
depends on NETFILTER_XTABLES
nft_match_select_ops() in net/netfilter/nft_compat.c looks up the
registered "dccp" match by name:
match = xt_request_find_match(family, mt_name, rev);
The nft_compat packet path then reaches the same parser:
__nft_match_eval()
match->match(skb, &xt)
dccp_mt()
match_option()
dccp_find_option()
So iptables-nft setups without legacy tables support seem to have the
same reserved-option bypass. The fix in the shared helper covers both
frontends. Only the stated trigger conditions look wrong.
Could the commit message drop CONFIG_NETFILTER_XTABLES_LEGACY from the
prerequisites, or list CONFIG_NFT_COMPAT as an alternative? That way
backport and security triage won't skip nft-based systems.
> diff --git a/net/netfilter/xt_dccp.c b/net/netfilter/xt_dccp.c
> index 3db81e041af9..23a4e3e8df7d 100644
> --- a/net/netfilter/xt_dccp.c
> +++ b/net/netfilter/xt_dccp.c
> @@ -62,7 +62,7 @@ dccp_find_option(u_int8_t option,
> return true;
> }
>
> - if (op[i] < 2 || i == optlen - 1)
> + if (op[i] <= DCCPO_MAX_RESERVED || i == optlen - 1)
> i++;
> else
> i += op[i + 1] ? : 1;
[Severity: Low]
This isn't a bug introduced by this patch, but should dccp_find_option()
also reject multi-byte option lengths below 2?
For a type of 32 or above, a length byte of 0 or 1 moves i forward by one
byte. The next loop iteration then reads the length byte as an option
type.
For example, with options {32, 1, 0, 0} and --dccp-option 1:
i = 0: op[0] == 32, no match, i += op[1] (1)
i = 1: op[1] == 1, matches, returns true
No Mandatory option is actually present here. A {32, 0, ...} sequence
gives a phantom match for option 0 the same way.
nft_exthdr_dccp_eval() in net/netfilter/nft_exthdr.c parses the same
option space. It stops with no match on such lengths:
if (buflen < 2)
goto err;
len = bufp[1];
if (len < 2)
goto err;
So xt_dccp and nft exthdr dccp still give different answers for the same
malformed packet. Since the length byte is below 2, the phantom match
can only be option type 0 or 1. This patch brings the single-byte
classification in line with nft_exthdr but leaves this length check
difference in place.
Would it make sense to stop parsing (or treat it as no match) when
op[i + 1] < 2, as nft_exthdr_dccp_eval() does?
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261019150249.1879516-2-pablo%40netfilter.org
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH net 10/10] netfilter: nf_conntrack_netlink: fix inverted IPv6 tuple filter
2026-10-19 15:02 ` [PATCH net 10/10] netfilter: nf_conntrack_netlink: fix inverted IPv6 tuple filter Pablo Neira Ayuso
@ 2026-10-20 15:38 ` netdev-bot+sashiko
0 siblings, 0 replies; 18+ messages in thread
From: netdev-bot+sashiko @ 2026-10-20 15:38 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] The first sentence of the commit message describes the pre-patch
behaviour backwards.
--
netfilter: nf_conntrack_netlink: fix inverted IPv6 tuple filter
This commit drops the negation in front of ipv6_addr_cmp() in the IPv6
branch of ctnetlink_filter_match_tuple(). With that change, the
CTA_IP_SRC and CTA_IP_DST filters return 0 only when the addresses
differ, which matches the IPv4 path.
> ctnetlink_filter_match_tuple() returns 0 (no match) when the IPv6
> source or destination address differs from the filter, because the
> !ipv6_addr_cmp() test is inverted.
[Severity: Low]
Does this first sentence describe the pre-patch behaviour backwards?
ipv6_addr_cmp() is a memcmp() wrapper and returns 0 for equal addresses:
include/net/ipv6.h:ipv6_addr_cmp() {
return memcmp(a1, a2, sizeof(struct in6_addr));
}
Before the patch, ctnetlink_filter_match_tuple() had:
if ((flags & CTA_FILTER_FLAG(CTA_IP_SRC)) &&
!ipv6_addr_cmp(&filter_tuple->src.u3.in6,
&ct_tuple->src.u3.in6))
return 0;
That code returned 0 when the addresses were equal. When they differed,
it fell through and the entry counted as a match. The quoted sentence
describes the behaviour after the fix, not the bug.
The next sentence ("the negation makes the branch trigger on a match
instead of a mismatch") does describe the bug correctly, so the
paragraph contradicts itself.
Could the first sentence be reworded to say that the function returned
0 when the IPv6 address matched the filter?
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261019150249.1879516-2-pablo%40netfilter.org
^ permalink raw reply [flat|nested] 18+ messages in thread
end of thread, other threads:[~2026-10-20 15:39 UTC | newest]
Thread overview: 18+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-19 15:02 [PATCH net 00/10] Netfilter/IPVS fixes for net Pablo Neira Ayuso
2026-10-19 15:02 ` [PATCH net 01/10] netfilter: nf_conntrack: avoid truncating IPv6 nexthdr offset Pablo Neira Ayuso
2026-10-19 15:55 ` netdev-bot+sinfo
2026-10-19 15:02 ` [PATCH net 02/10] netfilter: nft_payload: restrict checksum offsets to known values Pablo Neira Ayuso
2026-10-20 15:36 ` netdev-bot+sashiko
2026-10-19 15:02 ` [PATCH net 03/10] ipvs: fix more races around the overload flag Pablo Neira Ayuso
2026-10-20 15:36 ` netdev-bot+sashiko
2026-10-19 15:02 ` [PATCH net 04/10] ipvs: skip committing sync_threshold when proc_dointvec fails Pablo Neira Ayuso
2026-10-20 15:37 ` netdev-bot+sashiko
2026-10-19 15:02 ` [PATCH net 05/10] netfilter: flowtable: use the vlan id, not the full tci, in the tuple key Pablo Neira Ayuso
2026-10-19 15:02 ` [PATCH net 06/10] netfilter: nf_reject: initialize IPCB at inet ingress Pablo Neira Ayuso
2026-10-20 15:37 ` netdev-bot+sashiko
2026-10-19 15:02 ` [PATCH net 07/10] netfilter: nft_set_rbtree: narrow down range completion to anonymous sets Pablo Neira Ayuso
2026-10-19 15:02 ` [PATCH net 08/10] netfilter: xt_dccp: handle reserved options as single-byte Pablo Neira Ayuso
2026-10-20 15:38 ` netdev-bot+sashiko
2026-10-19 15:02 ` [PATCH net 09/10] netfilter: flowtable: reset transport header after nf_flow_encap_pop() Pablo Neira Ayuso
2026-10-19 15:02 ` [PATCH net 10/10] netfilter: nf_conntrack_netlink: fix inverted IPv6 tuple filter Pablo Neira Ayuso
2026-10-20 15:38 ` netdev-bot+sashiko
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox