mirror of
https://github.com/torvalds/linux.git
synced 2026-09-22 12:44:03 +02:00
ipvs: fix the checksum validations
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: 2a3b791e6e ("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>
This commit is contained in:
parent
305b63e140
commit
e876b75b90
|
|
@ -25,7 +25,9 @@
|
||||||
#include <linux/netfilter.h> /* for union nf_inet_addr */
|
#include <linux/netfilter.h> /* for union nf_inet_addr */
|
||||||
#include <linux/ip.h>
|
#include <linux/ip.h>
|
||||||
#include <linux/ipv6.h> /* for struct ipv6hdr */
|
#include <linux/ipv6.h> /* for struct ipv6hdr */
|
||||||
|
#include <net/route.h>
|
||||||
#include <net/ipv6.h>
|
#include <net/ipv6.h>
|
||||||
|
#include <net/ip6_fib.h>
|
||||||
#if IS_ENABLED(CONFIG_NF_CONNTRACK)
|
#if IS_ENABLED(CONFIG_NF_CONNTRACK)
|
||||||
#include <net/netfilter/nf_conntrack.h>
|
#include <net/netfilter/nf_conntrack.h>
|
||||||
#endif
|
#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);
|
struct ip_vs_conn *cp, int dir);
|
||||||
#endif
|
#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)
|
static inline __wsum ip_vs_check_diff4(__be32 old, __be32 new, __wsum oldsum)
|
||||||
{
|
{
|
||||||
__be32 diff[2] = { ~old, new };
|
__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);
|
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 */
|
/* Forget current conntrack (unconfirmed) and attach notrack entry */
|
||||||
static inline void ip_vs_notrack(struct sk_buff *skb)
|
static inline void ip_vs_notrack(struct sk_buff *skb)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -867,7 +867,7 @@ static int sysctl_nat_icmp_send(struct netns_ipvs *ipvs) { return 0; }
|
||||||
|
|
||||||
#endif
|
#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));
|
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 offset, unsigned int ihl,
|
||||||
unsigned int hooknum)
|
unsigned int hooknum)
|
||||||
{
|
{
|
||||||
|
int iproto = af == AF_INET6 ? IPPROTO_ICMPV6 : IPPROTO_ICMP;
|
||||||
unsigned int verdict = NF_DROP;
|
unsigned int verdict = NF_DROP;
|
||||||
|
|
||||||
if (IP_VS_FWD_METHOD(cp) != IP_VS_CONN_F_MASQ)
|
if (IP_VS_FWD_METHOD(cp) != IP_VS_CONN_F_MASQ)
|
||||||
goto after_nat;
|
goto after_nat;
|
||||||
|
|
||||||
/* Ensure the checksum is correct */
|
/* 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! */
|
/* Failed checksum! */
|
||||||
IP_VS_DBG_BUF(1, "Forward ICMP: failed checksum from %s!\n",
|
IP_VS_DBG_BUF(1, "Forward ICMP: failed checksum from %s!\n",
|
||||||
IP_VS_DBG_ADDR(af, snet));
|
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;
|
verdict = NF_DROP;
|
||||||
|
|
||||||
/* Ensure the checksum is correct */
|
/* 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! */
|
/* Failed checksum! */
|
||||||
IP_VS_DBG(1, "Incoming ICMP: failed checksum from %pI4!\n",
|
IP_VS_DBG(1, "Incoming ICMP: failed checksum from %pI4!\n",
|
||||||
&iph->saddr);
|
&iph->saddr);
|
||||||
|
|
@ -2064,6 +2066,18 @@ static int ip_vs_in_icmp_v6(struct netns_ipvs *ipvs, struct sk_buff *skb,
|
||||||
goto out;
|
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 */
|
/* do the statistics and put it back */
|
||||||
ip_vs_in_stats(cp, skb);
|
ip_vs_in_stats(cp, skb);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@
|
||||||
|
|
||||||
static int
|
static int
|
||||||
sctp_csum_check(int af, struct sk_buff *skb, struct ip_vs_protocol *pp,
|
sctp_csum_check(int af, struct sk_buff *skb, struct ip_vs_protocol *pp,
|
||||||
unsigned int sctphoff);
|
struct ip_vs_iphdr *iph);
|
||||||
|
|
||||||
static int
|
static int
|
||||||
sctp_conn_schedule(struct netns_ipvs *ipvs, int af, struct sk_buff *skb,
|
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;
|
int ret;
|
||||||
|
|
||||||
/* Some checks before mangling */
|
/* Some checks before mangling */
|
||||||
if (!sctp_csum_check(cp->af, skb, pp, sctphoff))
|
if (!sctp_csum_check(cp->af, skb, pp, iph))
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
/* Call application helper if needed */
|
/* Call application helper if needed */
|
||||||
|
|
@ -157,7 +157,7 @@ sctp_dnat_handler(struct sk_buff *skb, struct ip_vs_protocol *pp,
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
/* Some checks before mangling */
|
/* Some checks before mangling */
|
||||||
if (!sctp_csum_check(cp->af, skb, pp, sctphoff))
|
if (!sctp_csum_check(cp->af, skb, pp, iph))
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
/* Call application helper if needed */
|
/* Call application helper if needed */
|
||||||
|
|
@ -187,19 +187,22 @@ sctp_dnat_handler(struct sk_buff *skb, struct ip_vs_protocol *pp,
|
||||||
|
|
||||||
static int
|
static int
|
||||||
sctp_csum_check(int af, struct sk_buff *skb, struct ip_vs_protocol *pp,
|
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;
|
struct sctphdr *sh;
|
||||||
__le32 cmp, val;
|
__le32 cmp, val;
|
||||||
|
|
||||||
|
if (!ip_vs_checksum_needed(skb, af))
|
||||||
|
return 1;
|
||||||
sh = (struct sctphdr *)(skb->data + sctphoff);
|
sh = (struct sctphdr *)(skb->data + sctphoff);
|
||||||
cmp = sh->checksum;
|
cmp = sh->checksum;
|
||||||
val = sctp_compute_cksum(skb, sctphoff);
|
val = sctp_compute_cksum(skb, sctphoff);
|
||||||
|
|
||||||
if (val != cmp) {
|
if (val != cmp) {
|
||||||
/* CRC failure, dump it. */
|
/* CRC failure, dump it. */
|
||||||
IP_VS_DBG_RL_PKT(0, af, pp, skb, 0,
|
IP_VS_DBG_RL_PKT(0, af, pp, skb, iph->off,
|
||||||
"Failed checksum for");
|
"Failed checksum for");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
return 1;
|
return 1;
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@
|
||||||
|
|
||||||
static int
|
static int
|
||||||
tcp_csum_check(int af, struct sk_buff *skb, struct ip_vs_protocol *pp,
|
tcp_csum_check(int af, struct sk_buff *skb, struct ip_vs_protocol *pp,
|
||||||
unsigned int tcphoff);
|
struct ip_vs_iphdr *iph);
|
||||||
|
|
||||||
static int
|
static int
|
||||||
tcp_conn_schedule(struct netns_ipvs *ipvs, int af, struct sk_buff *skb,
|
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;
|
int ret;
|
||||||
|
|
||||||
/* Some checks before mangling */
|
/* Some checks before mangling */
|
||||||
if (!tcp_csum_check(cp->af, skb, pp, tcphoff))
|
if (!tcp_csum_check(cp->af, skb, pp, iph))
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
/* Call application helper if needed */
|
/* Call application helper if needed */
|
||||||
|
|
@ -244,7 +244,7 @@ tcp_dnat_handler(struct sk_buff *skb, struct ip_vs_protocol *pp,
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
/* Some checks before mangling */
|
/* Some checks before mangling */
|
||||||
if (!tcp_csum_check(cp->af, skb, pp, tcphoff))
|
if (!tcp_csum_check(cp->af, skb, pp, iph))
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
@ -302,41 +302,13 @@ tcp_dnat_handler(struct sk_buff *skb, struct ip_vs_protocol *pp,
|
||||||
|
|
||||||
static int
|
static int
|
||||||
tcp_csum_check(int af, struct sk_buff *skb, struct ip_vs_protocol *pp,
|
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) {
|
if (!ip_vs_checksum_common_check(skb, iph->len, IPPROTO_TCP, af)) {
|
||||||
case CHECKSUM_NONE:
|
IP_VS_DBG_RL_PKT(0, af, pp, skb, iph->off,
|
||||||
skb->csum = skb_checksum(skb, tcphoff, skb->len - tcphoff, 0);
|
"Failed checksum for");
|
||||||
fallthrough;
|
return 0;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@
|
||||||
|
|
||||||
static int
|
static int
|
||||||
udp_csum_check(int af, struct sk_buff *skb, struct ip_vs_protocol *pp,
|
udp_csum_check(int af, struct sk_buff *skb, struct ip_vs_protocol *pp,
|
||||||
unsigned int udphoff);
|
struct ip_vs_iphdr *iph);
|
||||||
|
|
||||||
static int
|
static int
|
||||||
udp_conn_schedule(struct netns_ipvs *ipvs, int af, struct sk_buff *skb,
|
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;
|
int ret;
|
||||||
|
|
||||||
/* Some checks before mangling */
|
/* Some checks before mangling */
|
||||||
if (!udp_csum_check(cp->af, skb, pp, udphoff))
|
if (!udp_csum_check(cp->af, skb, pp, iph))
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
@ -238,7 +238,7 @@ udp_dnat_handler(struct sk_buff *skb, struct ip_vs_protocol *pp,
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
/* Some checks before mangling */
|
/* Some checks before mangling */
|
||||||
if (!udp_csum_check(cp->af, skb, pp, udphoff))
|
if (!udp_csum_check(cp->af, skb, pp, iph))
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
@ -298,48 +298,20 @@ udp_dnat_handler(struct sk_buff *skb, struct ip_vs_protocol *pp,
|
||||||
|
|
||||||
static int
|
static int
|
||||||
udp_csum_check(int af, struct sk_buff *skb, struct ip_vs_protocol *pp,
|
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;
|
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)
|
if (uh == NULL)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
if (uh->check != 0) {
|
if (!uh->check)
|
||||||
switch (skb->ip_summed) {
|
return 1;
|
||||||
case CHECKSUM_NONE:
|
if (!ip_vs_checksum_common_check(skb, iph->len, IPPROTO_UDP, af)) {
|
||||||
skb->csum = skb_checksum(skb, udphoff,
|
IP_VS_DBG_RL_PKT(0, af, pp, skb, iph->off,
|
||||||
skb->len - udphoff, 0);
|
"Failed checksum for");
|
||||||
fallthrough;
|
return 0;
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user