mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2026-08-09 06:14:34 +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:
committed by
Pablo Neira Ayuso
parent
305b63e140
commit
e876b75b90
+29
-2
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user