Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

udp: properly support MSG_PEEK with truncated buffers

Backport of this upstream commit into stable kernels :
89c22d8c3b27 ("net: Fix skb csum races when peeking")
exposed a bug in udp stack vs MSG_PEEK support, when user provides
a buffer smaller than skb payload.

In this case,
skb_copy_and_csum_datagram_iovec(skb, sizeof(struct udphdr),
msg->msg_iov);
returns -EFAULT.

This bug does not happen in upstream kernels since Al Viro did a great
job to replace this into :
skb_copy_and_csum_datagram_msg(skb, sizeof(struct udphdr), msg);
This variant is safe vs short buffers.

For the time being, instead reverting Herbert Xu patch and add back
skb->ip_summed invalid changes, simply store the result of
udp_lib_checksum_complete() so that we avoid computing the checksum a
second time, and avoid the problematic
skb_copy_and_csum_datagram_iovec() call.

This patch can be applied on recent kernels as it avoids a double
checksumming, then backported to stable kernels as a bug fix.

Signed-off-by: Eric Dumazet <edumazet@google.com>
Acked-by: Herbert Xu <herbert@gondor.apana.org.au>
Signed-off-by: David S. Miller <davem@davemloft.net>

authored by

Eric Dumazet and committed by
David S. Miller
197c949e 815bc580

+8 -4
+4 -2
net/ipv4/udp.c
··· 1271 1271 int peeked, off = 0; 1272 1272 int err; 1273 1273 int is_udplite = IS_UDPLITE(sk); 1274 + bool checksum_valid = false; 1274 1275 bool slow; 1275 1276 1276 1277 if (flags & MSG_ERRQUEUE) ··· 1297 1296 */ 1298 1297 1299 1298 if (copied < ulen || UDP_SKB_CB(skb)->partial_cov) { 1300 - if (udp_lib_checksum_complete(skb)) 1299 + checksum_valid = !udp_lib_checksum_complete(skb); 1300 + if (!checksum_valid) 1301 1301 goto csum_copy_err; 1302 1302 } 1303 1303 1304 - if (skb_csum_unnecessary(skb)) 1304 + if (checksum_valid || skb_csum_unnecessary(skb)) 1305 1305 err = skb_copy_datagram_msg(skb, sizeof(struct udphdr), 1306 1306 msg, copied); 1307 1307 else {
+4 -2
net/ipv6/udp.c
··· 402 402 int peeked, off = 0; 403 403 int err; 404 404 int is_udplite = IS_UDPLITE(sk); 405 + bool checksum_valid = false; 405 406 int is_udp4; 406 407 bool slow; 407 408 ··· 434 433 */ 435 434 436 435 if (copied < ulen || UDP_SKB_CB(skb)->partial_cov) { 437 - if (udp_lib_checksum_complete(skb)) 436 + checksum_valid = !udp_lib_checksum_complete(skb); 437 + if (!checksum_valid) 438 438 goto csum_copy_err; 439 439 } 440 440 441 - if (skb_csum_unnecessary(skb)) 441 + if (checksum_valid || skb_csum_unnecessary(skb)) 442 442 err = skb_copy_datagram_msg(skb, sizeof(struct udphdr), 443 443 msg, copied); 444 444 else {