* [PATCH net 00/13] Netfilter/IPVS fixes for net
@ 2026-07-22 21:14 Pablo Neira Ayuso
2026-07-22 21:14 ` [PATCH net 01/13] netfilter: nf_conntrack_sip: widen NAT rewrite delta to s32 in sip_help_tcp() Pablo Neira Ayuso
` (12 more replies)
0 siblings, 13 replies; 19+ messages in thread
From: Pablo Neira Ayuso @ 2026-07-22 21:14 UTC (permalink / raw)
To: netfilter-devel; +Cc: davem, netdev, kuba, pabeni, edumazet, fw, horms
Hi,
The following batch contains Netfilter/IPVS fixes for net. This batch
includes a mix of IPVS follow ups related to Sashiko reports, as well as
crash fixes for connection tracking expectation, helpers, ipset and
nf_tables mostly for old bugs. This also includes a fix for the
flowtable tunnel selftest.
1) Use s32 instead of s16 to calculate the remaining payload containing
SIP messages, otherwise underflow is possible allowing out-of-bound
memory access beyond the skb->data area. From Xiang Mei.
2) Fix the counter check in the flowtable selftest for tunnels, from
Lorenzo Bianconi.
3) Add and use nf_ct_expect_related_pair() to add the RTP and RTCP
expectations under the expectation lock, this is required by the SIP
and H.323 NAT helpers. This fixes a possible reinsertion of an
expectation with the DEAD flag set on while looping to find
consecutive ports.
4) Fix ipset UaF during table resize by blocking comment updates on
kernel-side adds. From David Lee.
5) Do not propagate the IP_VS_CONN_F_ONE_PACKET flag when using IPVS
state synchronization, otherwise reaching stale freed from
ip_vs_conn struct is possible, Zhiling Zou.
6) Adjust the hn1 hash node when the forwarding method changes between
MASQ and non-MASQ for an already hashed connection. This can leave
stale hash nodes pointing to a freed struct ip_vs_conn and trigger
UaF while reading /proc/net/ip_vs_conn. From Julian Anastasov.
7) nft_object rhltable needs to be per table, just like chain rhltable,
otherwise UaF from object lookup path while netns is being released.
There is also the nlevent path that can reach stale objects. Placing
this rhltable under the table hierarchy fixes this issue.
8) Reject invalid combined usage of hashlimit tables with and without
XT_HASHLIMIT_RATE_MATCH flag mode, otherwise access to uninitialized
.burst field of dsthash_ent is possible.
9) Fix checksum validations in IPVS performed from LOCAL_IN,
from Julian Anastasov.
10) Fix incorrect packet offset to layer 4 protocol in IPVS, uncovered
by Sashiko, from Julian Anastasov.
11) Skip the mangling of ICMP replies for non-first fragments, also
reported by Sashiko. Also from Julian.
12) Clear ip_vs_conn flags under the spinlock to fix a possible data
race. From Julian Anastasov.
13) Fix incorrect calculation of the payload bitmask in the nf_tables
hardware offload support, leading to UBSAN splat. From Xiang Mei.
Julian Anastasov clarifies that some of the IPVS patches might still
result in pre-existing issues reports by Sashiko, they are ready to
follow up on that.
Please, pull these changes from:
git://git.kernel.org/pub/scm/linux/kernel/git/netfilter/nf.git nf-26-07-22
Thanks.
----------------------------------------------------------------
The following changes since commit fca68249b6f5e84859b200b54cb5e0aef98f2b3a:
Merge branch 'net-fix-two-issues-in-sk_clone-error-path' (2026-07-21 09:15:01 -0700)
are available in the Git repository at:
git://git.kernel.org/pub/scm/linux/kernel/git/netfilter/nf.git nf-26-07-22
for you to fetch changes up to 09e0d3e6d4f401e8216227657d5e412553785607:
netfilter: nft_payload: fix mask build for partial field offload (2026-07-22 20:47:50 +0200)
----------------------------------------------------------------
netfilter pull request 26-07-22
----------------------------------------------------------------
David Lee (1):
netfilter: ipset: do not update comments from kernel-side hash adds
Julian Anastasov (5):
ipvs: adjust double hashing when fwd method changes
ipvs: fix the checksum validations
ipvs: fix places with wrong packet offsets
ipvs: do not mangle ICMP replies for non-first fragments
ipvs: clear the nfct flag under lock
Lorenzo Bianconi (1):
selftests: netfilter: nft_flowtable.sh: fix offload counter verification for tunnel tests
Pablo Neira Ayuso (3):
netfilter: nf_conntrack_expect: add and use nf_ct_expect_related_pair()
netfilter: nf_tables: make nft_object rhltable per table
netfilter: xt_hashlimit: validate hashtable supports XT_HASHLIMIT_RATE_MATCH
Xiang Mei (1):
netfilter: nf_conntrack_sip: widen NAT rewrite delta to s32 in sip_help_tcp()
Xiang Mei (Microsoft) (1):
netfilter: nft_payload: fix mask build for partial field offload
Zhiling Zou (1):
ipvs: do not propagate one-packet flag to synced conns
include/linux/netfilter/nf_conntrack_sip.h | 2 +-
include/net/ip_vs.h | 48 +++++-
include/net/netfilter/nf_conntrack_expect.h | 3 +
include/net/netfilter/nf_tables.h | 4 +-
net/ipv4/netfilter/nf_nat_h323.c | 22 +--
net/netfilter/ipset/ip_set_hash_gen.h | 2 +-
net/netfilter/ipvs/ip_vs_app.c | 4 +-
net/netfilter/ipvs/ip_vs_conn.c | 192 ++++++++++++++++-----
net/netfilter/ipvs/ip_vs_core.c | 190 ++++++++++----------
net/netfilter/ipvs/ip_vs_proto_sctp.c | 19 +-
net/netfilter/ipvs/ip_vs_proto_tcp.c | 48 ++----
net/netfilter/ipvs/ip_vs_proto_udp.c | 54 ++----
net/netfilter/ipvs/ip_vs_xmit.c | 42 +++--
net/netfilter/nf_conntrack_expect.c | 35 +++-
net/netfilter/nf_conntrack_sip.c | 2 +-
net/netfilter/nf_nat_sip.c | 22 +--
net/netfilter/nf_tables_api.c | 34 ++--
net/netfilter/nft_payload.c | 12 +-
net/netfilter/xt_hashlimit.c | 16 +-
.../selftests/net/netfilter/nft_flowtable.sh | 14 +-
20 files changed, 452 insertions(+), 313 deletions(-)
^ permalink raw reply [flat|nested] 19+ messages in thread
* [PATCH net 01/13] netfilter: nf_conntrack_sip: widen NAT rewrite delta to s32 in sip_help_tcp()
2026-07-22 21:14 [PATCH net 00/13] Netfilter/IPVS fixes for net Pablo Neira Ayuso
@ 2026-07-22 21:14 ` Pablo Neira Ayuso
2026-07-22 21:14 ` [PATCH net 02/13] selftests: netfilter: nft_flowtable.sh: fix offload counter verification for tunnel tests Pablo Neira Ayuso
` (11 subsequent siblings)
12 siblings, 0 replies; 19+ messages in thread
From: Pablo Neira Ayuso @ 2026-07-22 21:14 UTC (permalink / raw)
To: netfilter-devel; +Cc: davem, netdev, kuba, pabeni, edumazet, fw, horms
From: Xiang Mei <xmei5@asu.edu>
sip_help_tcp() stores the size change of each NAT-rewritten SIP message
in s16 diff and accumulates it in s16 tdiff, but a single message can
grow by more than S16_MAX while the packet stays under the 65535
enlarge_skb() limit: nf_nat_sip() rewrites every matching URI, and a long
Contact list expands the message by tens of kilobytes. diff then wraps,
and "datalen = datalen + diff - msglen" yields a huge unsigned datalen,
so the next iteration's ct_sip_get_header() reads past the linearized skb
tail.
Widen diff, tdiff and the seq_adjust hook to s32. Both are bounded by the
65535 byte packet limit, and the seqadj core is already s32
(nf_ct_seqadj_set() takes s32), so no previously accepted input is
rejected.
BUG: KASAN: use-after-free in ct_sip_get_header (net/netfilter/nf_conntrack_sip.c:464)
Read of size 1 at addr ffff888010800000 by task ksoftirqd/1/25
ct_sip_get_header (net/netfilter/nf_conntrack_sip.c:464)
sip_help_tcp (net/netfilter/nf_conntrack_sip.c:1694)
nf_confirm (net/netfilter/nf_conntrack_proto.c:183)
nf_hook_slow (net/netfilter/core.c:619)
ip6_output (net/ipv6/ip6_output.c:246)
ip6_forward (net/ipv6/ip6_output.c:690)
ipv6_rcv (net/ipv6/ip6_input.c:351)
__netif_receive_skb_one_core (net/core/dev.c:6212)
process_backlog (net/core/dev.c:6676)
__napi_poll (net/core/dev.c:7735)
net_rx_action (net/core/dev.c:7955)
handle_softirqs (kernel/softirq.c:622)
run_ksoftirqd (kernel/softirq.c:1076)
...
Fixes: f5b321bd37fb ("netfilter: nf_conntrack_sip: add TCP support")
Reported-by: Weiming Shi <bestswngs@gmail.com>
Link: https://patch.msgid.link/netfilter-devel/20260712234201.3213635-1-xmei5@asu.edu
Assisted-by: Claude:claude-opus-4-8
Signed-off-by: Xiang Mei <xmei5@asu.edu>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
---
include/linux/netfilter/nf_conntrack_sip.h | 2 +-
net/netfilter/nf_conntrack_sip.c | 2 +-
net/netfilter/nf_nat_sip.c | 2 +-
3 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/include/linux/netfilter/nf_conntrack_sip.h b/include/linux/netfilter/nf_conntrack_sip.h
index dbc614dfe0d5..aafa0c04f917 100644
--- a/include/linux/netfilter/nf_conntrack_sip.h
+++ b/include/linux/netfilter/nf_conntrack_sip.h
@@ -115,7 +115,7 @@ struct nf_nat_sip_hooks {
unsigned int *datalen);
void (*seq_adjust)(struct sk_buff *skb,
- unsigned int protoff, s16 off);
+ unsigned int protoff, s32 off);
unsigned int (*expect)(struct sk_buff *skb,
unsigned int protoff,
diff --git a/net/netfilter/nf_conntrack_sip.c b/net/netfilter/nf_conntrack_sip.c
index f3f90a866338..e4a70d1d77b0 100644
--- a/net/netfilter/nf_conntrack_sip.c
+++ b/net/netfilter/nf_conntrack_sip.c
@@ -1663,7 +1663,7 @@ static int sip_help_tcp(struct sk_buff *skb, unsigned int protoff,
unsigned int matchoff, matchlen;
unsigned int msglen, origlen;
const char *dptr, *end;
- s16 diff, tdiff = 0;
+ s32 diff, tdiff = 0;
int ret = NF_ACCEPT;
unsigned long clen;
bool term;
diff --git a/net/netfilter/nf_nat_sip.c b/net/netfilter/nf_nat_sip.c
index aea02f6aff09..a93eaf0f7d30 100644
--- a/net/netfilter/nf_nat_sip.c
+++ b/net/netfilter/nf_nat_sip.c
@@ -321,7 +321,7 @@ static unsigned int nf_nat_sip(struct sk_buff *skb, unsigned int protoff,
}
static void nf_nat_sip_seq_adjust(struct sk_buff *skb, unsigned int protoff,
- s16 off)
+ s32 off)
{
enum ip_conntrack_info ctinfo;
struct nf_conn *ct = nf_ct_get(skb, &ctinfo);
--
2.47.3
^ permalink raw reply related [flat|nested] 19+ messages in thread
* [PATCH net 02/13] selftests: netfilter: nft_flowtable.sh: fix offload counter verification for tunnel tests
2026-07-22 21:14 [PATCH net 00/13] Netfilter/IPVS fixes for net Pablo Neira Ayuso
2026-07-22 21:14 ` [PATCH net 01/13] netfilter: nf_conntrack_sip: widen NAT rewrite delta to s32 in sip_help_tcp() Pablo Neira Ayuso
@ 2026-07-22 21:14 ` Pablo Neira Ayuso
2026-07-22 21:14 ` [PATCH net 03/13] netfilter: nf_conntrack_expect: add and use nf_ct_expect_related_pair() Pablo Neira Ayuso
` (10 subsequent siblings)
12 siblings, 0 replies; 19+ messages in thread
From: Pablo Neira Ayuso @ 2026-07-22 21:14 UTC (permalink / raw)
To: netfilter-devel; +Cc: davem, netdev, kuba, pabeni, edumazet, fw, horms
From: Lorenzo Bianconi <lorenzo@kernel.org>
The IPIP and IP6IP6 tunnel tests call check_counters() to verify
flowtable offloading occurred, but the flow-add rule only matches
meta oif "veth1". When traffic is routed through a tunnel device,
oif is the tunnel interface (tun0, tun6, etc.), not veth1, so
the flow-add rule never fires, no flowtable entry is created,
and counters stay at zero — producing a silent false pass.
Fix by adding tunnel-specific flow-add rules for each tunnel
interface. These match TCP dport 12345 traffic before the bare
accept rule, set ct mark, add the flow to the flowtable, and
increment routed_orig. The existing routed_repl rule on veth0
already handles the reply direction since decapsulated reply
packets exit through the physical interface.
Also add check_counters() for the IP6IP6 non-VLAN and
IP6IP6-over-VLAN tests which previously used a bare PASS message.
Fixes: fe8313316eaf ("selftests: netfilter: nft_flowtable.sh: Add IPIP flowtable selftest")
Fixes: 5e5180352193 ("selftests: netfilter: nft_flowtable.sh: Add IP6IP6 flowtable selftest")
Signed-off-by: Lorenzo Bianconi <lorenzo@kernel.org>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
---
.../selftests/net/netfilter/nft_flowtable.sh | 14 ++++++++++++--
1 file changed, 12 insertions(+), 2 deletions(-)
diff --git a/tools/testing/selftests/net/netfilter/nft_flowtable.sh b/tools/testing/selftests/net/netfilter/nft_flowtable.sh
index fb1c59d45567..449c518bd947 100755
--- a/tools/testing/selftests/net/netfilter/nft_flowtable.sh
+++ b/tools/testing/selftests/net/netfilter/nft_flowtable.sh
@@ -617,7 +617,11 @@ ip -6 -net "$nsr2" route add default via fee1:3::1
ip -net "$ns2" route add default via 10.0.2.1
ip -6 -net "$ns2" route add default via dead:2::1
+ip netns exec "$nsr1" nft -a insert rule inet filter forward \
+ 'meta oif tun0 tcp dport 12345 ct mark set 1 flow add @f1 counter name routed_orig accept'
ip netns exec "$nsr1" nft -a insert rule inet filter forward 'meta oif tun0 accept'
+ip netns exec "$nsr1" nft -a insert rule inet filter forward \
+ 'meta oif tun6 tcp dport 12345 ct mark set 1 flow add @f1 counter name routed_orig accept'
ip netns exec "$nsr1" nft -a insert rule inet filter forward 'meta oif tun6 accept'
ip netns exec "$nsr1" nft -a insert rule inet filter forward \
'meta oif "veth0" tcp sport 12345 ct mark set 1 flow add @f1 counter name routed_repl accept'
@@ -629,7 +633,7 @@ if ! test_tcp_forwarding_nat "$ns1" "$ns2" 1 "IPIP tunnel"; then
fi
if test_tcp_forwarding "$ns1" "$ns2" 1 6 "[dead:2::99]" 12345; then
- echo "PASS: flow offload for ns1/ns2 IP6IP6 tunnel"
+ check_counters "flow offload for ns1/ns2 IP6IP6 tunnel"
else
echo "FAIL: flow offload for ns1/ns2 with IP6IP6 tunnel" 1>&2
ip netns exec "$nsr1" nft list ruleset
@@ -642,6 +646,8 @@ ip -net "$nsr1" link set veth1.10 up
ip -net "$nsr1" addr add 192.168.20.1/24 dev veth1.10
ip -net "$nsr1" addr add fee1:4::1/64 dev veth1.10 nodad
ip netns exec "$nsr1" sysctl net.ipv4.conf.veth1/10.forwarding=1 > /dev/null
+ip netns exec "$nsr1" nft -a insert rule inet filter forward \
+ 'meta oif veth1.10 tcp dport 12345 ct mark set 1 flow add @f1 counter name routed_orig accept'
ip netns exec "$nsr1" nft -a insert rule inet filter forward 'meta oif veth1.10 accept'
ip -net "$nsr1" link add name tun0.10 type ipip local 192.168.20.1 remote 192.168.20.2
@@ -649,6 +655,8 @@ ip -net "$nsr1" link set tun0.10 up
ip -net "$nsr1" addr add 192.168.200.1/24 dev tun0.10
ip -net "$nsr1" route change default via 192.168.200.2
ip netns exec "$nsr1" sysctl net.ipv4.conf.tun0/10.forwarding=1 > /dev/null
+ip netns exec "$nsr1" nft -a insert rule inet filter forward \
+ 'meta oif tun0.10 tcp dport 12345 ct mark set 1 flow add @f1 counter name routed_orig accept'
ip netns exec "$nsr1" nft -a insert rule inet filter forward 'meta oif tun0.10 accept'
ip -net "$nsr1" link add name tun6.10 type ip6tnl local fee1:4::1 remote fee1:4::2 encaplimit none
@@ -656,6 +664,8 @@ ip -net "$nsr1" link set tun6.10 up
ip -net "$nsr1" addr add fee1:5::1/64 dev tun6.10 nodad
ip -6 -net "$nsr1" route delete default
ip -6 -net "$nsr1" route add default via fee1:5::2
+ip netns exec "$nsr1" nft -a insert rule inet filter forward \
+ 'meta oif tun6.10 tcp dport 12345 ct mark set 1 flow add @f1 counter name routed_orig accept'
ip netns exec "$nsr1" nft -a insert rule inet filter forward 'meta oif tun6.10 accept'
ip -net "$nsr2" link add link veth0 name veth0.10 type vlan id 10
@@ -683,7 +693,7 @@ if ! test_tcp_forwarding_nat "$ns1" "$ns2" 1 "IPIP tunnel over vlan"; then
fi
if test_tcp_forwarding "$ns1" "$ns2" 1 6 "[dead:2::99]" 12345; then
- echo "PASS: flow offload for ns1/ns2 IP6IP6 tunnel over vlan"
+ check_counters "flow offload for ns1/ns2 IP6IP6 tunnel over vlan"
else
echo "FAIL: flow offload for ns1/ns2 with IP6IP6 tunnel over vlan" 1>&2
ip netns exec "$nsr1" nft list ruleset
--
2.47.3
^ permalink raw reply related [flat|nested] 19+ messages in thread
* [PATCH net 03/13] netfilter: nf_conntrack_expect: add and use nf_ct_expect_related_pair()
2026-07-22 21:14 [PATCH net 00/13] Netfilter/IPVS fixes for net Pablo Neira Ayuso
2026-07-22 21:14 ` [PATCH net 01/13] netfilter: nf_conntrack_sip: widen NAT rewrite delta to s32 in sip_help_tcp() Pablo Neira Ayuso
2026-07-22 21:14 ` [PATCH net 02/13] selftests: netfilter: nft_flowtable.sh: fix offload counter verification for tunnel tests Pablo Neira Ayuso
@ 2026-07-22 21:14 ` Pablo Neira Ayuso
2026-07-22 21:14 ` [PATCH net 04/13] netfilter: ipset: do not update comments from kernel-side hash adds Pablo Neira Ayuso
` (9 subsequent siblings)
12 siblings, 0 replies; 19+ messages in thread
From: Pablo Neira Ayuso @ 2026-07-22 21:14 UTC (permalink / raw)
To: netfilter-devel; +Cc: davem, netdev, kuba, pabeni, edumazet, fw, horms
Add a new function to insert a pair of expectations, this is required by
the SIP and H323 NAT helpers. The spinlock is held to check if there is
a slot for both expectations, in such case, insert them.
This removes the need for nf_ct_unexpect_related() inside the loop to
find a pair of consecutive ports, otherwise inserting expectations whose
dead flag is already set on can happen.
Bump master_help->expecting for the expectation class after checking if
the expectation fits in the master expectation list, which is needed for
this new _pair() function variant to run the eviction routine including
the preallocated slot for the first expectation in the pair.
Fixes: b8b09dc2bf35 ("netfilter: nf_conntrack_expect: use conntrack GC to reap expectations")
Reported-by: Jaeyeong Lee <iostreampy@proton.me>
Link: https://patch.msgid.link/178377968720.33756.12204817361601593230@proton.me/
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
---
include/net/netfilter/nf_conntrack_expect.h | 3 ++
net/ipv4/netfilter/nf_nat_h323.c | 22 +++++--------
net/netfilter/nf_conntrack_expect.c | 35 ++++++++++++++++++++-
net/netfilter/nf_nat_sip.c | 20 ++++--------
4 files changed, 50 insertions(+), 30 deletions(-)
diff --git a/include/net/netfilter/nf_conntrack_expect.h b/include/net/netfilter/nf_conntrack_expect.h
index c024345c9bd8..26d6babd92fc 100644
--- a/include/net/netfilter/nf_conntrack_expect.h
+++ b/include/net/netfilter/nf_conntrack_expect.h
@@ -161,6 +161,9 @@ static inline int nf_ct_expect_related(struct nf_conntrack_expect *expect,
return nf_ct_expect_related_report(expect, 0, 0, flags);
}
+int nf_ct_expect_related_pair(struct nf_conntrack_expect *expect[],
+ unsigned int flag);
+
struct nf_conn_help;
void nf_ct_expectation_gc(struct nf_conn_help *master_help);
diff --git a/net/ipv4/netfilter/nf_nat_h323.c b/net/ipv4/netfilter/nf_nat_h323.c
index 183e8a3ff2ba..6bcd6734769b 100644
--- a/net/ipv4/netfilter/nf_nat_h323.c
+++ b/net/ipv4/netfilter/nf_nat_h323.c
@@ -182,6 +182,7 @@ static int nat_rtp_rtcp(struct sk_buff *skb, struct nf_conn *ct,
struct nf_conntrack_expect *rtp_exp,
struct nf_conntrack_expect *rtcp_exp)
{
+ struct nf_conntrack_expect *rtp_pair[2] = { rtp_exp, rtcp_exp };
struct nf_ct_h323_master *info = nfct_help_data(ct);
int dir = CTINFO2DIR(ctinfo);
int i;
@@ -227,22 +228,13 @@ static int nat_rtp_rtcp(struct sk_buff *skb, struct nf_conn *ct,
int ret;
rtp_exp->tuple.dst.u.udp.port = htons(nated_port);
- ret = nf_ct_expect_related(rtp_exp, 0);
+ rtcp_exp->tuple.dst.u.udp.port = htons(nated_port + 1);
+ ret = nf_ct_expect_related_pair(rtp_pair, 0);
if (ret == 0) {
- rtcp_exp->tuple.dst.u.udp.port =
- htons(nated_port + 1);
- ret = nf_ct_expect_related(rtcp_exp, 0);
- if (ret == 0)
- break;
- else if (ret == -EBUSY) {
- nf_ct_unexpect_related(rtp_exp);
- continue;
- } else if (ret < 0) {
- nf_ct_unexpect_related(rtp_exp);
- nated_port = 0;
- break;
- }
- } else if (ret != -EBUSY) {
+ break;
+ } else if (ret == -EBUSY) {
+ continue;
+ } else if (ret < 0) {
nated_port = 0;
break;
}
diff --git a/net/netfilter/nf_conntrack_expect.c b/net/netfilter/nf_conntrack_expect.c
index 7ae68d60586a..8a3b9e33e94f 100644
--- a/net/netfilter/nf_conntrack_expect.c
+++ b/net/netfilter/nf_conntrack_expect.c
@@ -427,7 +427,6 @@ static void nf_ct_expect_insert(struct nf_conntrack_expect *exp,
exp->timeout += helper->expect_policy[exp->class].timeout * HZ;
hlist_add_head_rcu(&exp->lnode, &master_help->expectations);
- master_help->expecting[exp->class]++;
hlist_add_head_rcu(&exp->hnode, &nf_ct_expect_hash[h]);
cnet = nf_ct_pernet(net);
@@ -534,6 +533,7 @@ int nf_ct_expect_related_report(struct nf_conntrack_expect *expect,
if (ret < 0)
goto out;
+ master_help->expecting[expect->class]++;
nf_ct_expect_insert(expect, master_help);
nf_ct_expect_event_report(IPEXP_NEW, expect, portid, report);
@@ -546,6 +546,39 @@ int nf_ct_expect_related_report(struct nf_conntrack_expect *expect,
}
EXPORT_SYMBOL_GPL(nf_ct_expect_related_report);
+int nf_ct_expect_related_pair(struct nf_conntrack_expect *expect[],
+ unsigned int flags)
+{
+ struct nf_conn_help *master_help;
+ int i, ret;
+
+ spin_lock_bh(&nf_conntrack_expect_lock);
+ master_help = nfct_help(expect[0]->master);
+ if (!master_help || master_help != nfct_help(expect[1]->master)) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ for (i = 0; i < 2; i++) {
+ ret = __nf_ct_expect_check(expect[i], master_help, flags);
+ if (ret < 0) {
+ if (i == 1)
+ master_help->expecting[expect[0]->class]--;
+ goto out;
+ }
+ master_help->expecting[expect[i]->class]++;
+ }
+
+ for (i = 0; i < 2; i++) {
+ nf_ct_expect_insert(expect[i], master_help);
+ nf_ct_expect_event_report(IPEXP_NEW, expect[i], 0, 0);
+ }
+out:
+ spin_unlock_bh(&nf_conntrack_expect_lock);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(nf_ct_expect_related_pair);
+
void nf_ct_expect_iterate_destroy(bool (*iter)(struct nf_conntrack_expect *e, void *data),
void *data)
{
diff --git a/net/netfilter/nf_nat_sip.c b/net/netfilter/nf_nat_sip.c
index a93eaf0f7d30..133bd713fe0c 100644
--- a/net/netfilter/nf_nat_sip.c
+++ b/net/netfilter/nf_nat_sip.c
@@ -592,6 +592,7 @@ static unsigned int nf_nat_sdp_media(struct sk_buff *skb, unsigned int protoff,
unsigned int medialen,
union nf_inet_addr *rtp_addr)
{
+ struct nf_conntrack_expect *rtp_pair[2] = { rtp_exp, rtcp_exp };
enum ip_conntrack_info ctinfo;
struct nf_conn *ct = nf_ct_get(skb, &ctinfo);
enum ip_conntrack_dir dir = CTINFO2DIR(ctinfo);
@@ -622,24 +623,15 @@ static unsigned int nf_nat_sdp_media(struct sk_buff *skb, unsigned int protoff,
int ret;
rtp_exp->tuple.dst.u.udp.port = htons(port);
- ret = nf_ct_expect_related(rtp_exp,
- NF_CT_EXP_F_SKIP_MASTER);
- if (ret == -EBUSY)
- continue;
- else if (ret < 0) {
- port = 0;
- break;
- }
rtcp_exp->tuple.dst.u.udp.port = htons(port + 1);
- ret = nf_ct_expect_related(rtcp_exp,
- NF_CT_EXP_F_SKIP_MASTER);
+
+ ret = nf_ct_expect_related_pair(rtp_pair,
+ NF_CT_EXP_F_SKIP_MASTER);
if (ret == 0)
break;
- else if (ret == -EBUSY) {
- nf_ct_unexpect_related(rtp_exp);
+ else if (ret == -EBUSY)
continue;
- } else if (ret < 0) {
- nf_ct_unexpect_related(rtp_exp);
+ else if (ret < 0) {
port = 0;
break;
}
--
2.47.3
^ permalink raw reply related [flat|nested] 19+ messages in thread
* [PATCH net 04/13] netfilter: ipset: do not update comments from kernel-side hash adds
2026-07-22 21:14 [PATCH net 00/13] Netfilter/IPVS fixes for net Pablo Neira Ayuso
` (2 preceding siblings ...)
2026-07-22 21:14 ` [PATCH net 03/13] netfilter: nf_conntrack_expect: add and use nf_ct_expect_related_pair() Pablo Neira Ayuso
@ 2026-07-22 21:14 ` Pablo Neira Ayuso
2026-07-22 21:14 ` [PATCH net 05/13] ipvs: do not propagate one-packet flag to synced conns Pablo Neira Ayuso
` (8 subsequent siblings)
12 siblings, 0 replies; 19+ messages in thread
From: Pablo Neira Ayuso @ 2026-07-22 21:14 UTC (permalink / raw)
To: netfilter-devel; +Cc: davem, netdev, kuba, pabeni, edumazet, fw, horms
From: David Lee <david.lee@trailofbits.com>
mtype_resize() copies comment pointers with memcpy(), not the comment objects
themselves. During the window after an entry has been copied but before the
table swap and backlog replay, the old table is still published for
packet-side updates while the replacement-table entry already holds the same
ip_set_comment_rcu pointer.
If xt_SET --add-set ... --exist hits that old entry in this window,
mtype_add() calls ip_set_init_comment() even though packet-side adds carry no
comment payload. That call frees the shared comment through the old entry, so
the replacement-table entry now holds a stale pointer. When the queued add is
replayed on the new table, mtype_add() calls ip_set_init_comment() again and
strlen() dereferences the stale pointer.
Fix this in mtype_add() by skipping ip_set_init_comment() when ext->target
marks a packet-side add. Userspace adds still update comments, while
packet-side adds can no longer free comment storage shared with a resize copy.
Fixes: f66ee0410b1c ("netfilter: ipset: Fix "INFO: rcu detected stall in hash_xxx" reports")
Cc: stable@vger.kernel.org
Signed-off-by: David Lee <david.lee@trailofbits.com>
Assisted-by: Codex:gpt-5.5
Acked-by: Jozsef Kadlecsik <kadlec@netfilter.org>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
---
net/netfilter/ipset/ip_set_hash_gen.h | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/net/netfilter/ipset/ip_set_hash_gen.h b/net/netfilter/ipset/ip_set_hash_gen.h
index 8231317b0f1f..b2d77973272d 100644
--- a/net/netfilter/ipset/ip_set_hash_gen.h
+++ b/net/netfilter/ipset/ip_set_hash_gen.h
@@ -1005,7 +1005,7 @@ mtype_add(struct ip_set *set, void *value, const struct ip_set_ext *ext,
#endif
if (SET_WITH_COUNTER(set))
ip_set_init_counter(ext_counter(data, set), ext);
- if (SET_WITH_COMMENT(set))
+ if (SET_WITH_COMMENT(set) && !ext->target)
ip_set_init_comment(set, ext_comment(data, set), ext);
if (SET_WITH_SKBINFO(set))
ip_set_init_skbinfo(ext_skbinfo(data, set), ext);
--
2.47.3
^ permalink raw reply related [flat|nested] 19+ messages in thread
* [PATCH net 05/13] ipvs: do not propagate one-packet flag to synced conns
2026-07-22 21:14 [PATCH net 00/13] Netfilter/IPVS fixes for net Pablo Neira Ayuso
` (3 preceding siblings ...)
2026-07-22 21:14 ` [PATCH net 04/13] netfilter: ipset: do not update comments from kernel-side hash adds Pablo Neira Ayuso
@ 2026-07-22 21:14 ` Pablo Neira Ayuso
2026-07-22 21:14 ` [PATCH net 06/13] ipvs: adjust double hashing when fwd method changes Pablo Neira Ayuso
` (7 subsequent siblings)
12 siblings, 0 replies; 19+ messages in thread
From: Pablo Neira Ayuso @ 2026-07-22 21:14 UTC (permalink / raw)
To: netfilter-devel; +Cc: davem, netdev, kuba, pabeni, edumazet, fw, horms
From: Zhiling Zou <roxy520tt@gmail.com>
Synced connections can be created before their destination exists. When
the destination is later added, ip_vs_bind_dest() copies connection flags
from the destination into cp->flags.
IP_VS_CONN_F_ONE_PACKET connections are not synced. If a synced
connection inherits IP_VS_CONN_F_ONE_PACKET while it is already hashed,
expiry can treat it as a one-packet connection and skip unlinking the
existing conn_tab node, leaving stale hash nodes pointing at a freed
struct ip_vs_conn.
Drop IP_VS_CONN_F_ONE_PACKET from destination flags when binding synced
connections.
Fixes: 26ec037f9841 ("IPVS: one-packet scheduling")
Cc: stable@vger.kernel.org
Reported-by: Yuan Tan <yuantan098@gmail.com>
Reported-by: Yifan Wu <yifanwucs@gmail.com>
Reported-by: Juefei Pu <tomapufckgml@gmail.com>
Reported-by: Xin Liu <bird@lzu.edu.cn>
Suggested-by: Julian Anastasov <ja@ssi.bg>
Signed-off-by: Zhiling Zou <roxy520tt@gmail.com>
Signed-off-by: Ren Wei <n05ec@lzu.edu.cn>
Acked-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 6ed2622363f0..0682cec5f0a7 100644
--- a/net/netfilter/ipvs/ip_vs_conn.c
+++ b/net/netfilter/ipvs/ip_vs_conn.c
@@ -1014,6 +1014,9 @@ ip_vs_bind_dest(struct ip_vs_conn *cp, struct ip_vs_dest *dest)
flags = cp->flags;
/* 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 */
+ conn_flags &= ~IP_VS_CONN_F_ONE_PACKET;
+
/* if the connection is not template and is created
* by sync, preserve the activity flag.
*/
--
2.47.3
^ permalink raw reply related [flat|nested] 19+ messages in thread
* [PATCH net 06/13] ipvs: adjust double hashing when fwd method changes
2026-07-22 21:14 [PATCH net 00/13] Netfilter/IPVS fixes for net Pablo Neira Ayuso
` (4 preceding siblings ...)
2026-07-22 21:14 ` [PATCH net 05/13] ipvs: do not propagate one-packet flag to synced conns Pablo Neira Ayuso
@ 2026-07-22 21:14 ` Pablo Neira Ayuso
2026-07-22 21:14 ` [PATCH net 07/13] netfilter: nf_tables: make nft_object rhltable per table Pablo Neira Ayuso
` (6 subsequent siblings)
12 siblings, 0 replies; 19+ messages in thread
From: Pablo Neira Ayuso @ 2026-07-22 21:14 UTC (permalink / raw)
To: netfilter-devel; +Cc: davem, netdev, kuba, pabeni, edumazet, fw, horms
From: Julian Anastasov <ja@ssi.bg>
Synced conns can be created with one forwarding method
and later updated with different one after the dest
server is configured. This needs adjusting the hashing
for node hn1 because only MASQ supports double hashing.
Modify conn_tab_lock() to support seeking for hash node
hn0 together with adding for hn1. By this way we can
safely modify the forwarding method and hn1.hash_key
under bucket lock for the first node hn0. The forwarding
method is also protected by cp->lock as it is part of
cp->flags.
Fix the usage of stale idx/idx2 values in conn_tab_lock
after jumping to the retry label. Instead, use idx/idx2
values just to order the locking for the old/new tables.
Reported-by: Zhiling Zou <roxy520tt@gmail.com>
Link: https://patch.msgid.link/1b914f41d725bc064c9ba9830dc8169329737270.1782540466.git.roxy520tt@gmail.com/
Link: https://sashiko.dev/#/patchset/CALMqdkR704S2BG_QD_bgHTFp2%2B1QCi7n0T4zoZyTo8mDZevYSA%40mail.gmail.com
Fixes: f20c73b0460d ("ipvs: use more keys for connection hashing")
Signed-off-by: Julian Anastasov <ja@ssi.bg>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
---
net/netfilter/ipvs/ip_vs_conn.c | 189 +++++++++++++++++++++++++-------
1 file changed, 147 insertions(+), 42 deletions(-)
diff --git a/net/netfilter/ipvs/ip_vs_conn.c b/net/netfilter/ipvs/ip_vs_conn.c
index 0682cec5f0a7..36c5cba03f5b 100644
--- a/net/netfilter/ipvs/ip_vs_conn.c
+++ b/net/netfilter/ipvs/ip_vs_conn.c
@@ -70,25 +70,45 @@ static struct kmem_cache *ip_vs_conn_cachep __read_mostly;
* bucket or hash table
* - hash table resize works like rehash but always rehashes into new table
* - bit lock on bucket serializes all operations that modify the chain
+ * - on resize, bucket from the old table is locked before bucket from the
+ * new table
* - cp->lock protects conn fields like cp->flags, cp->dest
*/
-/* Lock conn_tab bucket for conn hash/unhash, not for rehash */
+/**
+ * conn_tab_lock - Lock conn_tab buckets for conn hash/unhash, not for rehash
+ * @t: hash table for hn0, new_tbl when new_hash=true
+ * @t2: hash table for hn1, new_tbl when new_hash2=true
+ * @cp: connection
+ * @hash_key: hash key for hn0
+ * @hash_key2: hash key for hn1
+ * @use2: using hn1 (double hashing) based on the forwarding method
+ * @new_hash: mode for hn0, hash node (true) or seek node (false)
+ * @new_hash2: mode for hn1, hash node (true) or seek node (false)
+ * @head_ret: returned head for hn0
+ * @head2_ret: returned head for hn1
+ *
+ * We support 3 modes:
+ * - seek mode for both nodes, used for unhashing
+ * - hash mode for both nodes, used for hashing
+ * - seek hn0 and hash hn1, used when forwarding method is changed
+ */
static __always_inline void
-conn_tab_lock(struct ip_vs_rht *t, struct ip_vs_conn *cp, u32 hash_key,
- u32 hash_key2, bool use2, bool new_hash,
- struct hlist_bl_head **head_ret, struct hlist_bl_head **head2_ret)
+conn_tab_lock(struct ip_vs_rht *t, struct ip_vs_rht *t2, struct ip_vs_conn *cp,
+ u32 hash_key, u32 hash_key2, bool use2, bool new_hash,
+ bool new_hash2, struct hlist_bl_head **head_ret,
+ struct hlist_bl_head **head2_ret)
{
struct hlist_bl_head *head, *head2;
u32 hash_key_new, hash_key_new2;
- struct ip_vs_rht *t2 = t;
- u32 idx, idx2;
+ int idx = 0, idx2 = 0;
+
+ /* Advance idx2 when new_hash is not set but hash_key2
+ * is for new table
+ */
+ if (new_hash2 && use2 && t != t2)
+ idx2++;
- idx = hash_key & t->mask;
- if (use2)
- idx2 = hash_key2 & t->mask;
- else
- idx2 = idx;
if (!new_hash) {
/* We need to lock the bucket in the right table */
@@ -100,46 +120,45 @@ conn_tab_lock(struct ip_vs_rht *t, struct ip_vs_conn *cp, u32 hash_key,
* both nodes in different tables, use idx/idx2
* for proper lock ordering for heads.
*/
- idx = hash_key & t->mask;
- idx |= IP_VS_RHT_TABLE_ID_MASK;
- }
- if (use2) {
- if (!ip_vs_rht_same_table(t2, hash_key2)) {
- /* It is already moved to new table */
- t2 = rcu_dereference(t2->new_tbl);
- idx2 = hash_key2 & t2->mask;
- idx2 |= IP_VS_RHT_TABLE_ID_MASK;
- }
- } else {
- idx2 = idx;
+ idx++;
}
}
+ if (use2 && !new_hash2 && !ip_vs_rht_same_table(t2, hash_key2)) {
+ /* It is already moved to new table */
+ t2 = rcu_dereference(t2->new_tbl);
+ idx2++;
+ }
+ if (!use2)
+ idx2 = idx;
head = t->buckets + (hash_key & t->mask);
head2 = use2 ? t2->buckets + (hash_key2 & t2->mask) : head;
- local_bh_disable();
- /* Do not touch seqcount, this is a safe operation */
-
- if (idx <= idx2) {
+ if (idx > idx2 || (head > head2 && idx == idx2)) {
+ hlist_bl_lock(head2);
hlist_bl_lock(head);
- if (head != head2)
- hlist_bl_lock(head2);
} else {
- hlist_bl_lock(head2);
hlist_bl_lock(head);
+ if (head != head2)
+ hlist_bl_lock(head2);
}
if (!new_hash) {
+ bool changed;
+
/* Ensure hash_key is read under lock */
hash_key_new = READ_ONCE(cp->hn0.hash_key);
- hash_key_new2 = READ_ONCE(cp->hn1.hash_key);
+ changed = hash_key != hash_key_new;
+ if (use2 && !new_hash2) {
+ hash_key_new2 = READ_ONCE(cp->hn1.hash_key);
+ changed |= hash_key2 != hash_key_new2;
+ } else {
+ hash_key_new2 = hash_key2;
+ }
/* Hash changed ? */
- if (hash_key != hash_key_new ||
- (hash_key2 != hash_key_new2 && use2)) {
+ if (changed) {
if (head != head2)
hlist_bl_unlock(head2);
hlist_bl_unlock(head);
- local_bh_enable();
hash_key = hash_key_new;
hash_key2 = hash_key_new2;
goto retry;
@@ -155,7 +174,6 @@ static inline void conn_tab_unlock(struct hlist_bl_head *head,
if (head != head2)
hlist_bl_unlock(head2);
hlist_bl_unlock(head);
- local_bh_enable();
}
static void ip_vs_conn_expire(struct timer_list *t);
@@ -268,8 +286,9 @@ static inline int ip_vs_conn_hash(struct ip_vs_conn *cp)
use2 = false;
}
- conn_tab_lock(t, cp, hash_key, hash_key2, use2, true /* new_hash */,
- &head, &head2);
+ local_bh_disable();
+ conn_tab_lock(t, t, cp, hash_key, hash_key2, use2, true /* new_hash */,
+ true /* new_hash2 */, &head, &head2);
cp->flags |= IP_VS_CONN_F_HASHED;
WRITE_ONCE(cp->hn0.hash_key, hash_key);
@@ -280,6 +299,7 @@ static inline int ip_vs_conn_hash(struct ip_vs_conn *cp)
hlist_bl_add_head_rcu(&cp->hn1.node, head2);
conn_tab_unlock(head, head2);
+ local_bh_enable();
ret = 1;
/* Schedule resizing if load increases */
@@ -306,18 +326,20 @@ static inline bool ip_vs_conn_unlink(struct ip_vs_conn *cp)
return refcount_dec_if_one(&cp->refcnt);
rcu_read_lock();
+ local_bh_disable();
t = rcu_dereference(ipvs->conn_tab);
hash_key = READ_ONCE(cp->hn0.hash_key);
hash_key2 = READ_ONCE(cp->hn1.hash_key);
use2 = ip_vs_conn_use_hash2(cp);
- conn_tab_lock(t, cp, hash_key, hash_key2, use2, false /* new_hash */,
- &head, &head2);
+ conn_tab_lock(t, t, cp, hash_key, hash_key2, use2, false /* new_hash */,
+ false /* new_hash2 */, &head, &head2);
if (cp->flags & IP_VS_CONN_F_HASHED) {
/* Decrease refcnt and unlink conn only if we are last user */
- if (refcount_dec_if_one(&cp->refcnt)) {
+ if (use2 == ip_vs_conn_use_hash2(cp) &&
+ refcount_dec_if_one(&cp->refcnt)) {
hlist_bl_del_rcu(&cp->hn0.node);
if (use2)
hlist_bl_del_rcu(&cp->hn1.node);
@@ -328,6 +350,7 @@ static inline bool ip_vs_conn_unlink(struct ip_vs_conn *cp)
conn_tab_unlock(head, head2);
+ local_bh_enable();
rcu_read_unlock();
return ret;
@@ -632,6 +655,7 @@ void ip_vs_conn_fill_cport(struct ip_vs_conn *cp, __be16 cport)
int ntbl;
int dir;
+restart:
/* No packets from inside, so we can do it in 2 steps. */
dir = use2 ? 1 : 0;
@@ -686,6 +710,23 @@ void ip_vs_conn_fill_cport(struct ip_vs_conn *cp, __be16 cport)
/* Protect the cp->flags modification */
spin_lock_bh(&cp->lock);
+ /* Recheck the forwarding method under lock */
+ if (use2 != ip_vs_conn_use_hash2(cp)) {
+ use2 = !use2;
+ if (use2) {
+ spin_unlock_bh(&cp->lock);
+ /* Restart with new use2 value */
+ goto restart;
+ }
+ if (dir) {
+ /* Not started yet, so just skip dir 1 */
+ spin_unlock_bh(&cp->lock);
+ dir--;
+ goto next_dir;
+ }
+ /* Just finish dir 0 */
+ }
+
/* Lock seqcount only for the old bucket, even if we are on new table
* because it affects the del operation, not the adding.
*/
@@ -752,6 +793,61 @@ void ip_vs_conn_fill_cport(struct ip_vs_conn *cp, __be16 cport)
goto next_dir;
}
+/* Change forwarding method for hashed conn */
+static void ip_vs_conn_change_fwd_mask(struct ip_vs_conn *cp, u32 new_flags)
+{
+ struct netns_ipvs *ipvs = cp->ipvs;
+ struct hlist_bl_head *head, *head2;
+ u32 hash2, hash_key, hash_key2;
+ struct ip_vs_rht *t, *t2;
+
+ /* See ip_vs_conn_use_hash2() for reference */
+ if ((cp->flags & IP_VS_CONN_F_TEMPLATE) ||
+ /* No change in double hashing ? */
+ (IP_VS_FWD_METHOD(cp) == IP_VS_CONN_F_MASQ) ==
+ ((new_flags & IP_VS_CONN_F_FWD_MASK) == IP_VS_CONN_F_MASQ)) {
+ cp->flags = new_flags;
+ return;
+ }
+ t = rcu_dereference(ipvs->conn_tab);
+ if (ip_vs_conn_use_hash2(cp)) {
+ /* Stop double hashing */
+ hash_key = READ_ONCE(cp->hn0.hash_key);
+ hash_key2 = READ_ONCE(cp->hn1.hash_key);
+
+ conn_tab_lock(t, t, cp, hash_key, hash_key2, true /* use2 */,
+ false /* new_hash */, false /* new_hash2 */,
+ &head, &head2);
+
+ /* Keep both hash keys in same table */
+ hash_key = READ_ONCE(cp->hn0.hash_key);
+ WRITE_ONCE(cp->hn1.hash_key, hash_key);
+ hlist_bl_del_rcu(&cp->hn1.node);
+ cp->flags = new_flags;
+
+ conn_tab_unlock(head, head2);
+ } else {
+ /* Start double hashing */
+
+ hash_key = READ_ONCE(cp->hn0.hash_key);
+
+ t2 = rcu_dereference(t->new_tbl);
+ hash2 = ip_vs_conn_hashkey_conn(t2, cp, true);
+ hash_key2 = ip_vs_rht_build_hash_key(t2, hash2);
+
+ /* Change the forwarding method under locked hn0 */
+ conn_tab_lock(t, t2, cp, hash_key, hash_key2, true /* use2 */,
+ false /* new_hash */, true /* new_hash2 */,
+ &head, &head2);
+
+ WRITE_ONCE(cp->hn1.hash_key, hash_key2);
+ cp->flags = new_flags;
+ hlist_bl_add_head_rcu(&cp->hn1.node, head2);
+
+ conn_tab_unlock(head, head2);
+ }
+}
+
/* Get default load factor to map conn_count/u_thresh to t->size */
static int ip_vs_conn_default_load_factor(struct netns_ipvs *ipvs)
{
@@ -1024,9 +1120,18 @@ ip_vs_bind_dest(struct ip_vs_conn *cp, struct ip_vs_dest *dest)
conn_flags &= ~IP_VS_CONN_F_INACTIVE;
/* connections inherit forwarding method from dest */
flags &= ~(IP_VS_CONN_F_FWD_MASK | IP_VS_CONN_F_NOOUTPUT);
+ flags |= conn_flags;
+ /* Changing forwarding method for hashed conn can
+ * happen only under locks
+ */
+ if (cp->flags & IP_VS_CONN_F_HASHED)
+ ip_vs_conn_change_fwd_mask(cp, flags);
+ else
+ cp->flags = flags;
+ } else {
+ flags |= conn_flags;
+ cp->flags = flags;
}
- flags |= conn_flags;
- cp->flags = flags;
cp->dest = dest;
IP_VS_DBG_BUF(7, "Bind-dest %s c:%s:%d v:%s:%d "
--
2.47.3
^ permalink raw reply related [flat|nested] 19+ messages in thread
* [PATCH net 07/13] netfilter: nf_tables: make nft_object rhltable per table
2026-07-22 21:14 [PATCH net 00/13] Netfilter/IPVS fixes for net Pablo Neira Ayuso
` (5 preceding siblings ...)
2026-07-22 21:14 ` [PATCH net 06/13] ipvs: adjust double hashing when fwd method changes Pablo Neira Ayuso
@ 2026-07-22 21:14 ` Pablo Neira Ayuso
2026-07-22 21:14 ` [PATCH net 08/13] netfilter: xt_hashlimit: validate hashtable supports XT_HASHLIMIT_RATE_MATCH Pablo Neira Ayuso
` (5 subsequent siblings)
12 siblings, 0 replies; 19+ messages in thread
From: Pablo Neira Ayuso @ 2026-07-22 21:14 UTC (permalink / raw)
To: netfilter-devel; +Cc: davem, netdev, kuba, pabeni, edumazet, fw, horms
The nft_object rhltable is global, this allows for accessing objects
that are being dismangled from lookup path by other existing netns.
Given the nft_obj_destroy() releases the object inmediately, this might
lead to use-after-free of these objects that are being released.
Make the existing rhltable per table to address this issue to deal with
with the nft_rcv_nl_event() path too.
Update nft_obj_lookup() to take the table as non-const, otherwise,
compiler complains when passing the objname_ht to rhltable_lookup().
Fixes: 4d44175aa5bb ("netfilter: nf_tables: handle nft_object lookups via rhltable")
Suggested-by: Florian Westphal <fw@strlen.de>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
---
include/net/netfilter/nf_tables.h | 4 +++-
net/netfilter/nf_tables_api.c | 34 +++++++++++++++----------------
2 files changed, 19 insertions(+), 19 deletions(-)
diff --git a/include/net/netfilter/nf_tables.h b/include/net/netfilter/nf_tables.h
index 9d844354c4d9..3be612145c13 100644
--- a/include/net/netfilter/nf_tables.h
+++ b/include/net/netfilter/nf_tables.h
@@ -1294,6 +1294,7 @@ static inline void nft_use_inc_restore(u32 *use)
* @sets: sets in the table
* @objects: stateful objects in the table
* @flowtables: flow tables in the table
+ * @objname_ht: hashtable for objects lookup by name
* @hgenerator: handle generator state
* @handle: table handle
* @use: number of chain references to this table
@@ -1313,6 +1314,7 @@ struct nft_table {
struct list_head sets;
struct list_head objects;
struct list_head flowtables;
+ struct rhltable objname_ht;
u64 hgenerator;
u64 handle;
u32 use;
@@ -1400,7 +1402,7 @@ static inline void *nft_obj_data(const struct nft_object *obj)
#define nft_expr_obj(expr) *((struct nft_object **)nft_expr_priv(expr))
struct nft_object *nft_obj_lookup(const struct net *net,
- const struct nft_table *table,
+ struct nft_table *table,
const struct nlattr *nla, u32 objtype,
u8 genmask);
diff --git a/net/netfilter/nf_tables_api.c b/net/netfilter/nf_tables_api.c
index a9eaf9455c77..af357f6c5070 100644
--- a/net/netfilter/nf_tables_api.c
+++ b/net/netfilter/nf_tables_api.c
@@ -45,8 +45,6 @@ enum {
NFT_VALIDATE_DO,
};
-static struct rhltable nft_objname_ht;
-
static u32 nft_chain_hash(const void *data, u32 len, u32 seed);
static u32 nft_chain_hash_obj(const void *data, u32 len, u32 seed);
static int nft_chain_hash_cmp(struct rhashtable_compare_arg *, const void *);
@@ -1635,6 +1633,10 @@ static int nf_tables_newtable(struct sk_buff *skb, const struct nfnl_info *info,
if (err)
goto err_chain_ht;
+ err = rhltable_init(&table->objname_ht, &nft_objname_ht_params);
+ if (err < 0)
+ goto err_obj_ht;
+
INIT_LIST_HEAD(&table->chains);
INIT_LIST_HEAD(&table->sets);
INIT_LIST_HEAD(&table->objects);
@@ -1653,6 +1655,8 @@ static int nf_tables_newtable(struct sk_buff *skb, const struct nfnl_info *info,
list_add_tail_rcu(&table->list, &nft_net->tables);
return 0;
err_trans:
+ rhltable_destroy(&table->objname_ht);
+err_obj_ht:
rhltable_destroy(&table->chains_ht);
err_chain_ht:
kfree(table->udata);
@@ -1819,6 +1823,7 @@ static void nf_tables_table_destroy(struct nft_table *table)
return;
rhltable_destroy(&table->chains_ht);
+ rhltable_destroy(&table->objname_ht);
kfree(table->name);
kfree(table->udata);
kfree(table);
@@ -8086,7 +8091,7 @@ void nft_unregister_obj(struct nft_object_type *obj_type)
EXPORT_SYMBOL_GPL(nft_unregister_obj);
struct nft_object *nft_obj_lookup(const struct net *net,
- const struct nft_table *table,
+ struct nft_table *table,
const struct nlattr *nla, u32 objtype,
u8 genmask)
{
@@ -8102,7 +8107,7 @@ struct nft_object *nft_obj_lookup(const struct net *net,
!lockdep_commit_lock_is_held(net));
rcu_read_lock();
- list = rhltable_lookup(&nft_objname_ht, &k, nft_objname_ht_params);
+ list = rhltable_lookup(&table->objname_ht, &k, nft_objname_ht_params);
if (!list)
goto out;
@@ -8382,7 +8387,7 @@ static int nf_tables_newobj(struct sk_buff *skb, const struct nfnl_info *info,
if (err < 0)
goto err_trans;
- err = rhltable_insert(&nft_objname_ht, &obj->rhlhead,
+ err = rhltable_insert(&table->objname_ht, &obj->rhlhead,
nft_objname_ht_params);
if (err < 0)
goto err_obj_ht;
@@ -8567,8 +8572,8 @@ nf_tables_getobj_single(u32 portid, const struct nfnl_info *info,
struct netlink_ext_ack *extack = info->extack;
u8 genmask = nft_genmask_cur(info->net);
u8 family = info->nfmsg->nfgen_family;
- const struct nft_table *table;
struct net *net = info->net;
+ struct nft_table *table;
struct nft_object *obj;
struct sk_buff *skb2;
u32 objtype;
@@ -10437,9 +10442,9 @@ static void nf_tables_commit_chain(struct net *net, struct nft_chain *chain)
nf_tables_commit_chain_free_rules_old(g0);
}
-static void nft_obj_del(struct nft_object *obj)
+static void nft_obj_del(struct nft_table *table, struct nft_object *obj)
{
- rhltable_remove(&nft_objname_ht, &obj->rhlhead, nft_objname_ht_params);
+ rhltable_remove(&table->objname_ht, &obj->rhlhead, nft_objname_ht_params);
list_del_rcu(&obj->list);
}
@@ -11124,7 +11129,7 @@ static int nf_tables_commit(struct net *net, struct sk_buff *skb)
break;
case NFT_MSG_DELOBJ:
case NFT_MSG_DESTROYOBJ:
- nft_obj_del(nft_trans_obj(trans));
+ nft_obj_del(table, nft_trans_obj(trans));
nf_tables_obj_notify(&ctx, nft_trans_obj(trans),
trans->msg_type);
break;
@@ -11416,7 +11421,7 @@ static int __nf_tables_abort(struct net *net, enum nfnl_abort_action action)
nft_trans_destroy(trans);
} else {
nft_use_dec_restore(&table->use);
- nft_obj_del(nft_trans_obj(trans));
+ nft_obj_del(table, nft_trans_obj(trans));
}
break;
case NFT_MSG_DELOBJ:
@@ -12043,7 +12048,7 @@ static void __nft_release_table(struct net *net, struct nft_table *table)
nft_set_destroy(&ctx, set);
}
list_for_each_entry_safe(obj, ne, &table->objects, list) {
- nft_obj_del(obj);
+ nft_obj_del(table, obj);
nft_use_dec(&table->use);
nft_obj_destroy(&ctx, obj);
}
@@ -12225,10 +12230,6 @@ static int __init nf_tables_module_init(void)
if (err < 0)
goto err_netdev_notifier;
- err = rhltable_init(&nft_objname_ht, &nft_objname_ht_params);
- if (err < 0)
- goto err_rht_objname;
-
err = nft_offload_init();
if (err < 0)
goto err_offload;
@@ -12251,8 +12252,6 @@ static int __init nf_tables_module_init(void)
err_netlink_notifier:
nft_offload_exit();
err_offload:
- rhltable_destroy(&nft_objname_ht);
-err_rht_objname:
unregister_netdevice_notifier(&nf_tables_flowtable_notifier);
err_netdev_notifier:
nf_tables_core_module_exit();
@@ -12274,7 +12273,6 @@ static void __exit nf_tables_module_exit(void)
unregister_pernet_subsys(&nf_tables_net_ops);
cancel_work_sync(&trans_gc_work);
rcu_barrier();
- rhltable_destroy(&nft_objname_ht);
nf_tables_core_module_exit();
}
--
2.47.3
^ permalink raw reply related [flat|nested] 19+ messages in thread
* [PATCH net 08/13] netfilter: xt_hashlimit: validate hashtable supports XT_HASHLIMIT_RATE_MATCH
2026-07-22 21:14 [PATCH net 00/13] Netfilter/IPVS fixes for net Pablo Neira Ayuso
` (6 preceding siblings ...)
2026-07-22 21:14 ` [PATCH net 07/13] netfilter: nf_tables: make nft_object rhltable per table Pablo Neira Ayuso
@ 2026-07-22 21:14 ` Pablo Neira Ayuso
2026-07-22 21:14 ` [PATCH net 09/13] ipvs: fix the checksum validations Pablo Neira Ayuso
` (4 subsequent siblings)
12 siblings, 0 replies; 19+ messages in thread
From: Pablo Neira Ayuso @ 2026-07-22 21:14 UTC (permalink / raw)
To: netfilter-devel; +Cc: davem, netdev, kuba, pabeni, edumazet, fw, horms
The XT_HASHLIMIT_RATE_MATCH flag mode changes the semantics of the
dsthash_ent structure which represents an entry in the hashtable. There
is a union area which uses a different layout to express the rate match
mode.
Update .checkentry path to validate the XT_HASHLIMIT_RATE_MATCH mode
flag is requested by two or more different rules that refer to the same
hashtable. Otherwise, uninitialized access to the burst field in the
union is possible.
Reject the use of the XT_HASHLIMIT_RATE_MATCH mode flag if set on by
revision less than 3 too.
Fixes: bea74641e378 ("netfilter: xt_hashlimit: add rate match mode")
Reported-and-tested-by: Talha Berk Arslan <talha.anything.info@gmail.com>
Link: https://patch.msgid.link/20260721074629.668-1-talha.anything.info@gmail.com/
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
---
net/netfilter/xt_hashlimit.c | 16 +++++++++++++++-
1 file changed, 15 insertions(+), 1 deletion(-)
diff --git a/net/netfilter/xt_hashlimit.c b/net/netfilter/xt_hashlimit.c
index 2704b4b60d1e..9af0fa895f73 100644
--- a/net/netfilter/xt_hashlimit.c
+++ b/net/netfilter/xt_hashlimit.c
@@ -117,6 +117,7 @@ struct xt_hashlimit_htable {
refcount_t use;
u_int8_t family;
bool rnd_initialized;
+ bool ratematch;
struct hashlimit_cfg3 cfg; /* config */
@@ -323,6 +324,7 @@ static int htable_create(struct net *net, struct hashlimit_cfg3 *cfg,
kvfree(hinfo);
return -ENOMEM;
}
+ hinfo->ratematch = !!(cfg->mode & XT_HASHLIMIT_RATE_MATCH);
spin_lock_init(&hinfo->lock);
switch (revision) {
@@ -872,7 +874,10 @@ static int hashlimit_mt_check_common(const struct xt_mtchk_param *par,
}
/* Check for overflow. */
- if (revision >= 3 && cfg->mode & XT_HASHLIMIT_RATE_MATCH) {
+ if (cfg->mode & XT_HASHLIMIT_RATE_MATCH) {
+ if (revision < 3)
+ return -EINVAL;
+
if (cfg->avg == 0 || cfg->avg > U32_MAX) {
pr_info_ratelimited("invalid rate\n");
return -ERANGE;
@@ -905,6 +910,15 @@ static int hashlimit_mt_check_common(const struct xt_mtchk_param *par,
mutex_unlock(&hashlimit_mutex);
return ret;
}
+ } else {
+ if ((cfg->mode & XT_HASHLIMIT_RATE_MATCH &&
+ !(*hinfo)->ratematch) ||
+ (!(cfg->mode & XT_HASHLIMIT_RATE_MATCH) &&
+ (*hinfo)->ratematch)) {
+ mutex_unlock(&hashlimit_mutex);
+ htable_put(*hinfo);
+ return -EINVAL;
+ }
}
mutex_unlock(&hashlimit_mutex);
--
2.47.3
^ permalink raw reply related [flat|nested] 19+ messages in thread
* [PATCH net 09/13] ipvs: fix the checksum validations
2026-07-22 21:14 [PATCH net 00/13] Netfilter/IPVS fixes for net Pablo Neira Ayuso
` (7 preceding siblings ...)
2026-07-22 21:14 ` [PATCH net 08/13] netfilter: xt_hashlimit: validate hashtable supports XT_HASHLIMIT_RATE_MATCH Pablo Neira Ayuso
@ 2026-07-22 21:14 ` Pablo Neira Ayuso
2026-07-23 14:08 ` Jakub Kicinski
2026-07-22 21:14 ` [PATCH net 10/13] ipvs: fix places with wrong packet offsets Pablo Neira Ayuso
` (3 subsequent siblings)
12 siblings, 1 reply; 19+ messages in thread
From: Pablo Neira Ayuso @ 2026-07-22 21:14 UTC (permalink / raw)
To: netfilter-devel; +Cc: davem, netdev, kuba, pabeni, edumazet, fw, horms
From: Julian Anastasov <ja@ssi.bg>
ip_vs_in_icmp_v6() is missing checksum validation for ICMPv6
packets from clients. In fact, as for TCP/UDP we should
validate the checksum for ICMP packets only when we
mangle the packets on MASQ or on reply for tunnel.
Also, Sashiko points out that handle_response_icmp() being
common for IPv4 and IPv6 is missing the pseudo-header
calculation while validating ICMPv6 messages from real
servers which is a problem if checksum is not validated
by the hardware.
Fix the problems by creating ip_vs_checksum_common_check()
helper and use it for TCP/UDP/ICMP both for IPv4 and IPv6.
Rely on the nf_checksum() for validating the ICMP messages
but use it also for TCP and UDP.
Use correct IP offset for IP_VS_DBG_RL_PKT for TCP/UDP/SCTP.
IPVS packets (TCP/UDP/SCTP/ICMP) do not need checksum
validation on LOCAL_OUT (local clients or local real
servers) and on FORWARD (traffic from servers on LAN).
Do it only on LOCAL_IN, in case nf_checksum() is not
called on PRE_ROUTING.
Also, ip_vs_checksum_complete() can be marked static.
Fixes: 2a3b791e6e11 ("IPVS: Add/adjust Netfilter hook functions and helpers for v6")
Link: https://sashiko.dev/#/patchset/20260708180315.77413-1-ja%40ssi.bg
Signed-off-by: Julian Anastasov <ja@ssi.bg>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
---
include/net/ip_vs.h | 34 ++++++++++++++++--
net/netfilter/ipvs/ip_vs_core.c | 20 +++++++++--
net/netfilter/ipvs/ip_vs_proto_sctp.c | 15 ++++----
net/netfilter/ipvs/ip_vs_proto_tcp.c | 44 +++++------------------
net/netfilter/ipvs/ip_vs_proto_udp.c | 50 ++++++---------------------
5 files changed, 77 insertions(+), 86 deletions(-)
diff --git a/include/net/ip_vs.h b/include/net/ip_vs.h
index 417ff51f62fc..37df7477eaaf 100644
--- a/include/net/ip_vs.h
+++ b/include/net/ip_vs.h
@@ -25,7 +25,9 @@
#include <linux/netfilter.h> /* for union nf_inet_addr */
#include <linux/ip.h>
#include <linux/ipv6.h> /* for struct ipv6hdr */
+#include <net/route.h>
#include <net/ipv6.h>
+#include <net/ip6_fib.h>
#if IS_ENABLED(CONFIG_NF_CONNTRACK)
#include <net/netfilter/nf_conntrack.h>
#endif
@@ -2066,8 +2068,6 @@ void ip_vs_nat_icmp_v6(struct sk_buff *skb, struct ip_vs_protocol *pp,
struct ip_vs_conn *cp, int dir);
#endif
-__sum16 ip_vs_checksum_complete(struct sk_buff *skb, int offset);
-
static inline __wsum ip_vs_check_diff4(__be32 old, __be32 new, __wsum oldsum)
{
__be32 diff[2] = { ~old, new };
@@ -2093,6 +2093,36 @@ static inline __wsum ip_vs_check_diff2(__be16 old, __be16 new, __wsum oldsum)
return csum_partial(diff, sizeof(diff), oldsum);
}
+static inline bool ip_vs_checksum_needed(struct sk_buff *skb, int af)
+{
+ /* Checksum unnecessary or already validated? */
+ if (skb_csum_unnecessary(skb))
+ return false;
+ /* LOCAL_OUT ? */
+ if (!skb->dev || skb->dev->flags & IFF_LOOPBACK)
+ return false;
+ /* !LOCAL_IN (FORWARD) ? */
+ if (af == AF_INET6) {
+ if (!(dst_rt6_info(skb_dst(skb))->rt6i_flags & RTF_LOCAL))
+ return false;
+ } else {
+ if (!(skb_rtable(skb)->rt_flags & RTCF_LOCAL))
+ return false;
+ }
+ return true;
+}
+
+/**
+ * ip_vs_checksum_common_check - validate checksum for TCP/UDP/ICMP
+ */
+static inline bool ip_vs_checksum_common_check(struct sk_buff *skb,
+ int offset, int proto, int af)
+{
+ if (!ip_vs_checksum_needed(skb, af))
+ return true;
+ return !nf_checksum(skb, NF_INET_LOCAL_IN, offset, proto, af);
+}
+
/* Forget current conntrack (unconfirmed) and attach notrack entry */
static inline void ip_vs_notrack(struct sk_buff *skb)
{
diff --git a/net/netfilter/ipvs/ip_vs_core.c b/net/netfilter/ipvs/ip_vs_core.c
index bafab93451d0..c8b512725e6e 100644
--- a/net/netfilter/ipvs/ip_vs_core.c
+++ b/net/netfilter/ipvs/ip_vs_core.c
@@ -867,7 +867,7 @@ static int sysctl_nat_icmp_send(struct netns_ipvs *ipvs) { return 0; }
#endif
-__sum16 ip_vs_checksum_complete(struct sk_buff *skb, int offset)
+static __sum16 ip_vs_checksum_complete(struct sk_buff *skb, int offset)
{
return csum_fold(skb_checksum(skb, offset, skb->len - offset, 0));
}
@@ -1038,13 +1038,14 @@ static int handle_response_icmp(int af, struct sk_buff *skb,
unsigned int offset, unsigned int ihl,
unsigned int hooknum)
{
+ int iproto = af == AF_INET6 ? IPPROTO_ICMPV6 : IPPROTO_ICMP;
unsigned int verdict = NF_DROP;
if (IP_VS_FWD_METHOD(cp) != IP_VS_CONN_F_MASQ)
goto after_nat;
/* Ensure the checksum is correct */
- if (!skb_csum_unnecessary(skb) && ip_vs_checksum_complete(skb, ihl)) {
+ if (!ip_vs_checksum_common_check(skb, ihl, iproto, af)) {
/* Failed checksum! */
IP_VS_DBG_BUF(1, "Forward ICMP: failed checksum from %s!\n",
IP_VS_DBG_ADDR(af, snet));
@@ -1898,7 +1899,8 @@ ip_vs_in_icmp(struct netns_ipvs *ipvs, struct sk_buff *skb, int *related,
verdict = NF_DROP;
/* Ensure the checksum is correct */
- if (!skb_csum_unnecessary(skb) && ip_vs_checksum_complete(skb, ihl)) {
+ if ((IP_VS_FWD_METHOD(cp) == IP_VS_CONN_F_MASQ || tunnel) &&
+ !ip_vs_checksum_common_check(skb, ihl, IPPROTO_ICMP, AF_INET)) {
/* Failed checksum! */
IP_VS_DBG(1, "Incoming ICMP: failed checksum from %pI4!\n",
&iph->saddr);
@@ -2064,6 +2066,18 @@ static int ip_vs_in_icmp_v6(struct netns_ipvs *ipvs, struct sk_buff *skb,
goto out;
}
+ verdict = NF_DROP;
+
+ /* Ensure the checksum is correct */
+ if (IP_VS_FWD_METHOD(cp) == IP_VS_CONN_F_MASQ &&
+ !ip_vs_checksum_common_check(skb, iph->len, IPPROTO_ICMPV6,
+ AF_INET6)) {
+ /* Failed checksum! */
+ IP_VS_DBG(1, "Incoming ICMPv6: failed checksum from %pI6c!\n",
+ &iph->saddr);
+ goto out;
+ }
+
/* do the statistics and put it back */
ip_vs_in_stats(cp, skb);
diff --git a/net/netfilter/ipvs/ip_vs_proto_sctp.c b/net/netfilter/ipvs/ip_vs_proto_sctp.c
index c67317be17df..f6f732b7dfa8 100644
--- a/net/netfilter/ipvs/ip_vs_proto_sctp.c
+++ b/net/netfilter/ipvs/ip_vs_proto_sctp.c
@@ -11,7 +11,7 @@
static int
sctp_csum_check(int af, struct sk_buff *skb, struct ip_vs_protocol *pp,
- unsigned int sctphoff);
+ struct ip_vs_iphdr *iph);
static int
sctp_conn_schedule(struct netns_ipvs *ipvs, int af, struct sk_buff *skb,
@@ -109,7 +109,7 @@ sctp_snat_handler(struct sk_buff *skb, struct ip_vs_protocol *pp,
int ret;
/* Some checks before mangling */
- if (!sctp_csum_check(cp->af, skb, pp, sctphoff))
+ if (!sctp_csum_check(cp->af, skb, pp, iph))
return 0;
/* Call application helper if needed */
@@ -157,7 +157,7 @@ sctp_dnat_handler(struct sk_buff *skb, struct ip_vs_protocol *pp,
int ret;
/* Some checks before mangling */
- if (!sctp_csum_check(cp->af, skb, pp, sctphoff))
+ if (!sctp_csum_check(cp->af, skb, pp, iph))
return 0;
/* Call application helper if needed */
@@ -187,19 +187,22 @@ sctp_dnat_handler(struct sk_buff *skb, struct ip_vs_protocol *pp,
static int
sctp_csum_check(int af, struct sk_buff *skb, struct ip_vs_protocol *pp,
- unsigned int sctphoff)
+ struct ip_vs_iphdr *iph)
{
+ unsigned int sctphoff = iph->len;
struct sctphdr *sh;
__le32 cmp, val;
+ if (!ip_vs_checksum_needed(skb, af))
+ return 1;
sh = (struct sctphdr *)(skb->data + sctphoff);
cmp = sh->checksum;
val = sctp_compute_cksum(skb, sctphoff);
if (val != cmp) {
/* CRC failure, dump it. */
- IP_VS_DBG_RL_PKT(0, af, pp, skb, 0,
- "Failed checksum for");
+ IP_VS_DBG_RL_PKT(0, af, pp, skb, iph->off,
+ "Failed checksum for");
return 0;
}
return 1;
diff --git a/net/netfilter/ipvs/ip_vs_proto_tcp.c b/net/netfilter/ipvs/ip_vs_proto_tcp.c
index f86b763efcc4..533fce3e5e4e 100644
--- a/net/netfilter/ipvs/ip_vs_proto_tcp.c
+++ b/net/netfilter/ipvs/ip_vs_proto_tcp.c
@@ -29,7 +29,7 @@
static int
tcp_csum_check(int af, struct sk_buff *skb, struct ip_vs_protocol *pp,
- unsigned int tcphoff);
+ struct ip_vs_iphdr *iph);
static int
tcp_conn_schedule(struct netns_ipvs *ipvs, int af, struct sk_buff *skb,
@@ -166,7 +166,7 @@ tcp_snat_handler(struct sk_buff *skb, struct ip_vs_protocol *pp,
int ret;
/* Some checks before mangling */
- if (!tcp_csum_check(cp->af, skb, pp, tcphoff))
+ if (!tcp_csum_check(cp->af, skb, pp, iph))
return 0;
/* Call application helper if needed */
@@ -244,7 +244,7 @@ tcp_dnat_handler(struct sk_buff *skb, struct ip_vs_protocol *pp,
int ret;
/* Some checks before mangling */
- if (!tcp_csum_check(cp->af, skb, pp, tcphoff))
+ if (!tcp_csum_check(cp->af, skb, pp, iph))
return 0;
/*
@@ -302,41 +302,13 @@ tcp_dnat_handler(struct sk_buff *skb, struct ip_vs_protocol *pp,
static int
tcp_csum_check(int af, struct sk_buff *skb, struct ip_vs_protocol *pp,
- unsigned int tcphoff)
+ struct ip_vs_iphdr *iph)
{
- switch (skb->ip_summed) {
- case CHECKSUM_NONE:
- skb->csum = skb_checksum(skb, tcphoff, skb->len - tcphoff, 0);
- fallthrough;
- case CHECKSUM_COMPLETE:
-#ifdef CONFIG_IP_VS_IPV6
- if (af == AF_INET6) {
- if (csum_ipv6_magic(&ipv6_hdr(skb)->saddr,
- &ipv6_hdr(skb)->daddr,
- skb->len - tcphoff,
- IPPROTO_TCP,
- skb->csum)) {
- IP_VS_DBG_RL_PKT(0, af, pp, skb, 0,
- "Failed checksum for");
- return 0;
- }
- } else
-#endif
- if (csum_tcpudp_magic(ip_hdr(skb)->saddr,
- ip_hdr(skb)->daddr,
- skb->len - tcphoff,
- ip_hdr(skb)->protocol,
- skb->csum)) {
- IP_VS_DBG_RL_PKT(0, af, pp, skb, 0,
- "Failed checksum for");
- return 0;
- }
- break;
- default:
- /* No need to checksum. */
- break;
+ if (!ip_vs_checksum_common_check(skb, iph->len, IPPROTO_TCP, af)) {
+ IP_VS_DBG_RL_PKT(0, af, pp, skb, iph->off,
+ "Failed checksum for");
+ return 0;
}
-
return 1;
}
diff --git a/net/netfilter/ipvs/ip_vs_proto_udp.c b/net/netfilter/ipvs/ip_vs_proto_udp.c
index 58f9e255927e..de3597347542 100644
--- a/net/netfilter/ipvs/ip_vs_proto_udp.c
+++ b/net/netfilter/ipvs/ip_vs_proto_udp.c
@@ -25,7 +25,7 @@
static int
udp_csum_check(int af, struct sk_buff *skb, struct ip_vs_protocol *pp,
- unsigned int udphoff);
+ struct ip_vs_iphdr *iph);
static int
udp_conn_schedule(struct netns_ipvs *ipvs, int af, struct sk_buff *skb,
@@ -155,7 +155,7 @@ udp_snat_handler(struct sk_buff *skb, struct ip_vs_protocol *pp,
int ret;
/* Some checks before mangling */
- if (!udp_csum_check(cp->af, skb, pp, udphoff))
+ if (!udp_csum_check(cp->af, skb, pp, iph))
return 0;
/*
@@ -238,7 +238,7 @@ udp_dnat_handler(struct sk_buff *skb, struct ip_vs_protocol *pp,
int ret;
/* Some checks before mangling */
- if (!udp_csum_check(cp->af, skb, pp, udphoff))
+ if (!udp_csum_check(cp->af, skb, pp, iph))
return 0;
/*
@@ -298,48 +298,20 @@ udp_dnat_handler(struct sk_buff *skb, struct ip_vs_protocol *pp,
static int
udp_csum_check(int af, struct sk_buff *skb, struct ip_vs_protocol *pp,
- unsigned int udphoff)
+ struct ip_vs_iphdr *iph)
{
struct udphdr _udph, *uh;
- uh = skb_header_pointer(skb, udphoff, sizeof(_udph), &_udph);
+ uh = skb_header_pointer(skb, iph->len, sizeof(_udph), &_udph);
if (uh == NULL)
return 0;
- if (uh->check != 0) {
- switch (skb->ip_summed) {
- case CHECKSUM_NONE:
- skb->csum = skb_checksum(skb, udphoff,
- skb->len - udphoff, 0);
- fallthrough;
- case CHECKSUM_COMPLETE:
-#ifdef CONFIG_IP_VS_IPV6
- if (af == AF_INET6) {
- if (csum_ipv6_magic(&ipv6_hdr(skb)->saddr,
- &ipv6_hdr(skb)->daddr,
- skb->len - udphoff,
- IPPROTO_UDP,
- skb->csum)) {
- IP_VS_DBG_RL_PKT(0, af, pp, skb, 0,
- "Failed checksum for");
- return 0;
- }
- } else
-#endif
- if (csum_tcpudp_magic(ip_hdr(skb)->saddr,
- ip_hdr(skb)->daddr,
- skb->len - udphoff,
- ip_hdr(skb)->protocol,
- skb->csum)) {
- IP_VS_DBG_RL_PKT(0, af, pp, skb, 0,
- "Failed checksum for");
- return 0;
- }
- break;
- default:
- /* No need to checksum. */
- break;
- }
+ if (!uh->check)
+ return 1;
+ if (!ip_vs_checksum_common_check(skb, iph->len, IPPROTO_UDP, af)) {
+ IP_VS_DBG_RL_PKT(0, af, pp, skb, iph->off,
+ "Failed checksum for");
+ return 0;
}
return 1;
}
--
2.47.3
^ permalink raw reply related [flat|nested] 19+ messages in thread
* [PATCH net 10/13] ipvs: fix places with wrong packet offsets
2026-07-22 21:14 [PATCH net 00/13] Netfilter/IPVS fixes for net Pablo Neira Ayuso
` (8 preceding siblings ...)
2026-07-22 21:14 ` [PATCH net 09/13] ipvs: fix the checksum validations Pablo Neira Ayuso
@ 2026-07-22 21:14 ` Pablo Neira Ayuso
2026-07-22 21:14 ` [PATCH net 11/13] ipvs: do not mangle ICMP replies for non-first fragments Pablo Neira Ayuso
` (2 subsequent siblings)
12 siblings, 0 replies; 19+ messages in thread
From: Pablo Neira Ayuso @ 2026-07-22 21:14 UTC (permalink / raw)
To: netfilter-devel; +Cc: davem, netdev, kuba, pabeni, edumazet, fw, horms
From: Julian Anastasov <ja@ssi.bg>
The offsets we use to packet headers and payloads should be
based on skb->data. We even already respect non-zero
network offset in ip_vs_fill_iph_skb() but some places
do it wrongly and support only zero offset which is expected
for the IP layer where IPVS has hooks.
Change all places that instead of skb->data use offsets based
on the network header (skb_network_header, ip_hdr, etc) because
this doubles the network offset as noted by Sashiko.
For ip_vs_nat_icmp_v6() we can even rely on the IPv6 header
parsing done by the caller.
Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Link: https://sashiko.dev/#/patchset/20260710143733.29741-2-fw%40strlen.de
Signed-off-by: Julian Anastasov <ja@ssi.bg>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
---
include/net/ip_vs.h | 15 +--
net/netfilter/ipvs/ip_vs_app.c | 4 +-
net/netfilter/ipvs/ip_vs_core.c | 133 +++++++++++++-------------
net/netfilter/ipvs/ip_vs_proto_sctp.c | 4 +-
net/netfilter/ipvs/ip_vs_proto_tcp.c | 4 +-
net/netfilter/ipvs/ip_vs_proto_udp.c | 4 +-
net/netfilter/ipvs/ip_vs_xmit.c | 26 ++---
7 files changed, 97 insertions(+), 93 deletions(-)
diff --git a/include/net/ip_vs.h b/include/net/ip_vs.h
index 37df7477eaaf..c684a46ce15a 100644
--- a/include/net/ip_vs.h
+++ b/include/net/ip_vs.h
@@ -1974,8 +1974,9 @@ int ip_vs_tunnel_xmit(struct sk_buff *skb, struct ip_vs_conn *cp,
int ip_vs_dr_xmit(struct sk_buff *skb, struct ip_vs_conn *cp,
struct ip_vs_protocol *pp, struct ip_vs_iphdr *iph);
int ip_vs_icmp_xmit(struct sk_buff *skb, struct ip_vs_conn *cp,
- struct ip_vs_protocol *pp, int offset,
- unsigned int hooknum, struct ip_vs_iphdr *iph);
+ struct ip_vs_protocol *pp, unsigned int toff,
+ unsigned int wlen, unsigned int hooknum,
+ struct ip_vs_iphdr *ciph);
void ip_vs_dest_dst_rcu_free(struct rcu_head *head);
#ifdef CONFIG_IP_VS_IPV6
@@ -1988,8 +1989,9 @@ int ip_vs_tunnel_xmit_v6(struct sk_buff *skb, struct ip_vs_conn *cp,
int ip_vs_dr_xmit_v6(struct sk_buff *skb, struct ip_vs_conn *cp,
struct ip_vs_protocol *pp, struct ip_vs_iphdr *iph);
int ip_vs_icmp_xmit_v6(struct sk_buff *skb, struct ip_vs_conn *cp,
- struct ip_vs_protocol *pp, int offset,
- unsigned int hooknum, struct ip_vs_iphdr *iph);
+ struct ip_vs_protocol *pp, unsigned int toff,
+ unsigned int wlen, unsigned int hooknum,
+ struct ip_vs_iphdr *ciph);
#endif
#ifdef CONFIG_SYSCTL
@@ -2061,11 +2063,12 @@ static inline bool ip_vs_conn_use_hash2(struct ip_vs_conn *cp)
}
void ip_vs_nat_icmp(struct sk_buff *skb, struct ip_vs_protocol *pp,
- struct ip_vs_conn *cp, int dir);
+ struct ip_vs_conn *cp, int dir, unsigned int toff);
#ifdef CONFIG_IP_VS_IPV6
void ip_vs_nat_icmp_v6(struct sk_buff *skb, struct ip_vs_protocol *pp,
- struct ip_vs_conn *cp, int dir);
+ struct ip_vs_conn *cp, int dir, unsigned int toff,
+ struct ip_vs_iphdr *ciph);
#endif
static inline __wsum ip_vs_check_diff4(__be32 old, __be32 new, __wsum oldsum)
diff --git a/net/netfilter/ipvs/ip_vs_app.c b/net/netfilter/ipvs/ip_vs_app.c
index b0e00be85cb1..11cbdbaf561d 100644
--- a/net/netfilter/ipvs/ip_vs_app.c
+++ b/net/netfilter/ipvs/ip_vs_app.c
@@ -367,7 +367,7 @@ static inline int app_tcp_pkt_out(struct ip_vs_conn *cp, struct sk_buff *skb,
if (skb_ensure_writable(skb, ipvsh->len + sizeof(*th)))
return 0;
- th = (struct tcphdr *)(skb_network_header(skb) + ipvsh->len);
+ th = (struct tcphdr *)(skb->data + ipvsh->len);
/*
* Remember seq number in case this pkt gets resized
@@ -443,7 +443,7 @@ static inline int app_tcp_pkt_in(struct ip_vs_conn *cp, struct sk_buff *skb,
if (skb_ensure_writable(skb, ipvsh->len + sizeof(*th)))
return 0;
- th = (struct tcphdr *)(skb_network_header(skb) + ipvsh->len);
+ th = (struct tcphdr *)(skb->data + ipvsh->len);
/*
* Remember seq number in case this pkt gets resized
diff --git a/net/netfilter/ipvs/ip_vs_core.c b/net/netfilter/ipvs/ip_vs_core.c
index c8b512725e6e..cd5eb71543ec 100644
--- a/net/netfilter/ipvs/ip_vs_core.c
+++ b/net/netfilter/ipvs/ip_vs_core.c
@@ -924,13 +924,12 @@ static int ip_vs_route_me_harder(struct netns_ipvs *ipvs, int af,
* - inout: 1=in->out, 0=out->in
*/
void ip_vs_nat_icmp(struct sk_buff *skb, struct ip_vs_protocol *pp,
- struct ip_vs_conn *cp, int inout)
+ struct ip_vs_conn *cp, int inout, unsigned int toff)
{
struct iphdr *iph = ip_hdr(skb);
- unsigned int icmp_offset = iph->ihl*4;
- struct icmphdr *icmph = (struct icmphdr *)(skb_network_header(skb) +
- icmp_offset);
+ struct icmphdr *icmph = (struct icmphdr *)(skb->data + toff);
struct iphdr *ciph = (struct iphdr *)(icmph + 1);
+ unsigned int coff __maybe_unused = toff + sizeof(struct icmphdr);
if (inout) {
iph->saddr = cp->vaddr.ip;
@@ -957,48 +956,45 @@ void ip_vs_nat_icmp(struct sk_buff *skb, struct ip_vs_protocol *pp,
/* And finally the ICMP checksum */
icmph->checksum = 0;
- icmph->checksum = ip_vs_checksum_complete(skb, icmp_offset);
+ icmph->checksum = ip_vs_checksum_complete(skb, toff);
skb->ip_summed = CHECKSUM_UNNECESSARY;
if (inout)
- IP_VS_DBG_PKT(11, AF_INET, pp, skb, (void *)ciph - (void *)iph,
- "Forwarding altered outgoing ICMP");
+ IP_VS_DBG_PKT(11, AF_INET, pp, skb, coff,
+ "Forwarding altered outgoing ICMP");
else
- IP_VS_DBG_PKT(11, AF_INET, pp, skb, (void *)ciph - (void *)iph,
- "Forwarding altered incoming ICMP");
+ IP_VS_DBG_PKT(11, AF_INET, pp, skb, coff,
+ "Forwarding altered incoming ICMP");
}
#ifdef CONFIG_IP_VS_IPV6
void ip_vs_nat_icmp_v6(struct sk_buff *skb, struct ip_vs_protocol *pp,
- struct ip_vs_conn *cp, int inout)
+ struct ip_vs_conn *cp, int inout, unsigned int toff,
+ struct ip_vs_iphdr *ciph)
{
struct ipv6hdr *iph = ipv6_hdr(skb);
- unsigned int icmp_offset = 0;
- unsigned int offs = 0; /* header offset*/
int protocol;
struct icmp6hdr *icmph;
- struct ipv6hdr *ciph;
- unsigned short fragoffs;
+ struct ipv6hdr *cih;
- ipv6_find_hdr(skb, &icmp_offset, IPPROTO_ICMPV6, &fragoffs, NULL);
- icmph = (struct icmp6hdr *)(skb_network_header(skb) + icmp_offset);
- offs = icmp_offset + sizeof(struct icmp6hdr);
- ciph = (struct ipv6hdr *)(skb_network_header(skb) + offs);
+ icmph = (struct icmp6hdr *)(skb->data + toff);
+ cih = (struct ipv6hdr *)(skb->data + ciph->off);
- protocol = ipv6_find_hdr(skb, &offs, -1, &fragoffs, NULL);
+ protocol = ciph->protocol;
if (inout) {
iph->saddr = cp->vaddr.in6;
- ciph->daddr = cp->vaddr.in6;
+ cih->daddr = cp->vaddr.in6;
} else {
iph->daddr = cp->daddr.in6;
- ciph->saddr = cp->daddr.in6;
+ cih->saddr = cp->daddr.in6;
}
/* the TCP/UDP/SCTP port */
- if (!fragoffs && (IPPROTO_TCP == protocol || IPPROTO_UDP == protocol ||
- IPPROTO_SCTP == protocol)) {
- __be16 *ports = (void *)(skb_network_header(skb) + offs);
+ if (!ciph->fragoffs &&
+ (protocol == IPPROTO_TCP || protocol == IPPROTO_UDP ||
+ protocol == IPPROTO_SCTP)) {
+ __be16 *ports = (void *)(skb->data + ciph->len);
IP_VS_DBG(11, "%s() changed port %d to %d\n", __func__,
ntohs(inout ? ports[1] : ports[0]),
@@ -1011,19 +1007,17 @@ void ip_vs_nat_icmp_v6(struct sk_buff *skb, struct ip_vs_protocol *pp,
/* And finally the ICMP checksum */
icmph->icmp6_cksum = ~csum_ipv6_magic(&iph->saddr, &iph->daddr,
- skb->len - icmp_offset,
+ skb->len - toff,
IPPROTO_ICMPV6, 0);
- skb->csum_start = skb_network_header(skb) - skb->head + icmp_offset;
+ skb->csum_start = skb_headroom(skb) + toff;
skb->csum_offset = offsetof(struct icmp6hdr, icmp6_cksum);
skb->ip_summed = CHECKSUM_PARTIAL;
if (inout)
- IP_VS_DBG_PKT(11, AF_INET6, pp, skb,
- (void *)ciph - (void *)iph,
+ IP_VS_DBG_PKT(11, AF_INET6, pp, skb, ciph->off,
"Forwarding altered outgoing ICMPv6");
else
- IP_VS_DBG_PKT(11, AF_INET6, pp, skb,
- (void *)ciph - (void *)iph,
+ IP_VS_DBG_PKT(11, AF_INET6, pp, skb, ciph->off,
"Forwarding altered incoming ICMPv6");
}
#endif
@@ -1033,37 +1027,38 @@ void ip_vs_nat_icmp_v6(struct sk_buff *skb, struct ip_vs_protocol *pp,
*/
static int handle_response_icmp(int af, struct sk_buff *skb,
union nf_inet_addr *snet,
- __u8 protocol, struct ip_vs_conn *cp,
+ struct ip_vs_conn *cp,
struct ip_vs_protocol *pp,
- unsigned int offset, unsigned int ihl,
- unsigned int hooknum)
+ struct ip_vs_iphdr *ciph,
+ unsigned int toff, unsigned int hooknum)
{
int iproto = af == AF_INET6 ? IPPROTO_ICMPV6 : IPPROTO_ICMP;
unsigned int verdict = NF_DROP;
+ unsigned int ctoff = ciph->len;
if (IP_VS_FWD_METHOD(cp) != IP_VS_CONN_F_MASQ)
goto after_nat;
/* Ensure the checksum is correct */
- if (!ip_vs_checksum_common_check(skb, ihl, iproto, af)) {
+ if (!ip_vs_checksum_common_check(skb, toff, iproto, af)) {
/* Failed checksum! */
IP_VS_DBG_BUF(1, "Forward ICMP: failed checksum from %s!\n",
IP_VS_DBG_ADDR(af, snet));
goto out;
}
- if (IPPROTO_TCP == protocol || IPPROTO_UDP == protocol ||
- IPPROTO_SCTP == protocol)
- offset += 2 * sizeof(__u16);
- if (skb_ensure_writable(skb, offset))
+ if (ciph->protocol == IPPROTO_TCP || ciph->protocol == IPPROTO_UDP ||
+ ciph->protocol == IPPROTO_SCTP)
+ ctoff += 2 * sizeof(__u16);
+ if (skb_ensure_writable(skb, ctoff))
goto out;
#ifdef CONFIG_IP_VS_IPV6
if (af == AF_INET6)
- ip_vs_nat_icmp_v6(skb, pp, cp, 1);
+ ip_vs_nat_icmp_v6(skb, pp, cp, 1, toff, ciph);
else
#endif
- ip_vs_nat_icmp(skb, pp, cp, 1);
+ ip_vs_nat_icmp(skb, pp, cp, 1, toff);
if (ip_vs_route_me_harder(cp->ipvs, af, skb, hooknum))
goto out;
@@ -1091,9 +1086,9 @@ static int handle_response_icmp(int af, struct sk_buff *skb,
* Currently handles error types - unreachable, quench, ttl exceeded.
*/
static int ip_vs_out_icmp(struct netns_ipvs *ipvs, struct sk_buff *skb,
- int *related, unsigned int hooknum)
+ int *related, unsigned int hooknum,
+ struct ip_vs_iphdr *ipvsh)
{
- struct iphdr *iph;
struct icmphdr _icmph, *ic;
struct iphdr _ciph, *cih; /* The ip header contained within the ICMP */
struct ip_vs_iphdr ciph;
@@ -1108,17 +1103,19 @@ static int ip_vs_out_icmp(struct netns_ipvs *ipvs, struct sk_buff *skb,
if (ip_is_fragment(ip_hdr(skb))) {
if (ip_vs_gather_frags(ipvs, skb, ip_vs_defrag_user(hooknum)))
return NF_STOLEN;
+ if (!ip_vs_fill_iph_skb(AF_INET, skb, false, ipvsh))
+ return NF_ACCEPT;
}
- iph = ip_hdr(skb);
- offset = ihl = iph->ihl * 4;
+ ihl = ipvsh->len;
+ offset = ipvsh->len;
ic = skb_header_pointer(skb, offset, sizeof(_icmph), &_icmph);
if (ic == NULL)
return NF_DROP;
IP_VS_DBG(12, "Outgoing ICMP (%d,%d) %pI4->%pI4\n",
ic->type, ntohs(icmp_id(ic)),
- &iph->saddr, &iph->daddr);
+ &ipvsh->saddr.ip, &ipvsh->daddr.ip);
/*
* Work through seeing if this is for us.
@@ -1137,7 +1134,7 @@ static int ip_vs_out_icmp(struct netns_ipvs *ipvs, struct sk_buff *skb,
/* Now find the contained IP header */
offset += sizeof(_icmph);
cih = skb_header_pointer(skb, offset, sizeof(_ciph), &_ciph);
- if (cih == NULL)
+ if (!(cih && cih->version == 4 && cih->ihl >= 5))
return NF_ACCEPT; /* The packet looks wrong, ignore */
pp = ip_vs_proto_get(cih->protocol);
@@ -1160,9 +1157,9 @@ static int ip_vs_out_icmp(struct netns_ipvs *ipvs, struct sk_buff *skb,
if (!cp)
return NF_ACCEPT;
- snet.ip = iph->saddr;
- return handle_response_icmp(AF_INET, skb, &snet, cih->protocol, cp,
- pp, ciph.len, ihl, hooknum);
+ snet.ip = ipvsh->saddr.ip;
+ return handle_response_icmp(AF_INET, skb, &snet, cp, pp, &ciph, ihl,
+ hooknum);
}
#ifdef CONFIG_IP_VS_IPV6
@@ -1175,7 +1172,6 @@ static int ip_vs_out_icmp_v6(struct netns_ipvs *ipvs, struct sk_buff *skb,
struct ip_vs_conn *cp;
struct ip_vs_protocol *pp;
union nf_inet_addr snet;
- unsigned int offset;
*related = 1;
ic = frag_safe_skb_hp(skb, ipvsh->len, sizeof(_icmph), &_icmph);
@@ -1218,9 +1214,8 @@ static int ip_vs_out_icmp_v6(struct netns_ipvs *ipvs, struct sk_buff *skb,
return NF_ACCEPT;
snet.in6 = ciph.saddr.in6;
- offset = ciph.len;
- return handle_response_icmp(AF_INET6, skb, &snet, ciph.protocol, cp,
- pp, offset, ipvsh->len, hooknum);
+ return handle_response_icmp(AF_INET6, skb, &snet, cp, pp, &ciph,
+ ipvsh->len, hooknum);
}
#endif
@@ -1546,7 +1541,8 @@ ip_vs_out_hook(void *priv, struct sk_buff *skb, const struct nf_hook_state *stat
#endif
if (unlikely(iph.protocol == IPPROTO_ICMP)) {
int related;
- int verdict = ip_vs_out_icmp(ipvs, skb, &related, hooknum);
+ int verdict = ip_vs_out_icmp(ipvs, skb, &related,
+ hooknum, &iph);
if (related)
return verdict;
@@ -1754,9 +1750,8 @@ static int ipvs_gre_decap(struct netns_ipvs *ipvs, struct sk_buff *skb,
*/
static int
ip_vs_in_icmp(struct netns_ipvs *ipvs, struct sk_buff *skb, int *related,
- unsigned int hooknum)
+ unsigned int hooknum, struct ip_vs_iphdr *iph)
{
- struct iphdr *iph;
struct icmphdr _icmph, *ic;
struct iphdr _ciph, *cih; /* The ip header contained within the ICMP */
struct ip_vs_iphdr ciph;
@@ -1766,7 +1761,7 @@ ip_vs_in_icmp(struct netns_ipvs *ipvs, struct sk_buff *skb, int *related,
unsigned int offset, offset2, ihl, verdict;
bool tunnel, new_cp = false;
union nf_inet_addr *raddr;
- char *outer_proto = "IPIP";
+ char *outer_proto __maybe_unused = "IPIP";
unsigned int hlen_ipip;
int ulen = 0;
@@ -1776,17 +1771,19 @@ ip_vs_in_icmp(struct netns_ipvs *ipvs, struct sk_buff *skb, int *related,
if (ip_is_fragment(ip_hdr(skb))) {
if (ip_vs_gather_frags(ipvs, skb, ip_vs_defrag_user(hooknum)))
return NF_STOLEN;
+ if (!ip_vs_fill_iph_skb(AF_INET, skb, false, iph))
+ return NF_ACCEPT;
}
- iph = ip_hdr(skb);
- offset = ihl = iph->ihl * 4;
+ ihl = iph->len;
+ offset = iph->len;
ic = skb_header_pointer(skb, offset, sizeof(_icmph), &_icmph);
if (ic == NULL)
return NF_DROP;
IP_VS_DBG(12, "Incoming ICMP (%d,%d) %pI4->%pI4\n",
ic->type, ntohs(icmp_id(ic)),
- &iph->saddr, &iph->daddr);
+ &iph->saddr.ip, &iph->daddr.ip);
/*
* Work through seeing if this is for us.
@@ -1903,7 +1900,7 @@ ip_vs_in_icmp(struct netns_ipvs *ipvs, struct sk_buff *skb, int *related,
!ip_vs_checksum_common_check(skb, ihl, IPPROTO_ICMP, AF_INET)) {
/* Failed checksum! */
IP_VS_DBG(1, "Incoming ICMP: failed checksum from %pI4!\n",
- &iph->saddr);
+ &iph->saddr.ip);
goto out;
}
@@ -1974,7 +1971,8 @@ ip_vs_in_icmp(struct netns_ipvs *ipvs, struct sk_buff *skb, int *related,
if (IPPROTO_TCP == cih->protocol || IPPROTO_UDP == cih->protocol ||
IPPROTO_SCTP == cih->protocol)
offset += 2 * sizeof(__u16);
- verdict = ip_vs_icmp_xmit(skb, cp, pp, offset, hooknum, &ciph);
+ verdict = ip_vs_icmp_xmit(skb, cp, pp, iph->len, offset, hooknum,
+ &ciph);
out:
if (likely(!new_cp))
@@ -2087,7 +2085,8 @@ static int ip_vs_in_icmp_v6(struct netns_ipvs *ipvs, struct sk_buff *skb,
IPPROTO_SCTP == ciph.protocol)
offset += 2 * sizeof(__u16); /* Also mangle ports */
- verdict = ip_vs_icmp_xmit_v6(skb, cp, pp, offset, hooknum, &ciph);
+ verdict = ip_vs_icmp_xmit_v6(skb, cp, pp, iph->len, offset, hooknum,
+ &ciph);
out:
if (likely(!new_cp))
@@ -2166,7 +2165,7 @@ ip_vs_in_hook(void *priv, struct sk_buff *skb, const struct nf_hook_state *state
if (unlikely(iph.protocol == IPPROTO_ICMP)) {
int related;
int verdict = ip_vs_in_icmp(ipvs, skb, &related,
- hooknum);
+ hooknum, &iph);
if (related)
return verdict;
@@ -2302,6 +2301,7 @@ ip_vs_forward_icmp(void *priv, struct sk_buff *skb,
const struct nf_hook_state *state)
{
struct netns_ipvs *ipvs = net_ipvs(state->net);
+ struct ip_vs_iphdr iphdr;
int r;
/* ipvs enabled in this netns ? */
@@ -2311,10 +2311,9 @@ ip_vs_forward_icmp(void *priv, struct sk_buff *skb,
if (state->pf == NFPROTO_IPV4) {
if (ip_hdr(skb)->protocol != IPPROTO_ICMP)
return NF_ACCEPT;
+ ip_vs_fill_iph_skb(AF_INET, skb, false, &iphdr);
#ifdef CONFIG_IP_VS_IPV6
} else {
- struct ip_vs_iphdr iphdr;
-
ip_vs_fill_iph_skb(AF_INET6, skb, false, &iphdr);
if (iphdr.protocol != IPPROTO_ICMPV6)
@@ -2324,7 +2323,7 @@ ip_vs_forward_icmp(void *priv, struct sk_buff *skb,
#endif
}
- return ip_vs_in_icmp(ipvs, skb, &r, state->hook);
+ return ip_vs_in_icmp(ipvs, skb, &r, state->hook, &iphdr);
}
static const struct nf_hook_ops ip_vs_ops4[] = {
diff --git a/net/netfilter/ipvs/ip_vs_proto_sctp.c b/net/netfilter/ipvs/ip_vs_proto_sctp.c
index f6f732b7dfa8..3dbd3096e163 100644
--- a/net/netfilter/ipvs/ip_vs_proto_sctp.c
+++ b/net/netfilter/ipvs/ip_vs_proto_sctp.c
@@ -121,7 +121,7 @@ sctp_snat_handler(struct sk_buff *skb, struct ip_vs_protocol *pp,
payload_csum = true;
}
- sctph = (void *) skb_network_header(skb) + sctphoff;
+ sctph = (void *)skb->data + sctphoff;
/* Only update csum if we really have to */
if (sctph->source != cp->vport || payload_csum ||
@@ -169,7 +169,7 @@ sctp_dnat_handler(struct sk_buff *skb, struct ip_vs_protocol *pp,
payload_csum = true;
}
- sctph = (void *) skb_network_header(skb) + sctphoff;
+ sctph = (void *)skb->data + sctphoff;
/* Only update csum if we really have to */
if (sctph->dest != cp->dport || payload_csum ||
diff --git a/net/netfilter/ipvs/ip_vs_proto_tcp.c b/net/netfilter/ipvs/ip_vs_proto_tcp.c
index 533fce3e5e4e..99a286fdc90c 100644
--- a/net/netfilter/ipvs/ip_vs_proto_tcp.c
+++ b/net/netfilter/ipvs/ip_vs_proto_tcp.c
@@ -179,7 +179,7 @@ tcp_snat_handler(struct sk_buff *skb, struct ip_vs_protocol *pp,
payload_csum = true;
}
- tcph = (void *)skb_network_header(skb) + tcphoff;
+ tcph = (void *)skb->data + tcphoff;
tcph->source = cp->vport;
/* Adjust TCP checksums */
@@ -260,7 +260,7 @@ tcp_dnat_handler(struct sk_buff *skb, struct ip_vs_protocol *pp,
payload_csum = true;
}
- tcph = (void *)skb_network_header(skb) + tcphoff;
+ tcph = (void *)skb->data + tcphoff;
tcph->dest = cp->dport;
/*
diff --git a/net/netfilter/ipvs/ip_vs_proto_udp.c b/net/netfilter/ipvs/ip_vs_proto_udp.c
index de3597347542..f32785682402 100644
--- a/net/netfilter/ipvs/ip_vs_proto_udp.c
+++ b/net/netfilter/ipvs/ip_vs_proto_udp.c
@@ -170,7 +170,7 @@ udp_snat_handler(struct sk_buff *skb, struct ip_vs_protocol *pp,
payload_csum = true;
}
- udph = (void *)skb_network_header(skb) + udphoff;
+ udph = (void *)skb->data + udphoff;
udph->source = cp->vport;
/*
@@ -254,7 +254,7 @@ udp_dnat_handler(struct sk_buff *skb, struct ip_vs_protocol *pp,
payload_csum = true;
}
- udph = (void *)skb_network_header(skb) + udphoff;
+ udph = (void *)skb->data + udphoff;
udph->dest = cp->dport;
/*
diff --git a/net/netfilter/ipvs/ip_vs_xmit.c b/net/netfilter/ipvs/ip_vs_xmit.c
index 9fef4335da13..c23401c789de 100644
--- a/net/netfilter/ipvs/ip_vs_xmit.c
+++ b/net/netfilter/ipvs/ip_vs_xmit.c
@@ -1502,8 +1502,9 @@ ip_vs_dr_xmit_v6(struct sk_buff *skb, struct ip_vs_conn *cp,
*/
int
ip_vs_icmp_xmit(struct sk_buff *skb, struct ip_vs_conn *cp,
- struct ip_vs_protocol *pp, int offset, unsigned int hooknum,
- struct ip_vs_iphdr *iph)
+ struct ip_vs_protocol *pp, unsigned int toff,
+ unsigned int wlen, unsigned int hooknum,
+ struct ip_vs_iphdr *ciph)
{
struct rtable *rt; /* Route to the other host */
int rc;
@@ -1515,7 +1516,7 @@ ip_vs_icmp_xmit(struct sk_buff *skb, struct ip_vs_conn *cp,
translate address/port back */
if (IP_VS_FWD_METHOD(cp) != IP_VS_CONN_F_MASQ) {
if (cp->packet_xmit)
- rc = cp->packet_xmit(skb, cp, pp, iph);
+ rc = cp->packet_xmit(skb, cp, pp, ciph);
else
rc = NF_ACCEPT;
/* do not touch skb anymore */
@@ -1533,7 +1534,7 @@ ip_vs_icmp_xmit(struct sk_buff *skb, struct ip_vs_conn *cp,
IP_VS_RT_MODE_LOCAL | IP_VS_RT_MODE_NON_LOCAL |
IP_VS_RT_MODE_RDR : IP_VS_RT_MODE_NON_LOCAL;
local = __ip_vs_get_out_rt(cp->ipvs, cp->af, skb, cp->dest, cp->daddr.ip, rt_mode,
- NULL, iph);
+ NULL, ciph);
if (local < 0)
goto tx_error;
rt = skb_rtable(skb);
@@ -1565,13 +1566,13 @@ ip_vs_icmp_xmit(struct sk_buff *skb, struct ip_vs_conn *cp,
}
/* copy-on-write the packet before mangling it */
- if (skb_ensure_writable(skb, offset))
+ if (skb_ensure_writable(skb, wlen))
goto tx_error;
if (skb_cow(skb, rt->dst.dev->hard_header_len))
goto tx_error;
- ip_vs_nat_icmp(skb, pp, cp, 0);
+ ip_vs_nat_icmp(skb, pp, cp, 0, toff);
/* Another hack: avoid icmp_send in ip_fragment */
skb->ignore_df = 1;
@@ -1587,8 +1588,9 @@ ip_vs_icmp_xmit(struct sk_buff *skb, struct ip_vs_conn *cp,
#ifdef CONFIG_IP_VS_IPV6
int
ip_vs_icmp_xmit_v6(struct sk_buff *skb, struct ip_vs_conn *cp,
- struct ip_vs_protocol *pp, int offset, unsigned int hooknum,
- struct ip_vs_iphdr *ipvsh)
+ struct ip_vs_protocol *pp, unsigned int toff,
+ unsigned int wlen, unsigned int hooknum,
+ struct ip_vs_iphdr *ciph)
{
struct rt6_info *rt; /* Route to the other host */
int rc;
@@ -1600,7 +1602,7 @@ ip_vs_icmp_xmit_v6(struct sk_buff *skb, struct ip_vs_conn *cp,
translate address/port back */
if (IP_VS_FWD_METHOD(cp) != IP_VS_CONN_F_MASQ) {
if (cp->packet_xmit)
- rc = cp->packet_xmit(skb, cp, pp, ipvsh);
+ rc = cp->packet_xmit(skb, cp, pp, ciph);
else
rc = NF_ACCEPT;
/* do not touch skb anymore */
@@ -1617,7 +1619,7 @@ ip_vs_icmp_xmit_v6(struct sk_buff *skb, struct ip_vs_conn *cp,
IP_VS_RT_MODE_LOCAL | IP_VS_RT_MODE_NON_LOCAL |
IP_VS_RT_MODE_RDR : IP_VS_RT_MODE_NON_LOCAL;
local = __ip_vs_get_out_rt_v6(cp->ipvs, cp->af, skb, cp->dest,
- &cp->daddr.in6, NULL, ipvsh, 0, rt_mode);
+ &cp->daddr.in6, NULL, ciph, 0, rt_mode);
if (local < 0)
goto tx_error;
rt = dst_rt6_info(skb_dst(skb));
@@ -1649,13 +1651,13 @@ ip_vs_icmp_xmit_v6(struct sk_buff *skb, struct ip_vs_conn *cp,
}
/* copy-on-write the packet before mangling it */
- if (skb_ensure_writable(skb, offset))
+ if (skb_ensure_writable(skb, wlen))
goto tx_error;
if (skb_cow(skb, rt->dst.dev->hard_header_len))
goto tx_error;
- ip_vs_nat_icmp_v6(skb, pp, cp, 0);
+ ip_vs_nat_icmp_v6(skb, pp, cp, 0, toff, ciph);
/* Another hack: avoid icmp_send in ip_fragment */
skb->ignore_df = 1;
--
2.47.3
^ permalink raw reply related [flat|nested] 19+ messages in thread
* [PATCH net 11/13] ipvs: do not mangle ICMP replies for non-first fragments
2026-07-22 21:14 [PATCH net 00/13] Netfilter/IPVS fixes for net Pablo Neira Ayuso
` (9 preceding siblings ...)
2026-07-22 21:14 ` [PATCH net 10/13] ipvs: fix places with wrong packet offsets Pablo Neira Ayuso
@ 2026-07-22 21:14 ` Pablo Neira Ayuso
2026-07-22 21:14 ` [PATCH net 12/13] ipvs: clear the nfct flag under lock Pablo Neira Ayuso
2026-07-22 21:14 ` [PATCH net 13/13] netfilter: nft_payload: fix mask build for partial field offload Pablo Neira Ayuso
12 siblings, 0 replies; 19+ messages in thread
From: Pablo Neira Ayuso @ 2026-07-22 21:14 UTC (permalink / raw)
To: netfilter-devel; +Cc: davem, netdev, kuba, pabeni, edumazet, fw, horms
From: Julian Anastasov <ja@ssi.bg>
Sashiko warns that ip_vs_nat_icmp() unconditionally mangles the
payload for embedded non-first IPv4 fragments. The problem is
in the very old inverted pp->dont_defrag check which should not
continue when embedded is a non-first TCP/UDP/SCTP fragment.
Check for embedded non-first fragment is also missing from
ip_vs_out_icmp_v6(), it is needed before any connection
lookups that expect ports after the network headers.
Drop the blocking code from ip_vs_in_icmp_v6() which prevents
ICMPv6 from local clients to use non-MASQ forwarding.
Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Link: https://sashiko.dev/#/patchset/20260720201122.79882-1-ja%40ssi.bg
Signed-off-by: Julian Anastasov <ja@ssi.bg>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
---
include/net/ip_vs.h | 11 +++---
net/netfilter/ipvs/ip_vs_core.c | 61 ++++++++++++---------------------
net/netfilter/ipvs/ip_vs_xmit.c | 28 +++++++++++----
3 files changed, 48 insertions(+), 52 deletions(-)
diff --git a/include/net/ip_vs.h b/include/net/ip_vs.h
index c684a46ce15a..9ae9aad65541 100644
--- a/include/net/ip_vs.h
+++ b/include/net/ip_vs.h
@@ -1975,8 +1975,7 @@ int ip_vs_dr_xmit(struct sk_buff *skb, struct ip_vs_conn *cp,
struct ip_vs_protocol *pp, struct ip_vs_iphdr *iph);
int ip_vs_icmp_xmit(struct sk_buff *skb, struct ip_vs_conn *cp,
struct ip_vs_protocol *pp, unsigned int toff,
- unsigned int wlen, unsigned int hooknum,
- struct ip_vs_iphdr *ciph);
+ unsigned int hooknum, struct ip_vs_iphdr *ciph);
void ip_vs_dest_dst_rcu_free(struct rcu_head *head);
#ifdef CONFIG_IP_VS_IPV6
@@ -1990,8 +1989,7 @@ int ip_vs_dr_xmit_v6(struct sk_buff *skb, struct ip_vs_conn *cp,
struct ip_vs_protocol *pp, struct ip_vs_iphdr *iph);
int ip_vs_icmp_xmit_v6(struct sk_buff *skb, struct ip_vs_conn *cp,
struct ip_vs_protocol *pp, unsigned int toff,
- unsigned int wlen, unsigned int hooknum,
- struct ip_vs_iphdr *ciph);
+ unsigned int hooknum, struct ip_vs_iphdr *ciph);
#endif
#ifdef CONFIG_SYSCTL
@@ -2063,12 +2061,13 @@ static inline bool ip_vs_conn_use_hash2(struct ip_vs_conn *cp)
}
void ip_vs_nat_icmp(struct sk_buff *skb, struct ip_vs_protocol *pp,
- struct ip_vs_conn *cp, int dir, unsigned int toff);
+ struct ip_vs_conn *cp, int dir, unsigned int toff,
+ bool has_ports);
#ifdef CONFIG_IP_VS_IPV6
void ip_vs_nat_icmp_v6(struct sk_buff *skb, struct ip_vs_protocol *pp,
struct ip_vs_conn *cp, int dir, unsigned int toff,
- struct ip_vs_iphdr *ciph);
+ bool has_ports, struct ip_vs_iphdr *ciph);
#endif
static inline __wsum ip_vs_check_diff4(__be32 old, __be32 new, __wsum oldsum)
diff --git a/net/netfilter/ipvs/ip_vs_core.c b/net/netfilter/ipvs/ip_vs_core.c
index cd5eb71543ec..7efa209a517b 100644
--- a/net/netfilter/ipvs/ip_vs_core.c
+++ b/net/netfilter/ipvs/ip_vs_core.c
@@ -924,7 +924,8 @@ static int ip_vs_route_me_harder(struct netns_ipvs *ipvs, int af,
* - inout: 1=in->out, 0=out->in
*/
void ip_vs_nat_icmp(struct sk_buff *skb, struct ip_vs_protocol *pp,
- struct ip_vs_conn *cp, int inout, unsigned int toff)
+ struct ip_vs_conn *cp, int inout, unsigned int toff,
+ bool has_ports)
{
struct iphdr *iph = ip_hdr(skb);
struct icmphdr *icmph = (struct icmphdr *)(skb->data + toff);
@@ -944,8 +945,7 @@ void ip_vs_nat_icmp(struct sk_buff *skb, struct ip_vs_protocol *pp,
}
/* the TCP/UDP/SCTP port */
- if (IPPROTO_TCP == ciph->protocol || IPPROTO_UDP == ciph->protocol ||
- IPPROTO_SCTP == ciph->protocol) {
+ if (has_ports) {
__be16 *ports = (void *)ciph + ciph->ihl*4;
if (inout)
@@ -970,18 +970,15 @@ void ip_vs_nat_icmp(struct sk_buff *skb, struct ip_vs_protocol *pp,
#ifdef CONFIG_IP_VS_IPV6
void ip_vs_nat_icmp_v6(struct sk_buff *skb, struct ip_vs_protocol *pp,
struct ip_vs_conn *cp, int inout, unsigned int toff,
- struct ip_vs_iphdr *ciph)
+ bool has_ports, struct ip_vs_iphdr *ciph)
{
struct ipv6hdr *iph = ipv6_hdr(skb);
- int protocol;
struct icmp6hdr *icmph;
struct ipv6hdr *cih;
icmph = (struct icmp6hdr *)(skb->data + toff);
cih = (struct ipv6hdr *)(skb->data + ciph->off);
- protocol = ciph->protocol;
-
if (inout) {
iph->saddr = cp->vaddr.in6;
cih->daddr = cp->vaddr.in6;
@@ -991,9 +988,7 @@ void ip_vs_nat_icmp_v6(struct sk_buff *skb, struct ip_vs_protocol *pp,
}
/* the TCP/UDP/SCTP port */
- if (!ciph->fragoffs &&
- (protocol == IPPROTO_TCP || protocol == IPPROTO_UDP ||
- protocol == IPPROTO_SCTP)) {
+ if (has_ports) {
__be16 *ports = (void *)(skb->data + ciph->len);
IP_VS_DBG(11, "%s() changed port %d to %d\n", __func__,
@@ -1035,6 +1030,7 @@ static int handle_response_icmp(int af, struct sk_buff *skb,
int iproto = af == AF_INET6 ? IPPROTO_ICMPV6 : IPPROTO_ICMP;
unsigned int verdict = NF_DROP;
unsigned int ctoff = ciph->len;
+ bool has_ports = false;
if (IP_VS_FWD_METHOD(cp) != IP_VS_CONN_F_MASQ)
goto after_nat;
@@ -1048,17 +1044,19 @@ static int handle_response_icmp(int af, struct sk_buff *skb,
}
if (ciph->protocol == IPPROTO_TCP || ciph->protocol == IPPROTO_UDP ||
- ciph->protocol == IPPROTO_SCTP)
+ ciph->protocol == IPPROTO_SCTP) {
ctoff += 2 * sizeof(__u16);
+ has_ports = true;
+ }
if (skb_ensure_writable(skb, ctoff))
goto out;
#ifdef CONFIG_IP_VS_IPV6
if (af == AF_INET6)
- ip_vs_nat_icmp_v6(skb, pp, cp, 1, toff, ciph);
+ ip_vs_nat_icmp_v6(skb, pp, cp, 1, toff, has_ports, ciph);
else
#endif
- ip_vs_nat_icmp(skb, pp, cp, 1, toff);
+ ip_vs_nat_icmp(skb, pp, cp, 1, toff, has_ports);
if (ip_vs_route_me_harder(cp->ipvs, af, skb, hooknum))
goto out;
@@ -1142,8 +1140,7 @@ static int ip_vs_out_icmp(struct netns_ipvs *ipvs, struct sk_buff *skb,
return NF_ACCEPT;
/* Is the embedded protocol header present? */
- if (unlikely(cih->frag_off & htons(IP_OFFSET) &&
- pp->dont_defrag))
+ if (unlikely(cih->frag_off & htons(IP_OFFSET) && !pp->dont_defrag))
return NF_ACCEPT;
IP_VS_DBG_PKT(11, AF_INET, pp, skb, offset,
@@ -1207,6 +1204,10 @@ static int ip_vs_out_icmp_v6(struct netns_ipvs *ipvs, struct sk_buff *skb,
if (!pp)
return NF_ACCEPT;
+ /* Is the embedded protocol header present? */
+ if (unlikely(ciph.fragoffs && !pp->dont_defrag))
+ return NF_ACCEPT;
+
/* The embedded headers contain source and dest in reverse order */
cp = INDIRECT_CALL_1(pp->conn_out_get, ip_vs_conn_out_get_proto,
ipvs, AF_INET6, skb, &ciph);
@@ -1865,8 +1866,7 @@ ip_vs_in_icmp(struct netns_ipvs *ipvs, struct sk_buff *skb, int *related,
pp = pd->pp;
/* Is the embedded protocol header present? */
- if (unlikely(cih->frag_off & htons(IP_OFFSET) &&
- pp->dont_defrag))
+ if (unlikely(cih->frag_off & htons(IP_OFFSET) && !pp->dont_defrag))
return NF_ACCEPT;
IP_VS_DBG_PKT(11, AF_INET, pp, skb, offset,
@@ -1874,7 +1874,6 @@ ip_vs_in_icmp(struct netns_ipvs *ipvs, struct sk_buff *skb, int *related,
offset2 = offset;
ip_vs_fill_iph_skb_icmp(AF_INET, skb, offset, !tunnel, &ciph);
- offset = ciph.len;
/* The embedded headers contain source and dest in reverse order.
* For IPIP/UDP/GRE tunnel this is error for request, not for reply.
@@ -1968,11 +1967,7 @@ ip_vs_in_icmp(struct netns_ipvs *ipvs, struct sk_buff *skb, int *related,
/* do the statistics and put it back */
ip_vs_in_stats(cp, skb);
- if (IPPROTO_TCP == cih->protocol || IPPROTO_UDP == cih->protocol ||
- IPPROTO_SCTP == cih->protocol)
- offset += 2 * sizeof(__u16);
- verdict = ip_vs_icmp_xmit(skb, cp, pp, iph->len, offset, hooknum,
- &ciph);
+ verdict = ip_vs_icmp_xmit(skb, cp, pp, iph->len, hooknum, &ciph);
out:
if (likely(!new_cp))
@@ -2032,8 +2027,8 @@ static int ip_vs_in_icmp_v6(struct netns_ipvs *ipvs, struct sk_buff *skb,
return NF_ACCEPT;
pp = pd->pp;
- /* Cannot handle fragmented embedded protocol */
- if (ciph.fragoffs)
+ /* Is the embedded protocol header present? */
+ if (ciph.fragoffs && !pp->dont_defrag)
return NF_ACCEPT;
IP_VS_DBG_PKT(11, AF_INET6, pp, skb, offset,
@@ -2057,13 +2052,6 @@ static int ip_vs_in_icmp_v6(struct netns_ipvs *ipvs, struct sk_buff *skb,
new_cp = true;
}
- /* VS/TUN, VS/DR and LOCALNODE just let it go */
- if ((hooknum == NF_INET_LOCAL_OUT) &&
- (IP_VS_FWD_METHOD(cp) != IP_VS_CONN_F_MASQ)) {
- verdict = NF_ACCEPT;
- goto out;
- }
-
verdict = NF_DROP;
/* Ensure the checksum is correct */
@@ -2079,14 +2067,7 @@ static int ip_vs_in_icmp_v6(struct netns_ipvs *ipvs, struct sk_buff *skb,
/* do the statistics and put it back */
ip_vs_in_stats(cp, skb);
- /* Need to mangle contained IPv6 header in ICMPv6 packet */
- offset = ciph.len;
- if (IPPROTO_TCP == ciph.protocol || IPPROTO_UDP == ciph.protocol ||
- IPPROTO_SCTP == ciph.protocol)
- offset += 2 * sizeof(__u16); /* Also mangle ports */
-
- verdict = ip_vs_icmp_xmit_v6(skb, cp, pp, iph->len, offset, hooknum,
- &ciph);
+ verdict = ip_vs_icmp_xmit_v6(skb, cp, pp, iph->len, hooknum, &ciph);
out:
if (likely(!new_cp))
diff --git a/net/netfilter/ipvs/ip_vs_xmit.c b/net/netfilter/ipvs/ip_vs_xmit.c
index c23401c789de..0b0c5304993a 100644
--- a/net/netfilter/ipvs/ip_vs_xmit.c
+++ b/net/netfilter/ipvs/ip_vs_xmit.c
@@ -1503,13 +1503,14 @@ ip_vs_dr_xmit_v6(struct sk_buff *skb, struct ip_vs_conn *cp,
int
ip_vs_icmp_xmit(struct sk_buff *skb, struct ip_vs_conn *cp,
struct ip_vs_protocol *pp, unsigned int toff,
- unsigned int wlen, unsigned int hooknum,
- struct ip_vs_iphdr *ciph)
+ unsigned int hooknum, struct ip_vs_iphdr *ciph)
{
struct rtable *rt; /* Route to the other host */
int rc;
int local;
int rt_mode, was_input;
+ bool has_ports = false;
+ unsigned int wlen;
/* The ICMP packet for VS/TUN, VS/DR and LOCALNODE will be
forwarded directly here, because there is no need to
@@ -1565,6 +1566,13 @@ ip_vs_icmp_xmit(struct sk_buff *skb, struct ip_vs_conn *cp,
goto tx_error;
}
+ wlen = ciph->len;
+ if (ciph->protocol == IPPROTO_TCP || ciph->protocol == IPPROTO_UDP ||
+ ciph->protocol == IPPROTO_SCTP) {
+ wlen += 2 * sizeof(__u16); /* Also mangle ports */
+ has_ports = true;
+ }
+
/* copy-on-write the packet before mangling it */
if (skb_ensure_writable(skb, wlen))
goto tx_error;
@@ -1572,7 +1580,7 @@ ip_vs_icmp_xmit(struct sk_buff *skb, struct ip_vs_conn *cp,
if (skb_cow(skb, rt->dst.dev->hard_header_len))
goto tx_error;
- ip_vs_nat_icmp(skb, pp, cp, 0, toff);
+ ip_vs_nat_icmp(skb, pp, cp, 0, toff, has_ports);
/* Another hack: avoid icmp_send in ip_fragment */
skb->ignore_df = 1;
@@ -1589,10 +1597,11 @@ ip_vs_icmp_xmit(struct sk_buff *skb, struct ip_vs_conn *cp,
int
ip_vs_icmp_xmit_v6(struct sk_buff *skb, struct ip_vs_conn *cp,
struct ip_vs_protocol *pp, unsigned int toff,
- unsigned int wlen, unsigned int hooknum,
- struct ip_vs_iphdr *ciph)
+ unsigned int hooknum, struct ip_vs_iphdr *ciph)
{
+ bool has_ports = false;
struct rt6_info *rt; /* Route to the other host */
+ unsigned int wlen;
int rc;
int local;
int rt_mode;
@@ -1650,6 +1659,13 @@ ip_vs_icmp_xmit_v6(struct sk_buff *skb, struct ip_vs_conn *cp,
goto tx_error;
}
+ wlen = ciph->len;
+ if (ciph->protocol == IPPROTO_TCP || ciph->protocol == IPPROTO_UDP ||
+ ciph->protocol == IPPROTO_SCTP) {
+ wlen += 2 * sizeof(__u16); /* Also mangle ports */
+ has_ports = true;
+ }
+
/* copy-on-write the packet before mangling it */
if (skb_ensure_writable(skb, wlen))
goto tx_error;
@@ -1657,7 +1673,7 @@ ip_vs_icmp_xmit_v6(struct sk_buff *skb, struct ip_vs_conn *cp,
if (skb_cow(skb, rt->dst.dev->hard_header_len))
goto tx_error;
- ip_vs_nat_icmp_v6(skb, pp, cp, 0, toff, ciph);
+ ip_vs_nat_icmp_v6(skb, pp, cp, 0, toff, has_ports, ciph);
/* Another hack: avoid icmp_send in ip_fragment */
skb->ignore_df = 1;
--
2.47.3
^ permalink raw reply related [flat|nested] 19+ messages in thread
* [PATCH net 12/13] ipvs: clear the nfct flag under lock
2026-07-22 21:14 [PATCH net 00/13] Netfilter/IPVS fixes for net Pablo Neira Ayuso
` (10 preceding siblings ...)
2026-07-22 21:14 ` [PATCH net 11/13] ipvs: do not mangle ICMP replies for non-first fragments Pablo Neira Ayuso
@ 2026-07-22 21:14 ` Pablo Neira Ayuso
2026-07-22 21:14 ` [PATCH net 13/13] netfilter: nft_payload: fix mask build for partial field offload Pablo Neira Ayuso
12 siblings, 0 replies; 19+ messages in thread
From: Pablo Neira Ayuso @ 2026-07-22 21:14 UTC (permalink / raw)
To: netfilter-devel; +Cc: davem, netdev, kuba, pabeni, edumazet, fw, horms
From: Julian Anastasov <ja@ssi.bg>
Sashiko warns that cp->flags should be changed under cp->lock
Fixes: 35dfb013149f ("ipvs: queue delayed work to expire no destination connections if expire_nodest_conn=1")
Fixes: f0a5e4d7a594 ("ipvs: allow connection reuse for unconfirmed conntrack")
Link: https://sashiko.dev/#/patchset/CALMqdkR704S2BG_QD_bgHTFp2%2B1QCi7n0T4zoZyTo8mDZevYSA%40mail.gmail.com
Signed-off-by: Julian Anastasov <ja@ssi.bg>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
---
net/netfilter/ipvs/ip_vs_core.c | 10 ++++++++--
1 file changed, 8 insertions(+), 2 deletions(-)
diff --git a/net/netfilter/ipvs/ip_vs_core.c b/net/netfilter/ipvs/ip_vs_core.c
index 7efa209a517b..6b79e0c4d9e2 100644
--- a/net/netfilter/ipvs/ip_vs_core.c
+++ b/net/netfilter/ipvs/ip_vs_core.c
@@ -2194,8 +2194,11 @@ ip_vs_in_hook(void *priv, struct sk_buff *skb, const struct nf_hook_state *state
}
if (resched) {
- if (!old_ct)
+ if (!old_ct) {
+ spin_lock_bh(&cp->lock);
cp->flags &= ~IP_VS_CONN_F_NFCT;
+ spin_unlock_bh(&cp->lock);
+ }
if (!atomic_read(&cp->n_control))
ip_vs_conn_expire_now(cp);
__ip_vs_conn_put(cp);
@@ -2211,8 +2214,11 @@ ip_vs_in_hook(void *priv, struct sk_buff *skb, const struct nf_hook_state *state
if (sysctl_expire_nodest_conn(ipvs)) {
bool old_ct = ip_vs_conn_uses_old_conntrack(cp, skb);
- if (!old_ct)
+ if (!old_ct) {
+ spin_lock_bh(&cp->lock);
cp->flags &= ~IP_VS_CONN_F_NFCT;
+ spin_unlock_bh(&cp->lock);
+ }
ip_vs_conn_expire_now(cp);
__ip_vs_conn_put(cp);
--
2.47.3
^ permalink raw reply related [flat|nested] 19+ messages in thread
* [PATCH net 13/13] netfilter: nft_payload: fix mask build for partial field offload
2026-07-22 21:14 [PATCH net 00/13] Netfilter/IPVS fixes for net Pablo Neira Ayuso
` (11 preceding siblings ...)
2026-07-22 21:14 ` [PATCH net 12/13] ipvs: clear the nfct flag under lock Pablo Neira Ayuso
@ 2026-07-22 21:14 ` Pablo Neira Ayuso
12 siblings, 0 replies; 19+ messages in thread
From: Pablo Neira Ayuso @ 2026-07-22 21:14 UTC (permalink / raw)
To: netfilter-devel; +Cc: davem, netdev, kuba, pabeni, edumazet, fw, horms
From: "Xiang Mei (Microsoft)" <xmei5@asu.edu>
nft_payload_offload_mask() builds the offload match mask for a payload
expression that covers only part of a header field. For a partial IPv6
address match (field_len = 16, priv_len = 1) that shift is 1 << 120, which
is undefined on the 32-bit int operand. It also trims only one word, so
the remaining words stay 0xffffffff (and when priv_len is a multiple of 4
the trim is skipped entirely), leaving the mask covering more bytes than
the rule matches.
UBSAN: shift-out-of-bounds in net/netfilter/nft_payload.c:278:20
shift exponent 120 is too large for 32-bit type 'int'
...
The match is byte-granular and struct nft_data is zero-initialised, so the
correct mask is simply the first priv_len bytes set to 0xff. Set those
bytes directly and drop the word/shift trimming; this removes the undefined
shift and no longer over-masks the trailing bytes.
Fixes: a5d45bc0dc50 ("netfilter: nftables_offload: build mask based from the matching bytes")
Reported-by: AutonomousCodeSecurity@microsoft.com
Signed-off-by: Xiang Mei (Microsoft) <xmei5@asu.edu>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
---
net/netfilter/nft_payload.c | 12 +-----------
1 file changed, 1 insertion(+), 11 deletions(-)
diff --git a/net/netfilter/nft_payload.c b/net/netfilter/nft_payload.c
index 391539a1ceaa..8a4472fd77d9 100644
--- a/net/netfilter/nft_payload.c
+++ b/net/netfilter/nft_payload.c
@@ -259,9 +259,7 @@ static int nft_payload_dump(struct sk_buff *skb,
static bool nft_payload_offload_mask(struct nft_offload_reg *reg,
u32 priv_len, u32 field_len)
{
- unsigned int remainder, delta, k;
struct nft_data mask = {};
- __be32 remainder_mask;
if (priv_len == field_len) {
memset(®->mask, 0xff, priv_len);
@@ -270,15 +268,7 @@ static bool nft_payload_offload_mask(struct nft_offload_reg *reg,
return false;
}
- memset(&mask, 0xff, field_len);
- remainder = priv_len % sizeof(u32);
- if (remainder) {
- k = priv_len / sizeof(u32);
- delta = field_len - priv_len;
- remainder_mask = htonl(~((1 << (delta * BITS_PER_BYTE)) - 1));
- mask.data[k] = (__force u32)remainder_mask;
- }
-
+ memset(&mask, 0xff, priv_len);
memcpy(®->mask, &mask, field_len);
return true;
--
2.47.3
^ permalink raw reply related [flat|nested] 19+ messages in thread
* Re: [PATCH net 09/13] ipvs: fix the checksum validations
2026-07-22 21:14 ` [PATCH net 09/13] ipvs: fix the checksum validations Pablo Neira Ayuso
@ 2026-07-23 14:08 ` Jakub Kicinski
2026-07-23 15:39 ` Pablo Neira Ayuso
0 siblings, 1 reply; 19+ messages in thread
From: Jakub Kicinski @ 2026-07-23 14:08 UTC (permalink / raw)
To: Pablo Neira Ayuso
Cc: netfilter-devel, davem, netdev, pabeni, edumazet, fw, horms
On Wed, 22 Jul 2026 23:14:16 +0200 Pablo Neira Ayuso wrote:
> +/**
> + * ip_vs_checksum_common_check - validate checksum for TCP/UDP/ICMP
> + */
this not-really-kdoc comment causes a bunch of new warnings,
and this file was previously warning free. Which in turn
makes our AI reviewer ignore the series. Could you respin?
> +static inline bool ip_vs_checksum_common_check(struct sk_buff *skb,
> + int offset, int proto, int af)
> +{
> + if (!ip_vs_checksum_needed(skb, af))
> + return true;
> + return !nf_checksum(skb, NF_INET_LOCAL_IN, offset, proto, af);
> +}
^ permalink raw reply [flat|nested] 19+ messages in thread
* Re: [PATCH net 09/13] ipvs: fix the checksum validations
2026-07-23 14:08 ` Jakub Kicinski
@ 2026-07-23 15:39 ` Pablo Neira Ayuso
2026-07-23 18:37 ` Jakub Kicinski
0 siblings, 1 reply; 19+ messages in thread
From: Pablo Neira Ayuso @ 2026-07-23 15:39 UTC (permalink / raw)
To: Jakub Kicinski
Cc: netfilter-devel, davem, netdev, pabeni, edumazet, fw, horms
Hi,
On Thu, Jul 23, 2026 at 07:08:45AM -0700, Jakub Kicinski wrote:
> On Wed, 22 Jul 2026 23:14:16 +0200 Pablo Neira Ayuso wrote:
> > +/**
> > + * ip_vs_checksum_common_check - validate checksum for TCP/UDP/ICMP
> > + */
>
> this not-really-kdoc comment causes a bunch of new warnings,
> and this file was previously warning free. Which in turn
> makes our AI reviewer ignore the series. Could you respin?
Yes, I can drop the patches that have the kdoc issues and respin.
^ permalink raw reply [flat|nested] 19+ messages in thread
* [PATCH net 09/13] ipvs: fix the checksum validations
2026-07-23 16:38 [PATCH net,v2 00/13] Netfilter/IPVS fixes for net Pablo Neira Ayuso
@ 2026-07-23 16:39 ` Pablo Neira Ayuso
0 siblings, 0 replies; 19+ messages in thread
From: Pablo Neira Ayuso @ 2026-07-23 16:39 UTC (permalink / raw)
To: netfilter-devel; +Cc: davem, netdev, kuba, pabeni, edumazet, fw, horms
From: Julian Anastasov <ja@ssi.bg>
ip_vs_in_icmp_v6() is missing checksum validation for ICMPv6
packets from clients. In fact, as for TCP/UDP we should
validate the checksum for ICMP packets only when we
mangle the packets on MASQ or on reply for tunnel.
Also, Sashiko points out that handle_response_icmp() being
common for IPv4 and IPv6 is missing the pseudo-header
calculation while validating ICMPv6 messages from real
servers which is a problem if checksum is not validated
by the hardware.
Fix the problems by creating ip_vs_checksum_common_check()
helper and use it for TCP/UDP/ICMP both for IPv4 and IPv6.
Rely on the nf_checksum() for validating the ICMP messages
but use it also for TCP and UDP.
Use correct IP offset for IP_VS_DBG_RL_PKT for TCP/UDP/SCTP.
IPVS packets (TCP/UDP/SCTP/ICMP) do not need checksum
validation on LOCAL_OUT (local clients or local real
servers) and on FORWARD (traffic from servers on LAN).
Do it only on LOCAL_IN, in case nf_checksum() is not
called on PRE_ROUTING.
Also, ip_vs_checksum_complete() can be marked static.
Fixes: 2a3b791e6e11 ("IPVS: Add/adjust Netfilter hook functions and helpers for v6")
Link: https://sashiko.dev/#/patchset/20260708180315.77413-1-ja%40ssi.bg
Signed-off-by: Julian Anastasov <ja@ssi.bg>
Signed-off-by: Pablo Neira Ayuso <pablo@netfilter.org>
---
include/net/ip_vs.h | 31 +++++++++++++++--
net/netfilter/ipvs/ip_vs_core.c | 20 +++++++++--
net/netfilter/ipvs/ip_vs_proto_sctp.c | 15 ++++----
net/netfilter/ipvs/ip_vs_proto_tcp.c | 44 +++++------------------
net/netfilter/ipvs/ip_vs_proto_udp.c | 50 ++++++---------------------
5 files changed, 74 insertions(+), 86 deletions(-)
diff --git a/include/net/ip_vs.h b/include/net/ip_vs.h
index 417ff51f62fc..d8f9ddb0fb38 100644
--- a/include/net/ip_vs.h
+++ b/include/net/ip_vs.h
@@ -25,7 +25,9 @@
#include <linux/netfilter.h> /* for union nf_inet_addr */
#include <linux/ip.h>
#include <linux/ipv6.h> /* for struct ipv6hdr */
+#include <net/route.h>
#include <net/ipv6.h>
+#include <net/ip6_fib.h>
#if IS_ENABLED(CONFIG_NF_CONNTRACK)
#include <net/netfilter/nf_conntrack.h>
#endif
@@ -2066,8 +2068,6 @@ void ip_vs_nat_icmp_v6(struct sk_buff *skb, struct ip_vs_protocol *pp,
struct ip_vs_conn *cp, int dir);
#endif
-__sum16 ip_vs_checksum_complete(struct sk_buff *skb, int offset);
-
static inline __wsum ip_vs_check_diff4(__be32 old, __be32 new, __wsum oldsum)
{
__be32 diff[2] = { ~old, new };
@@ -2093,6 +2093,33 @@ static inline __wsum ip_vs_check_diff2(__be16 old, __be16 new, __wsum oldsum)
return csum_partial(diff, sizeof(diff), oldsum);
}
+static inline bool ip_vs_checksum_needed(struct sk_buff *skb, int af)
+{
+ /* Checksum unnecessary or already validated? */
+ if (skb_csum_unnecessary(skb))
+ return false;
+ /* LOCAL_OUT ? */
+ if (!skb->dev || skb->dev->flags & IFF_LOOPBACK)
+ return false;
+ /* !LOCAL_IN (FORWARD) ? */
+ if (af == AF_INET6) {
+ if (!(dst_rt6_info(skb_dst(skb))->rt6i_flags & RTF_LOCAL))
+ return false;
+ } else {
+ if (!(skb_rtable(skb)->rt_flags & RTCF_LOCAL))
+ return false;
+ }
+ return true;
+}
+
+static inline bool ip_vs_checksum_common_check(struct sk_buff *skb,
+ int offset, int proto, int af)
+{
+ if (!ip_vs_checksum_needed(skb, af))
+ return true;
+ return !nf_checksum(skb, NF_INET_LOCAL_IN, offset, proto, af);
+}
+
/* Forget current conntrack (unconfirmed) and attach notrack entry */
static inline void ip_vs_notrack(struct sk_buff *skb)
{
diff --git a/net/netfilter/ipvs/ip_vs_core.c b/net/netfilter/ipvs/ip_vs_core.c
index bafab93451d0..c8b512725e6e 100644
--- a/net/netfilter/ipvs/ip_vs_core.c
+++ b/net/netfilter/ipvs/ip_vs_core.c
@@ -867,7 +867,7 @@ static int sysctl_nat_icmp_send(struct netns_ipvs *ipvs) { return 0; }
#endif
-__sum16 ip_vs_checksum_complete(struct sk_buff *skb, int offset)
+static __sum16 ip_vs_checksum_complete(struct sk_buff *skb, int offset)
{
return csum_fold(skb_checksum(skb, offset, skb->len - offset, 0));
}
@@ -1038,13 +1038,14 @@ static int handle_response_icmp(int af, struct sk_buff *skb,
unsigned int offset, unsigned int ihl,
unsigned int hooknum)
{
+ int iproto = af == AF_INET6 ? IPPROTO_ICMPV6 : IPPROTO_ICMP;
unsigned int verdict = NF_DROP;
if (IP_VS_FWD_METHOD(cp) != IP_VS_CONN_F_MASQ)
goto after_nat;
/* Ensure the checksum is correct */
- if (!skb_csum_unnecessary(skb) && ip_vs_checksum_complete(skb, ihl)) {
+ if (!ip_vs_checksum_common_check(skb, ihl, iproto, af)) {
/* Failed checksum! */
IP_VS_DBG_BUF(1, "Forward ICMP: failed checksum from %s!\n",
IP_VS_DBG_ADDR(af, snet));
@@ -1898,7 +1899,8 @@ ip_vs_in_icmp(struct netns_ipvs *ipvs, struct sk_buff *skb, int *related,
verdict = NF_DROP;
/* Ensure the checksum is correct */
- if (!skb_csum_unnecessary(skb) && ip_vs_checksum_complete(skb, ihl)) {
+ if ((IP_VS_FWD_METHOD(cp) == IP_VS_CONN_F_MASQ || tunnel) &&
+ !ip_vs_checksum_common_check(skb, ihl, IPPROTO_ICMP, AF_INET)) {
/* Failed checksum! */
IP_VS_DBG(1, "Incoming ICMP: failed checksum from %pI4!\n",
&iph->saddr);
@@ -2064,6 +2066,18 @@ static int ip_vs_in_icmp_v6(struct netns_ipvs *ipvs, struct sk_buff *skb,
goto out;
}
+ verdict = NF_DROP;
+
+ /* Ensure the checksum is correct */
+ if (IP_VS_FWD_METHOD(cp) == IP_VS_CONN_F_MASQ &&
+ !ip_vs_checksum_common_check(skb, iph->len, IPPROTO_ICMPV6,
+ AF_INET6)) {
+ /* Failed checksum! */
+ IP_VS_DBG(1, "Incoming ICMPv6: failed checksum from %pI6c!\n",
+ &iph->saddr);
+ goto out;
+ }
+
/* do the statistics and put it back */
ip_vs_in_stats(cp, skb);
diff --git a/net/netfilter/ipvs/ip_vs_proto_sctp.c b/net/netfilter/ipvs/ip_vs_proto_sctp.c
index c67317be17df..f6f732b7dfa8 100644
--- a/net/netfilter/ipvs/ip_vs_proto_sctp.c
+++ b/net/netfilter/ipvs/ip_vs_proto_sctp.c
@@ -11,7 +11,7 @@
static int
sctp_csum_check(int af, struct sk_buff *skb, struct ip_vs_protocol *pp,
- unsigned int sctphoff);
+ struct ip_vs_iphdr *iph);
static int
sctp_conn_schedule(struct netns_ipvs *ipvs, int af, struct sk_buff *skb,
@@ -109,7 +109,7 @@ sctp_snat_handler(struct sk_buff *skb, struct ip_vs_protocol *pp,
int ret;
/* Some checks before mangling */
- if (!sctp_csum_check(cp->af, skb, pp, sctphoff))
+ if (!sctp_csum_check(cp->af, skb, pp, iph))
return 0;
/* Call application helper if needed */
@@ -157,7 +157,7 @@ sctp_dnat_handler(struct sk_buff *skb, struct ip_vs_protocol *pp,
int ret;
/* Some checks before mangling */
- if (!sctp_csum_check(cp->af, skb, pp, sctphoff))
+ if (!sctp_csum_check(cp->af, skb, pp, iph))
return 0;
/* Call application helper if needed */
@@ -187,19 +187,22 @@ sctp_dnat_handler(struct sk_buff *skb, struct ip_vs_protocol *pp,
static int
sctp_csum_check(int af, struct sk_buff *skb, struct ip_vs_protocol *pp,
- unsigned int sctphoff)
+ struct ip_vs_iphdr *iph)
{
+ unsigned int sctphoff = iph->len;
struct sctphdr *sh;
__le32 cmp, val;
+ if (!ip_vs_checksum_needed(skb, af))
+ return 1;
sh = (struct sctphdr *)(skb->data + sctphoff);
cmp = sh->checksum;
val = sctp_compute_cksum(skb, sctphoff);
if (val != cmp) {
/* CRC failure, dump it. */
- IP_VS_DBG_RL_PKT(0, af, pp, skb, 0,
- "Failed checksum for");
+ IP_VS_DBG_RL_PKT(0, af, pp, skb, iph->off,
+ "Failed checksum for");
return 0;
}
return 1;
diff --git a/net/netfilter/ipvs/ip_vs_proto_tcp.c b/net/netfilter/ipvs/ip_vs_proto_tcp.c
index f86b763efcc4..533fce3e5e4e 100644
--- a/net/netfilter/ipvs/ip_vs_proto_tcp.c
+++ b/net/netfilter/ipvs/ip_vs_proto_tcp.c
@@ -29,7 +29,7 @@
static int
tcp_csum_check(int af, struct sk_buff *skb, struct ip_vs_protocol *pp,
- unsigned int tcphoff);
+ struct ip_vs_iphdr *iph);
static int
tcp_conn_schedule(struct netns_ipvs *ipvs, int af, struct sk_buff *skb,
@@ -166,7 +166,7 @@ tcp_snat_handler(struct sk_buff *skb, struct ip_vs_protocol *pp,
int ret;
/* Some checks before mangling */
- if (!tcp_csum_check(cp->af, skb, pp, tcphoff))
+ if (!tcp_csum_check(cp->af, skb, pp, iph))
return 0;
/* Call application helper if needed */
@@ -244,7 +244,7 @@ tcp_dnat_handler(struct sk_buff *skb, struct ip_vs_protocol *pp,
int ret;
/* Some checks before mangling */
- if (!tcp_csum_check(cp->af, skb, pp, tcphoff))
+ if (!tcp_csum_check(cp->af, skb, pp, iph))
return 0;
/*
@@ -302,41 +302,13 @@ tcp_dnat_handler(struct sk_buff *skb, struct ip_vs_protocol *pp,
static int
tcp_csum_check(int af, struct sk_buff *skb, struct ip_vs_protocol *pp,
- unsigned int tcphoff)
+ struct ip_vs_iphdr *iph)
{
- switch (skb->ip_summed) {
- case CHECKSUM_NONE:
- skb->csum = skb_checksum(skb, tcphoff, skb->len - tcphoff, 0);
- fallthrough;
- case CHECKSUM_COMPLETE:
-#ifdef CONFIG_IP_VS_IPV6
- if (af == AF_INET6) {
- if (csum_ipv6_magic(&ipv6_hdr(skb)->saddr,
- &ipv6_hdr(skb)->daddr,
- skb->len - tcphoff,
- IPPROTO_TCP,
- skb->csum)) {
- IP_VS_DBG_RL_PKT(0, af, pp, skb, 0,
- "Failed checksum for");
- return 0;
- }
- } else
-#endif
- if (csum_tcpudp_magic(ip_hdr(skb)->saddr,
- ip_hdr(skb)->daddr,
- skb->len - tcphoff,
- ip_hdr(skb)->protocol,
- skb->csum)) {
- IP_VS_DBG_RL_PKT(0, af, pp, skb, 0,
- "Failed checksum for");
- return 0;
- }
- break;
- default:
- /* No need to checksum. */
- break;
+ if (!ip_vs_checksum_common_check(skb, iph->len, IPPROTO_TCP, af)) {
+ IP_VS_DBG_RL_PKT(0, af, pp, skb, iph->off,
+ "Failed checksum for");
+ return 0;
}
-
return 1;
}
diff --git a/net/netfilter/ipvs/ip_vs_proto_udp.c b/net/netfilter/ipvs/ip_vs_proto_udp.c
index 58f9e255927e..de3597347542 100644
--- a/net/netfilter/ipvs/ip_vs_proto_udp.c
+++ b/net/netfilter/ipvs/ip_vs_proto_udp.c
@@ -25,7 +25,7 @@
static int
udp_csum_check(int af, struct sk_buff *skb, struct ip_vs_protocol *pp,
- unsigned int udphoff);
+ struct ip_vs_iphdr *iph);
static int
udp_conn_schedule(struct netns_ipvs *ipvs, int af, struct sk_buff *skb,
@@ -155,7 +155,7 @@ udp_snat_handler(struct sk_buff *skb, struct ip_vs_protocol *pp,
int ret;
/* Some checks before mangling */
- if (!udp_csum_check(cp->af, skb, pp, udphoff))
+ if (!udp_csum_check(cp->af, skb, pp, iph))
return 0;
/*
@@ -238,7 +238,7 @@ udp_dnat_handler(struct sk_buff *skb, struct ip_vs_protocol *pp,
int ret;
/* Some checks before mangling */
- if (!udp_csum_check(cp->af, skb, pp, udphoff))
+ if (!udp_csum_check(cp->af, skb, pp, iph))
return 0;
/*
@@ -298,48 +298,20 @@ udp_dnat_handler(struct sk_buff *skb, struct ip_vs_protocol *pp,
static int
udp_csum_check(int af, struct sk_buff *skb, struct ip_vs_protocol *pp,
- unsigned int udphoff)
+ struct ip_vs_iphdr *iph)
{
struct udphdr _udph, *uh;
- uh = skb_header_pointer(skb, udphoff, sizeof(_udph), &_udph);
+ uh = skb_header_pointer(skb, iph->len, sizeof(_udph), &_udph);
if (uh == NULL)
return 0;
- if (uh->check != 0) {
- switch (skb->ip_summed) {
- case CHECKSUM_NONE:
- skb->csum = skb_checksum(skb, udphoff,
- skb->len - udphoff, 0);
- fallthrough;
- case CHECKSUM_COMPLETE:
-#ifdef CONFIG_IP_VS_IPV6
- if (af == AF_INET6) {
- if (csum_ipv6_magic(&ipv6_hdr(skb)->saddr,
- &ipv6_hdr(skb)->daddr,
- skb->len - udphoff,
- IPPROTO_UDP,
- skb->csum)) {
- IP_VS_DBG_RL_PKT(0, af, pp, skb, 0,
- "Failed checksum for");
- return 0;
- }
- } else
-#endif
- if (csum_tcpudp_magic(ip_hdr(skb)->saddr,
- ip_hdr(skb)->daddr,
- skb->len - udphoff,
- ip_hdr(skb)->protocol,
- skb->csum)) {
- IP_VS_DBG_RL_PKT(0, af, pp, skb, 0,
- "Failed checksum for");
- return 0;
- }
- break;
- default:
- /* No need to checksum. */
- break;
- }
+ if (!uh->check)
+ return 1;
+ if (!ip_vs_checksum_common_check(skb, iph->len, IPPROTO_UDP, af)) {
+ IP_VS_DBG_RL_PKT(0, af, pp, skb, iph->off,
+ "Failed checksum for");
+ return 0;
}
return 1;
}
--
2.47.3
^ permalink raw reply related [flat|nested] 19+ messages in thread
* Re: [PATCH net 09/13] ipvs: fix the checksum validations
2026-07-23 15:39 ` Pablo Neira Ayuso
@ 2026-07-23 18:37 ` Jakub Kicinski
2026-07-23 19:40 ` Pablo Neira Ayuso
0 siblings, 1 reply; 19+ messages in thread
From: Jakub Kicinski @ 2026-07-23 18:37 UTC (permalink / raw)
To: Pablo Neira Ayuso
Cc: netfilter-devel, davem, netdev, pabeni, edumazet, fw, horms
On Thu, 23 Jul 2026 17:39:02 +0200 Pablo Neira Ayuso wrote:
> Hi,
>
> On Thu, Jul 23, 2026 at 07:08:45AM -0700, Jakub Kicinski wrote:
> > On Wed, 22 Jul 2026 23:14:16 +0200 Pablo Neira Ayuso wrote:
> > > +/**
> > > + * ip_vs_checksum_common_check - validate checksum for TCP/UDP/ICMP
> > > + */
> >
> > this not-really-kdoc comment causes a bunch of new warnings,
> > and this file was previously warning free. Which in turn
> > makes our AI reviewer ignore the series. Could you respin?
>
> Yes, I can drop the patches that have the kdoc issues and respin.
Thanks! We're seeing extra delays due to lore outage overnight
and a lot of submissions suddenly appearing in the pipeline.
Looks like this will have to wait for rc6 :(
^ permalink raw reply [flat|nested] 19+ messages in thread
* Re: [PATCH net 09/13] ipvs: fix the checksum validations
2026-07-23 18:37 ` Jakub Kicinski
@ 2026-07-23 19:40 ` Pablo Neira Ayuso
0 siblings, 0 replies; 19+ messages in thread
From: Pablo Neira Ayuso @ 2026-07-23 19:40 UTC (permalink / raw)
To: Jakub Kicinski
Cc: netfilter-devel, davem, netdev, pabeni, edumazet, fw, horms
On Thu, Jul 23, 2026 at 11:37:26AM -0700, Jakub Kicinski wrote:
> On Thu, 23 Jul 2026 17:39:02 +0200 Pablo Neira Ayuso wrote:
> > Hi,
> >
> > On Thu, Jul 23, 2026 at 07:08:45AM -0700, Jakub Kicinski wrote:
> > > On Wed, 22 Jul 2026 23:14:16 +0200 Pablo Neira Ayuso wrote:
> > > > +/**
> > > > + * ip_vs_checksum_common_check - validate checksum for TCP/UDP/ICMP
> > > > + */
> > >
> > > this not-really-kdoc comment causes a bunch of new warnings,
> > > and this file was previously warning free. Which in turn
> > > makes our AI reviewer ignore the series. Could you respin?
> >
> > Yes, I can drop the patches that have the kdoc issues and respin.
>
> Thanks! We're seeing extra delays due to lore outage overnight
> and a lot of submissions suddenly appearing in the pipeline.
> Looks like this will have to wait for rc6 :(
OK, no problem, thanks for explaining.
^ permalink raw reply [flat|nested] 19+ messages in thread
end of thread, other threads:[~2026-07-23 19:41 UTC | newest]
Thread overview: 19+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-22 21:14 [PATCH net 00/13] Netfilter/IPVS fixes for net Pablo Neira Ayuso
2026-07-22 21:14 ` [PATCH net 01/13] netfilter: nf_conntrack_sip: widen NAT rewrite delta to s32 in sip_help_tcp() Pablo Neira Ayuso
2026-07-22 21:14 ` [PATCH net 02/13] selftests: netfilter: nft_flowtable.sh: fix offload counter verification for tunnel tests Pablo Neira Ayuso
2026-07-22 21:14 ` [PATCH net 03/13] netfilter: nf_conntrack_expect: add and use nf_ct_expect_related_pair() Pablo Neira Ayuso
2026-07-22 21:14 ` [PATCH net 04/13] netfilter: ipset: do not update comments from kernel-side hash adds Pablo Neira Ayuso
2026-07-22 21:14 ` [PATCH net 05/13] ipvs: do not propagate one-packet flag to synced conns Pablo Neira Ayuso
2026-07-22 21:14 ` [PATCH net 06/13] ipvs: adjust double hashing when fwd method changes Pablo Neira Ayuso
2026-07-22 21:14 ` [PATCH net 07/13] netfilter: nf_tables: make nft_object rhltable per table Pablo Neira Ayuso
2026-07-22 21:14 ` [PATCH net 08/13] netfilter: xt_hashlimit: validate hashtable supports XT_HASHLIMIT_RATE_MATCH Pablo Neira Ayuso
2026-07-22 21:14 ` [PATCH net 09/13] ipvs: fix the checksum validations Pablo Neira Ayuso
2026-07-23 14:08 ` Jakub Kicinski
2026-07-23 15:39 ` Pablo Neira Ayuso
2026-07-23 18:37 ` Jakub Kicinski
2026-07-23 19:40 ` Pablo Neira Ayuso
2026-07-22 21:14 ` [PATCH net 10/13] ipvs: fix places with wrong packet offsets Pablo Neira Ayuso
2026-07-22 21:14 ` [PATCH net 11/13] ipvs: do not mangle ICMP replies for non-first fragments Pablo Neira Ayuso
2026-07-22 21:14 ` [PATCH net 12/13] ipvs: clear the nfct flag under lock Pablo Neira Ayuso
2026-07-22 21:14 ` [PATCH net 13/13] netfilter: nft_payload: fix mask build for partial field offload Pablo Neira Ayuso
-- strict thread matches above, loose matches on Subject: below --
2026-07-23 16:38 [PATCH net,v2 00/13] Netfilter/IPVS fixes for net Pablo Neira Ayuso
2026-07-23 16:39 ` [PATCH net 09/13] ipvs: fix the checksum validations Pablo Neira Ayuso
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.