Skip to content

Commit e7bbc87

Browse files
Sean Tranchettipopcornmix
Sean Tranchetti
authored andcommitted
net: udp: fix handling of CHECKSUM_COMPLETE packets
Current handling of CHECKSUM_COMPLETE packets by the UDP stack is incorrect for any packet that has an incorrect checksum value. udp4/6_csum_init() will both make a call to __skb_checksum_validate_complete() to initialize/validate the csum field when receiving a CHECKSUM_COMPLETE packet. When this packet fails validation, skb->csum will be overwritten with the pseudoheader checksum so the packet can be fully validated by software, but the skb->ip_summed value will be left as CHECKSUM_COMPLETE so that way the stack can later warn the user about their hardware spewing bad checksums. Unfortunately, leaving the SKB in this state can cause problems later on in the checksum calculation. Since the the packet is still marked as CHECKSUM_COMPLETE, udp_csum_pull_header() will SUBTRACT the checksum of the UDP header from skb->csum instead of adding it, leaving us with a garbage value in that field. Once we try to copy the packet to userspace in the udp4/6_recvmsg(), we'll make a call to skb_copy_and_csum_datagram_msg() to checksum the packet data and add it in the garbage skb->csum value to perform our final validation check. Since the value we're validating is not the proper checksum, it's possible that the folded value could come out to 0, causing us not to drop the packet. Instead, we believe that the packet was checksummed incorrectly by hardware since skb->ip_summed is still CHECKSUM_COMPLETE, and we attempt to warn the user with netdev_rx_csum_fault(skb->dev); Unfortunately, since this is the UDP path, skb->dev has been overwritten by skb->dev_scratch and is no longer a valid pointer, so we end up reading invalid memory. This patch addresses this problem in two ways: 1) Remove the invalid netdev_rx_csum_fault(skb->dev) call from skb_copy_and_csum_datagram_msg(). Since this is used by UDP where skb->dev is invalid, trying to warn doesn't make sense. 2) Add better CHECKSUM_COMPLETE handling to udp4/6_csum_init(). If we receive a packet that's CHECKSUM_COMPLETE that fails verification (i.e. skb->csum_valid == 0), check who performed the calculation. It's possible that the checksum was done in software by the network stack earlier (such as Netfilter's CONNTRACK module), and if that says the checksum is bad, we can drop the packet immediately instead of waiting until we try and copy it to userspace. Otherwise, we need to mark the SKB as CHECKSUM_NONE, since the skb->csum field no longer contains the full packet checksum after the call to __skb_checksum_validate_complete(). Signed-off-by: Sean Tranchetti <[email protected]>
1 parent 08f3bba commit e7bbc87

File tree

3 files changed

+36
-7
lines changed

3 files changed

+36
-7
lines changed

net/core/datagram.c

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -807,9 +807,6 @@ int skb_copy_and_csum_datagram_msg(struct sk_buff *skb,
807807
iov_iter_revert(&msg->msg_iter, chunk);
808808
return -EINVAL;
809809
}
810-
811-
if (unlikely(skb->ip_summed == CHECKSUM_COMPLETE))
812-
netdev_rx_csum_fault(skb->dev);
813810
}
814811
return 0;
815812
fault:

net/ipv4/udp.c

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2124,8 +2124,24 @@ static inline int udp4_csum_init(struct sk_buff *skb, struct udphdr *uh,
21242124
/* Note, we are only interested in != 0 or == 0, thus the
21252125
* force to int.
21262126
*/
2127-
return (__force int)skb_checksum_init_zero_check(skb, proto, uh->check,
2128-
inet_compute_pseudo);
2127+
err = (__force int)skb_checksum_init_zero_check(skb, proto, uh->check,
2128+
inet_compute_pseudo);
2129+
if (err)
2130+
return err;
2131+
2132+
if (skb->ip_summed == CHECKSUM_COMPLETE && !skb->csum_valid) {
2133+
/* If SW calculated the value, we know it's bad */
2134+
if (skb->csum_complete_sw)
2135+
return 1;
2136+
2137+
/* HW says the value is bad. Let's validate that.
2138+
* skb->csum is no longer the full packet checksum,
2139+
* so don't treat it as such.
2140+
*/
2141+
skb_checksum_complete_unset(skb);
2142+
}
2143+
2144+
return 0;
21292145
}
21302146

21312147
/* wrapper for udp_queue_rcv_skb tacking care of csum conversion and

net/ipv6/ip6_checksum.c

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,8 +88,24 @@ int udp6_csum_init(struct sk_buff *skb, struct udphdr *uh, int proto)
8888
* Note, we are only interested in != 0 or == 0, thus the
8989
* force to int.
9090
*/
91-
return (__force int)skb_checksum_init_zero_check(skb, proto, uh->check,
92-
ip6_compute_pseudo);
91+
err = (__force int)skb_checksum_init_zero_check(skb, proto, uh->check,
92+
ip6_compute_pseudo);
93+
if (err)
94+
return err;
95+
96+
if (skb->ip_summed == CHECKSUM_COMPLETE && !skb->csum_valid) {
97+
/* If SW calculated the value, we know it's bad */
98+
if (skb->csum_complete_sw)
99+
return 1;
100+
101+
/* HW says the value is bad. Let's validate that.
102+
* skb->csum is no longer the full packet checksum,
103+
* so don't treat is as such.
104+
*/
105+
skb_checksum_complete_unset(skb);
106+
}
107+
108+
return 0;
93109
}
94110
EXPORT_SYMBOL(udp6_csum_init);
95111

0 commit comments

Comments
 (0)