CVE-2016-10229: Improperly Implemented Security Check for Standard (CWE-358)
value: udp.c in the Linux kernel before 4.5 allows remote attackers to execute arbitrary code via UDP traffic that triggers an unsafe second checksum calculation during execution of a recv system call with the MSG_PEEK flag.
CWE description: The product does not implement or incorrectly implements one or more security-relevant checks as specified by the design of a standardized algorithm, protocol, or technique.
CVSS v3 base score: 9.8
Published: 2017-04-04T05:59Z
Repository: https://github.com/torvalds/linux
Language: C
Files changed: net/ipv4/udp.c, net/ipv6/udp.c
Commit date: 2016-01-04 17:23:36 -0500
Commit message: 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>

Diff: diff --git a/net/ipv4/udp.c b/net/ipv4/udp.c
index 8841e984f8bff6..ac14ae44390da6 100644
--- a/net/ipv4/udp.c
+++ b/net/ipv4/udp.c
@@ -1269,10 +1269,11 @@ int udp_recvmsg(struct sock *sk, struct msghdr *msg, size_t len, int noblock,
 	struct sk_buff *skb;
 	unsigned int ulen, copied;
 	int peeked, off = 0;
 	int err;
 	int is_udplite = IS_UDPLITE(sk);
+	bool checksum_valid = false;
 	bool slow;
 
 	if (flags & MSG_ERRQUEUE)
 		return ip_recv_error(sk, msg, len, addr_len);
 
@@ -1294,15 +1295,16 @@ int udp_recvmsg(struct sock *sk, struct msghdr *msg, size_t len, int noblock,
 	 * data.  If the data is truncated, or if we only want a partial
 	 * coverage checksum (UDP-Lite), do it before the copy.
 	 */
 
 	if (copied < ulen || UDP_SKB_CB(skb)->partial_cov) {
-		if (udp_lib_checksum_complete(skb))
+		checksum_valid = !udp_lib_checksum_complete(skb);
+		if (!checksum_valid)
 			goto csum_copy_err;
 	}
 
-	if (skb_csum_unnecessary(skb))
+	if (checksum_valid || skb_csum_unnecessary(skb))
 		err = skb_copy_datagram_msg(skb, sizeof(struct udphdr),
 					    msg, copied);
 	else {
 		err = skb_copy_and_csum_datagram_msg(skb, sizeof(struct udphdr),
 						     msg);
diff --git a/net/ipv6/udp.c b/net/ipv6/udp.c
index 9da3287a392370..00775ee27d86da 100644
--- a/net/ipv6/udp.c
+++ b/net/ipv6/udp.c
@@ -400,10 +400,11 @@ int udpv6_recvmsg(struct sock *sk, struct msghdr *msg, size_t len,
 	struct sk_buff *skb;
 	unsigned int ulen, copied;
 	int peeked, off = 0;
 	int err;
 	int is_udplite = IS_UDPLITE(sk);
+	bool checksum_valid = false;
 	int is_udp4;
 	bool slow;
 
 	if (flags & MSG_ERRQUEUE)
 		return ipv6_recv_error(sk, msg, len, addr_len);
@@ -431,15 +432,16 @@ int udpv6_recvmsg(struct sock *sk, struct msghdr *msg, size_t len,
 	 * data.  If the data is truncated, or if we only want a partial
 	 * coverage checksum (UDP-Lite), do it before the copy.
 	 */
 
 	if (copied < ulen || UDP_SKB_CB(skb)->partial_cov) {
-		if (udp_lib_checksum_complete(skb))
+		checksum_valid = !udp_lib_checksum_complete(skb);
+		if (!checksum_valid)
 			goto csum_copy_err;
 	}
 
-	if (skb_csum_unnecessary(skb))
+	if (checksum_valid || skb_csum_unnecessary(skb))
 		err = skb_copy_datagram_msg(skb, sizeof(struct udphdr),
 					    msg, copied);
 	else {
 		err = skb_copy_and_csum_datagram_msg(skb, sizeof(struct udphdr), msg);
 		if (err == -EINVAL)

Vulnerable code: 		if (udp_lib_checksum_complete(skb))
	if (skb_csum_unnecessary(skb))
		if (udp_lib_checksum_complete(skb))
	if (skb_csum_unnecessary(skb))
Fixed code: 	bool checksum_valid = false;
		checksum_valid = !udp_lib_checksum_complete(skb);
		if (!checksum_valid)
	if (checksum_valid || skb_csum_unnecessary(skb))
	bool checksum_valid = false;
		checksum_valid = !udp_lib_checksum_complete(skb);
		if (!checksum_valid)
	if (checksum_valid || skb_csum_unnecessary(skb))