* [PATCH nf v2 0/2] ipvs: csum validations, part 2
@ 2026-07-28 20:25 Julian Anastasov
2026-07-28 20:25 ` [PATCH nf v2 1/2] ipvs: avoid out-of-bounds write in ip_vs_nat_icmp Julian Anastasov
` (2 more replies)
0 siblings, 3 replies; 4+ messages in thread
From: Julian Anastasov @ 2026-07-28 20:25 UTC (permalink / raw)
To: Simon Horman
Cc: Pablo Neira Ayuso, Florian Westphal, lvs-devel, netfilter-devel
Hello,
This patchset contains more fixes after the
previous batch of fixes for checksum validations.
The first patch avoids reading the IPv4 ihl field
multiple times to prevent local attacker to cause
out-of-bounds write in ip_vs_nat_icmp.
The second patch returns the checksum validations
which can be needed for the FORWARD hook.
Julian Anastasov (2):
ipvs: avoid out-of-bounds write in ip_vs_nat_icmp
ipvs: return the csum validation for forward hook
include/net/ip_vs.h | 32 +--------
net/netfilter/ipvs/ip_vs_core.c | 94 +++++++++++++++++----------
net/netfilter/ipvs/ip_vs_proto_sctp.c | 2 +-
net/netfilter/ipvs/ip_vs_xmit.c | 2 +-
4 files changed, 66 insertions(+), 64 deletions(-)
--
2.55.0
^ permalink raw reply [flat|nested] 4+ messages in thread
* [PATCH nf v2 1/2] ipvs: avoid out-of-bounds write in ip_vs_nat_icmp
2026-07-28 20:25 [PATCH nf v2 0/2] ipvs: csum validations, part 2 Julian Anastasov
@ 2026-07-28 20:25 ` Julian Anastasov
2026-07-28 20:25 ` [PATCH nf v2 2/2] ipvs: return the csum validation for forward hook Julian Anastasov
2026-07-28 21:39 ` [PATCH nf v2 0/2] ipvs: csum validations, part 2 Julian Anastasov
2 siblings, 0 replies; 4+ messages in thread
From: Julian Anastasov @ 2026-07-28 20:25 UTC (permalink / raw)
To: Simon Horman
Cc: Pablo Neira Ayuso, Florian Westphal, lvs-devel, netfilter-devel
Sashiko warns that local attacker can modify the packet
while it is processed by IPVS. Some places read the
IP ihl field multiple times which can cause out-of-bounds
access. One such place is ip_vs_nat_icmp where we
can write after the validated area.
Fix it by providing ciph argument just like it is done for
IPv6 and use ciph->len as offset to the embedded transport
header.
Modify some IPv4 header checks by reading the ihl field
only once.
Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Link: https://sashiko.dev/#/patchset/20260722101517.36313-1-ja%40ssi.bg
Signed-off-by: Julian Anastasov <ja@ssi.bg>
---
include/net/ip_vs.h | 2 +-
net/netfilter/ipvs/ip_vs_core.c | 67 +++++++++++++++++----------------
net/netfilter/ipvs/ip_vs_xmit.c | 2 +-
3 files changed, 36 insertions(+), 35 deletions(-)
diff --git a/include/net/ip_vs.h b/include/net/ip_vs.h
index e6ca930a3507..1235f1934e94 100644
--- a/include/net/ip_vs.h
+++ b/include/net/ip_vs.h
@@ -2062,7 +2062,7 @@ 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,
- bool has_ports);
+ bool has_ports, struct ip_vs_iphdr *ciph);
#ifdef CONFIG_IP_VS_IPV6
void ip_vs_nat_icmp_v6(struct sk_buff *skb, struct ip_vs_protocol *pp,
diff --git a/net/netfilter/ipvs/ip_vs_core.c b/net/netfilter/ipvs/ip_vs_core.c
index 6b79e0c4d9e2..0bdaeb4ed61e 100644
--- a/net/netfilter/ipvs/ip_vs_core.c
+++ b/net/netfilter/ipvs/ip_vs_core.c
@@ -925,28 +925,27 @@ static int ip_vs_route_me_harder(struct netns_ipvs *ipvs, int af,
*/
void ip_vs_nat_icmp(struct sk_buff *skb, struct ip_vs_protocol *pp,
struct ip_vs_conn *cp, int inout, unsigned int toff,
- bool has_ports)
+ bool has_ports, struct ip_vs_iphdr *ciph)
{
struct iphdr *iph = ip_hdr(skb);
struct icmphdr *icmph = (struct icmphdr *)(skb->data + toff);
- struct iphdr *ciph = (struct iphdr *)(icmph + 1);
- unsigned int coff __maybe_unused = toff + sizeof(struct icmphdr);
+ struct iphdr *cih = (struct iphdr *)(icmph + 1);
if (inout) {
iph->saddr = cp->vaddr.ip;
ip_send_check(iph);
- ciph->daddr = cp->vaddr.ip;
- ip_send_check(ciph);
+ cih->daddr = cp->vaddr.ip;
+ ip_send_check(cih);
} else {
iph->daddr = cp->daddr.ip;
ip_send_check(iph);
- ciph->saddr = cp->daddr.ip;
- ip_send_check(ciph);
+ cih->saddr = cp->daddr.ip;
+ ip_send_check(cih);
}
/* the TCP/UDP/SCTP port */
if (has_ports) {
- __be16 *ports = (void *)ciph + ciph->ihl*4;
+ __be16 *ports = (void *)(skb->data + ciph->len);
if (inout)
ports[1] = cp->vport;
@@ -960,10 +959,10 @@ void ip_vs_nat_icmp(struct sk_buff *skb, struct ip_vs_protocol *pp,
skb->ip_summed = CHECKSUM_UNNECESSARY;
if (inout)
- IP_VS_DBG_PKT(11, AF_INET, pp, skb, coff,
+ IP_VS_DBG_PKT(11, AF_INET, pp, skb, ciph->off,
"Forwarding altered outgoing ICMP");
else
- IP_VS_DBG_PKT(11, AF_INET, pp, skb, coff,
+ IP_VS_DBG_PKT(11, AF_INET, pp, skb, ciph->off,
"Forwarding altered incoming ICMP");
}
@@ -1056,7 +1055,7 @@ static int handle_response_icmp(int af, struct sk_buff *skb,
ip_vs_nat_icmp_v6(skb, pp, cp, 1, toff, has_ports, ciph);
else
#endif
- ip_vs_nat_icmp(skb, pp, cp, 1, toff, has_ports);
+ ip_vs_nat_icmp(skb, pp, cp, 1, toff, has_ports, ciph);
if (ip_vs_route_me_harder(cp->ipvs, af, skb, hooknum))
goto out;
@@ -1092,7 +1091,7 @@ static int ip_vs_out_icmp(struct netns_ipvs *ipvs, struct sk_buff *skb,
struct ip_vs_iphdr ciph;
struct ip_vs_conn *cp;
struct ip_vs_protocol *pp;
- unsigned int offset, ihl;
+ unsigned int offset;
union nf_inet_addr snet;
*related = 1;
@@ -1105,7 +1104,6 @@ static int ip_vs_out_icmp(struct netns_ipvs *ipvs, struct sk_buff *skb,
return NF_ACCEPT;
}
- ihl = ipvsh->len;
offset = ipvsh->len;
ic = skb_header_pointer(skb, offset, sizeof(_icmph), &_icmph);
if (ic == NULL)
@@ -1131,11 +1129,15 @@ static int ip_vs_out_icmp(struct netns_ipvs *ipvs, struct sk_buff *skb,
/* Now find the contained IP header */
offset += sizeof(_icmph);
+ if (!ip_vs_fill_iph_skb_icmp(AF_INET, skb, offset, true, &ciph))
+ return NF_ACCEPT; /* The packet looks wrong, ignore */
+
cih = skb_header_pointer(skb, offset, sizeof(_ciph), &_ciph);
- if (!(cih && cih->version == 4 && cih->ihl >= 5))
+ if (!(cih && cih->version == 4 &&
+ ciph.len - ciph.off >= sizeof(struct iphdr)))
return NF_ACCEPT; /* The packet looks wrong, ignore */
- pp = ip_vs_proto_get(cih->protocol);
+ pp = ip_vs_proto_get(ciph.protocol);
if (!pp)
return NF_ACCEPT;
@@ -1146,8 +1148,6 @@ static int ip_vs_out_icmp(struct netns_ipvs *ipvs, struct sk_buff *skb,
IP_VS_DBG_PKT(11, AF_INET, pp, skb, offset,
"Checking outgoing ICMP for");
- ip_vs_fill_iph_skb_icmp(AF_INET, skb, offset, true, &ciph);
-
/* 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_INET, skb, &ciph);
@@ -1155,8 +1155,8 @@ static int ip_vs_out_icmp(struct netns_ipvs *ipvs, struct sk_buff *skb,
return NF_ACCEPT;
snet.ip = ipvsh->saddr.ip;
- return handle_response_icmp(AF_INET, skb, &snet, cp, pp, &ciph, ihl,
- hooknum);
+ return handle_response_icmp(AF_INET, skb, &snet, cp, pp, &ciph,
+ ipvsh->len, hooknum);
}
#ifdef CONFIG_IP_VS_IPV6
@@ -1803,10 +1803,12 @@ ip_vs_in_icmp(struct netns_ipvs *ipvs, struct sk_buff *skb, int *related,
/* Now find the contained IP header */
offset += sizeof(_icmph);
cih = skb_header_pointer(skb, offset, sizeof(_ciph), &_ciph);
- if (!(cih && cih->version == 4 && cih->ihl >= 5))
+ if (!cih)
return NF_ACCEPT; /* The packet looks wrong, ignore */
- raddr = (union nf_inet_addr *)&cih->daddr;
hlen_ipip = cih->ihl * 4;
+ if (!(cih->version == 4 && hlen_ipip >= sizeof(struct iphdr)))
+ return NF_ACCEPT; /* The packet looks wrong, ignore */
+ raddr = (union nf_inet_addr *)&cih->daddr;
/* Special case for errors for IPIP/UDP/GRE tunnel packets */
tunnel = false;
@@ -1823,9 +1825,6 @@ ip_vs_in_icmp(struct netns_ipvs *ipvs, struct sk_buff *skb, int *related,
if (!dest || dest->tun_type != IP_VS_CONN_F_TUNNEL_TYPE_IPIP)
return NF_ACCEPT;
offset += hlen_ipip;
- cih = skb_header_pointer(skb, offset, sizeof(_ciph), &_ciph);
- if (!(cih && cih->version == 4 && cih->ihl >= 5))
- return NF_ACCEPT; /* The packet looks wrong, ignore */
tunnel = true;
} else if ((cih->protocol == IPPROTO_UDP || /* Can be UDP encap */
cih->protocol == IPPROTO_GRE) && /* Can be GRE encap */
@@ -1850,21 +1849,25 @@ ip_vs_in_icmp(struct netns_ipvs *ipvs, struct sk_buff *skb, int *related,
/* Skip IP and UDP/GRE tunnel headers */
offset = offset2 + ulen;
/* Now we should be at the original IP header */
- cih = skb_header_pointer(skb, offset, sizeof(_ciph),
- &_ciph);
- if (cih && cih->version == 4 && cih->ihl >= 5 &&
- iproto == IPPROTO_IPIP)
+ if (iproto == IPPROTO_IPIP)
tunnel = true;
else
return NF_ACCEPT;
}
}
- pd = ip_vs_proto_data_get(ipvs, cih->protocol);
+ if (!ip_vs_fill_iph_skb_icmp(AF_INET, skb, offset, !tunnel, &ciph))
+ return NF_ACCEPT;
+ pd = ip_vs_proto_data_get(ipvs, ciph.protocol);
if (!pd)
return NF_ACCEPT;
pp = pd->pp;
+ cih = skb_header_pointer(skb, offset, sizeof(_ciph), &_ciph);
+ if (!(cih && cih->version == 4 &&
+ ciph.len - ciph.off >= sizeof(struct iphdr)))
+ return NF_ACCEPT; /* The packet looks wrong, ignore */
+
/* Is the embedded protocol header present? */
if (unlikely(cih->frag_off & htons(IP_OFFSET) && !pp->dont_defrag))
return NF_ACCEPT;
@@ -1872,9 +1875,6 @@ ip_vs_in_icmp(struct netns_ipvs *ipvs, struct sk_buff *skb, int *related,
IP_VS_DBG_PKT(11, AF_INET, pp, skb, offset,
"Checking incoming ICMP for");
- offset2 = offset;
- ip_vs_fill_iph_skb_icmp(AF_INET, skb, offset, !tunnel, &ciph);
-
/* The embedded headers contain source and dest in reverse order.
* For IPIP/UDP/GRE tunnel this is error for request, not for reply.
*/
@@ -1904,11 +1904,12 @@ ip_vs_in_icmp(struct netns_ipvs *ipvs, struct sk_buff *skb, int *related,
}
if (tunnel) {
- unsigned int hlen_orig = cih->ihl * 4;
+ unsigned int hlen_orig = ciph.len - ciph.off;
__be32 info = ic->un.gateway;
__u8 type = ic->type;
__u8 code = ic->code;
+ offset2 = offset;
/* Update the MTU */
if (ic->type == ICMP_DEST_UNREACH &&
ic->code == ICMP_FRAG_NEEDED) {
diff --git a/net/netfilter/ipvs/ip_vs_xmit.c b/net/netfilter/ipvs/ip_vs_xmit.c
index cdc567ceabf4..4469dcc4bb1e 100644
--- a/net/netfilter/ipvs/ip_vs_xmit.c
+++ b/net/netfilter/ipvs/ip_vs_xmit.c
@@ -1582,7 +1582,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, has_ports);
+ ip_vs_nat_icmp(skb, pp, cp, 0, toff, has_ports, ciph);
/* Another hack: avoid icmp_send in ip_fragment */
skb->ignore_df = 1;
--
2.55.0
^ permalink raw reply related [flat|nested] 4+ messages in thread
* [PATCH nf v2 2/2] ipvs: return the csum validation for forward hook
2026-07-28 20:25 [PATCH nf v2 0/2] ipvs: csum validations, part 2 Julian Anastasov
2026-07-28 20:25 ` [PATCH nf v2 1/2] ipvs: avoid out-of-bounds write in ip_vs_nat_icmp Julian Anastasov
@ 2026-07-28 20:25 ` Julian Anastasov
2026-07-28 21:39 ` [PATCH nf v2 0/2] ipvs: csum validations, part 2 Julian Anastasov
2 siblings, 0 replies; 4+ messages in thread
From: Julian Anastasov @ 2026-07-28 20:25 UTC (permalink / raw)
To: Simon Horman
Cc: Pablo Neira Ayuso, Florian Westphal, lvs-devel, netfilter-devel
Sashiko notes that playing games with the skb dst and rt
flags instead of providing hooknum is not a good idea
when validating the checksums.
Also, skipping checksum validation for FORWARD packets
risk silent data corruption, even if the only user is
the FTP-CMD packets coming from the real server.
Sashiko also noticed that by using common checksum
helper in the previous commit we actually fixed old bug
where the TCP/UDP checksum for IPv6 on CHECKSUM_COMPLETE
was not validated correctly.
Fixes: e876b75b9020 ("ipvs: fix the checksum validations")
Link: https://sashiko.dev/#/patchset/20260722211420.153933-1-pablo%40netfilter.org
Link: https://sashiko.dev/#/patchset/20260727185024.67534-1-ja%40ssi.bg
Signed-off-by: Julian Anastasov <ja@ssi.bg>
---
include/net/ip_vs.h | 30 ++-------------------------
net/netfilter/ipvs/ip_vs_core.c | 27 ++++++++++++++++++++++++
net/netfilter/ipvs/ip_vs_proto_sctp.c | 2 +-
3 files changed, 30 insertions(+), 29 deletions(-)
diff --git a/include/net/ip_vs.h b/include/net/ip_vs.h
index 1235f1934e94..10f5ce76a0ae 100644
--- a/include/net/ip_vs.h
+++ b/include/net/ip_vs.h
@@ -25,9 +25,7 @@
#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
@@ -2095,32 +2093,8 @@ 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);
-}
+bool ip_vs_checksum_common_check(struct sk_buff *skb, int offset, int proto,
+ int 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 0bdaeb4ed61e..a51418a449cc 100644
--- a/net/netfilter/ipvs/ip_vs_core.c
+++ b/net/netfilter/ipvs/ip_vs_core.c
@@ -872,6 +872,33 @@ static __sum16 ip_vs_checksum_complete(struct sk_buff *skb, int offset)
return csum_fold(skb_checksum(skb, offset, skb->len - offset, 0));
}
+bool ip_vs_checksum_common_check(struct sk_buff *skb, int offset, int proto,
+ int af)
+{
+ __wsum csum;
+
+ if (skb_csum_unnecessary(skb))
+ return true;
+ if (skb->ip_summed == CHECKSUM_NONE)
+ csum = skb_checksum(skb, offset, skb->len - offset, 0);
+ else if (skb->ip_summed == CHECKSUM_COMPLETE)
+ csum = csum_sub(skb->csum, skb_checksum(skb, 0, offset, 0));
+ else
+ return true;
+
+#ifdef CONFIG_IP_VS_IPV6
+ if (af == AF_INET6)
+ return !csum_ipv6_magic(&ipv6_hdr(skb)->saddr,
+ &ipv6_hdr(skb)->daddr,
+ skb->len - offset,
+ proto, csum);
+#endif
+ if (proto == IPPROTO_ICMP)
+ return !csum_fold(csum);
+ return !csum_tcpudp_magic(ip_hdr(skb)->saddr, ip_hdr(skb)->daddr,
+ skb->len - offset, proto, csum);
+}
+
static inline enum ip_defrag_users ip_vs_defrag_user(unsigned int hooknum)
{
if (NF_INET_LOCAL_IN == hooknum)
diff --git a/net/netfilter/ipvs/ip_vs_proto_sctp.c b/net/netfilter/ipvs/ip_vs_proto_sctp.c
index 3dbd3096e163..dd54a37dee29 100644
--- a/net/netfilter/ipvs/ip_vs_proto_sctp.c
+++ b/net/netfilter/ipvs/ip_vs_proto_sctp.c
@@ -193,7 +193,7 @@ sctp_csum_check(int af, struct sk_buff *skb, struct ip_vs_protocol *pp,
struct sctphdr *sh;
__le32 cmp, val;
- if (!ip_vs_checksum_needed(skb, af))
+ if (skb_csum_unnecessary(skb))
return 1;
sh = (struct sctphdr *)(skb->data + sctphoff);
cmp = sh->checksum;
--
2.55.0
^ permalink raw reply related [flat|nested] 4+ messages in thread
* Re: [PATCH nf v2 0/2] ipvs: csum validations, part 2
2026-07-28 20:25 [PATCH nf v2 0/2] ipvs: csum validations, part 2 Julian Anastasov
2026-07-28 20:25 ` [PATCH nf v2 1/2] ipvs: avoid out-of-bounds write in ip_vs_nat_icmp Julian Anastasov
2026-07-28 20:25 ` [PATCH nf v2 2/2] ipvs: return the csum validation for forward hook Julian Anastasov
@ 2026-07-28 21:39 ` Julian Anastasov
2 siblings, 0 replies; 4+ messages in thread
From: Julian Anastasov @ 2026-07-28 21:39 UTC (permalink / raw)
To: Simon Horman
Cc: Pablo Neira Ayuso, Florian Westphal, lvs-devel, netfilter-devel
Hello,
On Tue, 28 Jul 2026, Julian Anastasov wrote:
> This patchset contains more fixes after the
> previous batch of fixes for checksum validations.
>
> The first patch avoids reading the IPv4 ihl field
> multiple times to prevent local attacker to cause
> out-of-bounds write in ip_vs_nat_icmp.
>
> The second patch returns the checksum validations
> which can be needed for the FORWARD hook.
>
> Julian Anastasov (2):
> ipvs: avoid out-of-bounds write in ip_vs_nat_icmp
> ipvs: return the csum validation for forward hook
There is something more to fix, will send v3:
https://sashiko.dev/#/patchset/20260728202520.59179-1-ja%40ssi.bg
pw-bot: changes-requested
Regards
--
Julian Anastasov <ja@ssi.bg>
^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2026-07-28 21:39 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-28 20:25 [PATCH nf v2 0/2] ipvs: csum validations, part 2 Julian Anastasov
2026-07-28 20:25 ` [PATCH nf v2 1/2] ipvs: avoid out-of-bounds write in ip_vs_nat_icmp Julian Anastasov
2026-07-28 20:25 ` [PATCH nf v2 2/2] ipvs: return the csum validation for forward hook Julian Anastasov
2026-07-28 21:39 ` [PATCH nf v2 0/2] ipvs: csum validations, part 2 Julian Anastasov
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.