Vulnerability: The sock_setsockopt function in net/core/sock.c in the Linux kernel before 3.5 mishandles negative values of sk_sndbuf and sk_rcvbuf, which allows local users to cause a denial of service (memory corruption and system crash) or possibly have unspecified other impact by leveraging the CAP_NET_ADMIN capability for a crafted setsockopt system call with the (1) SO_SNDBUF or (2) SO_RCVBUF option.
Vulnerability ID: CVE-2012-6704
CWE: CWE-119
Severity score: 10
Project: https://github.com/torvalds/linux
Published: 2016-12-28
File: net/core/sock.c
Commit message: net: cleanups in sock_setsockopt()

Use min_t()/max_t() macros, reformat two comments, use !!test_bit() to
match !!sock_flag()

Signed-off-by: Eric Dumazet <edumazet@google.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
Commit author: Eric Dumazet

Vulnerable version (before fix):
 int sock_setsockopt(struct socket *sock, int level, int optname, 		break; 	case SO_SNDBUF: 		/* Don't error on this BSD doesn't and if you think 		   about it this is right. Otherwise apps have to 		   play 'guess the biggest size' games. RCVBUF/SNDBUF 		   are treated in BSD as hints */  		if (val > sysctl_wmem_max) 			val = sysctl_wmem_max; set_sndbuf: 		sk->sk_userlocks |= SOCK_SNDBUF_LOCK; 		if ((val * 2) < SOCK_MIN_SNDBUF) 			sk->sk_sndbuf = SOCK_MIN_SNDBUF; 		else 			sk->sk_sndbuf = val * 2;  		/* 		 *	Wake up sending tasks if we 		 *	upped the value. 		 */ 		sk->sk_write_space(sk); 		break;  int sock_setsockopt(struct socket *sock, int level, int optname,  	case SO_RCVBUF: 		/* Don't error on this BSD doesn't and if you think 		   about it this is right. Otherwise apps have to 		   play 'guess the biggest size' games. RCVBUF/SNDBUF 		   are treated in BSD as hints */  		if (val > sysctl_rmem_max) 			val = sysctl_rmem_max; set_rcvbuf: 		sk->sk_userlocks |= SOCK_RCVBUF_LOCK; 		/* int sock_setsockopt(struct socket *sock, int level, int optname, 		 * returning the value we actually used in getsockopt 		 * is the most desirable behavior. 		 */ 		if ((val * 2) < SOCK_MIN_RCVBUF) 			sk->sk_rcvbuf = SOCK_MIN_RCVBUF; 		else 			sk->sk_rcvbuf = val * 2; 		break;  	case SO_RCVBUFFORCE: int sock_getsockopt(struct socket *sock, int level, int optname, 		break;  	case SO_PASSCRED: 		v.val = test_bit(SOCK_PASSCRED, &sock->flags) ? 1 : 0; 		break;  	case SO_PEERCRED: int sock_getsockopt(struct socket *sock, int level, int optname, 		break;  	case SO_PASSSEC: 		v.val = test_bit(SOCK_PASSSEC, &sock->flags) ? 1 : 0; 		break;  	case SO_PEERSEC:

Fixed version (after fix):
 int sock_setsockopt(struct socket *sock, int level, int optname, 		break; 	case SO_SNDBUF: 		/* Don't error on this BSD doesn't and if you think 		 * about it this is right. Otherwise apps have to 		 * play 'guess the biggest size' games. RCVBUF/SNDBUF 		 * are treated in BSD as hints 		 */ 		val = min_t(u32, val, sysctl_wmem_max); set_sndbuf: 		sk->sk_userlocks |= SOCK_SNDBUF_LOCK; 		sk->sk_sndbuf = max_t(u32, val * 2, SOCK_MIN_SNDBUF); 		/* Wake up sending tasks if we upped the value. */ 		sk->sk_write_space(sk); 		break;  int sock_setsockopt(struct socket *sock, int level, int optname,  	case SO_RCVBUF: 		/* Don't error on this BSD doesn't and if you think 		 * about it this is right. Otherwise apps have to 		 * play 'guess the biggest size' games. RCVBUF/SNDBUF 		 * are treated in BSD as hints 		 */ 		val = min_t(u32, val, sysctl_rmem_max); set_rcvbuf: 		sk->sk_userlocks |= SOCK_RCVBUF_LOCK; 		/* int sock_setsockopt(struct socket *sock, int level, int optname, 		 * returning the value we actually used in getsockopt 		 * is the most desirable behavior. 		 */ 		sk->sk_rcvbuf = max_t(u32, val * 2, SOCK_MIN_RCVBUF); 		break;  	case SO_RCVBUFFORCE: int sock_getsockopt(struct socket *sock, int level, int optname, 		break;  	case SO_PASSCRED: 		v.val = !!test_bit(SOCK_PASSCRED, &sock->flags); 		break;  	case SO_PEERCRED: int sock_getsockopt(struct socket *sock, int level, int optname, 		break;  	case SO_PASSSEC: 		v.val = !!test_bit(SOCK_PASSSEC, &sock->flags); 		break;  	case SO_PEERSEC:

Patch:
patch: @@ -577,23 +577,15 @@ int sock_setsockopt(struct socket *sock, int level, int optname,
 		break;
 	case SO_SNDBUF:
 		/* Don't error on this BSD doesn't and if you think
-		   about it this is right. Otherwise apps have to
-		   play 'guess the biggest size' games. RCVBUF/SNDBUF
-		   are treated in BSD as hints */
-
-		if (val > sysctl_wmem_max)
-			val = sysctl_wmem_max;
+		 * about it this is right. Otherwise apps have to
+		 * play 'guess the biggest size' games. RCVBUF/SNDBUF
+		 * are treated in BSD as hints
+		 */
+		val = min_t(u32, val, sysctl_wmem_max);
 set_sndbuf:
 		sk->sk_userlocks |= SOCK_SNDBUF_LOCK;
-		if ((val * 2) < SOCK_MIN_SNDBUF)
-			sk->sk_sndbuf = SOCK_MIN_SNDBUF;
-		else
-			sk->sk_sndbuf = val * 2;
-
-		/*
-		 *	Wake up sending tasks if we
-		 *	upped the value.
-		 */
+		sk->sk_sndbuf = max_t(u32, val * 2, SOCK_MIN_SNDBUF);
+		/* Wake up sending tasks if we upped the value. */
 		sk->sk_write_space(sk);
 		break;
 
@@ -606,12 +598,11 @@ int sock_setsockopt(struct socket *sock, int level, int optname,
 
 	case SO_RCVBUF:
 		/* Don't error on this BSD doesn't and if you think
-		   about it this is right. Otherwise apps have to
-		   play 'guess the biggest size' games. RCVBUF/SNDBUF
-		   are treated in BSD as hints */
-
-		if (val > sysctl_rmem_max)
-			val = sysctl_rmem_max;
+		 * about it this is right. Otherwise apps have to
+		 * play 'guess the biggest size' games. RCVBUF/SNDBUF
+		 * are treated in BSD as hints
+		 */
+		val = min_t(u32, val, sysctl_rmem_max);
 set_rcvbuf:
 		sk->sk_userlocks |= SOCK_RCVBUF_LOCK;
 		/*
@@ -629,10 +620,7 @@ int sock_setsockopt(struct socket *sock, int level, int optname,
 		 * returning the value we actually used in getsockopt
 		 * is the most desirable behavior.
 		 */
-		if ((val * 2) < SOCK_MIN_RCVBUF)
-			sk->sk_rcvbuf = SOCK_MIN_RCVBUF;
-		else
-			sk->sk_rcvbuf = val * 2;
+		sk->sk_rcvbuf = max_t(u32, val * 2, SOCK_MIN_RCVBUF);
 		break;
 
 	case SO_RCVBUFFORCE:
@@ -975,7 +963,7 @@ int sock_getsockopt(struct socket *sock, int level, int optname,
 		break;
 
 	case SO_PASSCRED:
-		v.val = test_bit(SOCK_PASSCRED, &sock->flags) ? 1 : 0;
+		v.val = !!test_bit(SOCK_PASSCRED, &sock->flags);
 		break;
 
 	case SO_PEERCRED:
@@ -1010,7 +998,7 @@ int sock_getsockopt(struct socket *sock, int level, int optname,
 		break;
 
 	case SO_PASSSEC:
-		v.val = test_bit(SOCK_PASSSEC, &sock->flags) ? 1 : 0;
+		v.val = !!test_bit(SOCK_PASSSEC, &sock->flags);
 		break;
 
 	case SO_PEERSEC:
path: net/core/sock.c