[PATCH] 2.4: PPC64: 32 bit sys_recvmsg corruption

Stephen Rothwell sfr at canb.auug.org.au
Wed Feb 16 17:22:59 EST 2005


[Take 2]

Hi Marcello,

In the presence of threads, there is a possibility of the kernel being
fooled by the 32 bit sys_recvmsg control data into copying more than it
should into the kernel and corrupting kernel data structures.

We call the 64 bit version of sys_recvmsg which writes control messages
directly to user memory which we then read back and "fix up" for the
differences between 32 and 64 bit structures.  If two threads share the
buffer that we are writing into (and then reading from) it is possible for
the control message headers to be changed from what we expect.  One of the
header fields is the length we need to copy back into the kernel ...

This patch just does some more length checking.

This bug was actually being hit by BIND running at a customer site.  It is
very hard to hit, but (obviously) possible.

Signed-off-by: Stephen Rothwell <sfr at canb.auug.org.au>

Please consider for inclusion into 2.4.30.

Only the ppc64 part of this patch has been compiled and tested.  I have
applied the same fix to all the 46 bit archs with 32 bit compatibility.

-- 
Cheers,
Stephen Rothwell                    sfr at canb.auug.org.au
http://www.canb.auug.org.au/~sfr/

diff -ruN 2.4.30-pre1/arch/ia64/ia32/sys_ia32.c 2.4.30-pre1-sfr.2/arch/ia64/ia32/sys_ia32.c
--- 2.4.30-pre1/arch/ia64/ia32/sys_ia32.c	2005-02-16 10:57:03.000000000 +1100
+++ 2.4.30-pre1-sfr.2/arch/ia64/ia32/sys_ia32.c	2005-02-16 16:49:51.000000000 +1100
@@ -1649,7 +1649,8 @@
  *		IPV6_AUTHHDR	ipv6 auth exthdr	32-bit clean
  */
 static void
-cmsg32_recvmsg_fixup (struct msghdr *kmsg, unsigned long orig_cmsg_uptr)
+cmsg32_recvmsg_fixup (struct msghdr *kmsg, unsigned long orig_cmsg_uptr,
+		__kernel_size_t orig_cmsg_len)
 {
 	unsigned char *workbuf, *wp;
 	unsigned long bufsz, space_avail;
@@ -1683,6 +1684,19 @@
 			goto fail2;
 
 		clen64 = kcmsg32->cmsg_len;
+		if ((clen64 < CMSG_ALIGN(sizeof(*ucmsg)))
+				(clen64 > (orig_cmsg_len + wp - workbuf))) {
+			static int count;
+
+			if (count++ < 20)
+				printk(KERN_WARNING "recvmsg_fixup: "
+					"bad data length %d, level %d, "
+					"type %d, process %d (%s)\n",
+					clen64, kcmsg32->cmsg_level,
+					kcmsg32->cmsg_type,
+					current->pid, current->comm);
+			break;
+		}
 		copy_from_user(CMSG32_DATA(kcmsg32), CMSG_DATA(ucmsg),
 			       clen64 - CMSG_ALIGN(sizeof(*ucmsg)));
 		clen32 = ((clen64 - CMSG_ALIGN(sizeof(*ucmsg))) +
@@ -1812,6 +1826,7 @@
 	struct iovec *iov=iovstack;
 	struct msghdr msg_sys;
 	unsigned long cmsg_ptr;
+	__kernel_size_t cmsg_len;
 	int err, iov_size, total_len, len;
 	struct scm_cookie scm;
 
@@ -1856,6 +1871,7 @@
 	total_len=err;
 
 	cmsg_ptr = (unsigned long)msg_sys.msg_control;
+	cmsg_len = msg_sys.msg_controllen;
 	msg_sys.msg_flags = 0;
 
 	if (sock->file->f_flags & O_NONBLOCK)
@@ -1882,7 +1898,8 @@
 			 * fix it up before we tack on more stuff.
 			 */
 			if ((unsigned long) msg_sys.msg_control != cmsg_ptr)
-				cmsg32_recvmsg_fixup(&msg_sys, cmsg_ptr);
+				cmsg32_recvmsg_fixup(&msg_sys, cmsg_ptr,
+						cmsg_len);
 
 			/* Wheee... */
 			if (sock->passcred)
diff -ruN 2.4.30-pre1/arch/mips64/kernel/linux32.c 2.4.30-pre1-sfr.2/arch/mips64/kernel/linux32.c
--- 2.4.30-pre1/arch/mips64/kernel/linux32.c	2005-02-16 10:57:39.000000000 +1100
+++ 2.4.30-pre1-sfr.2/arch/mips64/kernel/linux32.c	2005-02-16 16:56:24.000000000 +1100
@@ -2790,7 +2790,8 @@
  *		IPV6_RTHDR	ipv6 routing exthdr	32-bit clean
  *		IPV6_AUTHHDR	ipv6 auth exthdr	32-bit clean
  */
-static void cmsg32_recvmsg_fixup(struct msghdr *kmsg, unsigned long orig_cmsg_uptr)
+static void cmsg32_recvmsg_fixup(struct msghdr *kmsg,
+		unsigned long orig_cmsg_uptr, __kernel_size_t orig_cmsg_len)
 {
 	unsigned char *workbuf, *wp;
 	unsigned long bufsz, space_avail;
@@ -2821,6 +2822,19 @@
 		__get_user(kcmsg32->cmsg_type, &ucmsg->cmsg_type);
 
 		clen64 = kcmsg32->cmsg_len;
+		if ((clen64 < CMSG_ALIGN(sizeof(*ucmsg)))
+				(clen64 > (orig_cmsg_len + wp - workbuf))) {
+			static int count;
+
+			if (count++ < 20)
+				printk(KERN_WARNING "recvmsg_fixup: "
+					"bad data length %d, level %d, "
+					"type %d, process %d (%s)\n",
+					clen64, kcmsg32->cmsg_level,
+					kcmsg32->cmsg_type,
+					current->pid, current->comm);
+			break;
+		}
 		copy_from_user(CMSG32_DATA(kcmsg32), CMSG_DATA(ucmsg),
 			       clen64 - CMSG_ALIGN(sizeof(*ucmsg)));
 		clen32 = ((clen64 - CMSG_ALIGN(sizeof(*ucmsg))) +
@@ -2906,6 +2920,7 @@
 	struct sockaddr *uaddr;
 	int *uaddr_len;
 	unsigned long cmsg_ptr;
+	__kernel_size_t cmsg_len;
 	int err, total_len, len = 0;
 
 	if(msghdr_from_user32_to_kern(&kern_msg, user_msg))
@@ -2921,6 +2936,7 @@
 	total_len = err;
 
 	cmsg_ptr = (unsigned long) kern_msg.msg_control;
+	cmsg_len = kern_msg.msg_controllen;
 	kern_msg.msg_flags = 0;
 
 	sock = sockfd_lookup(fd, &err);
@@ -2946,7 +2962,8 @@
 				 * to fix it up before we tack on more stuff.
 				 */
 				if((unsigned long) kern_msg.msg_control != cmsg_ptr)
-					cmsg32_recvmsg_fixup(&kern_msg, cmsg_ptr);
+					cmsg32_recvmsg_fixup(&kern_msg,
+							cmsg_ptr, cmsg_len);
 
 				/* Wheee... */
 				if(sock->passcred)
diff -ruN 2.4.30-pre1/arch/parisc/kernel/sys_parisc32.c 2.4.30-pre1-sfr.2/arch/parisc/kernel/sys_parisc32.c
--- 2.4.30-pre1/arch/parisc/kernel/sys_parisc32.c	2005-02-16 10:57:39.000000000 +1100
+++ 2.4.30-pre1-sfr.2/arch/parisc/kernel/sys_parisc32.c	2005-02-16 16:58:48.000000000 +1100
@@ -2106,7 +2106,8 @@
  *		IPV6_RTHDR	ipv6 routing exthdr	32-bit clean
  *		IPV6_AUTHHDR	ipv6 auth exthdr	32-bit clean
  */
-static void cmsg32_recvmsg_fixup(struct msghdr *kmsg, unsigned long orig_cmsg_uptr)
+static void cmsg32_recvmsg_fixup(struct msghdr *kmsg,
+		unsigned long orig_cmsg_uptr, __kernel_size_t orig_cmsg_len)
 {
 	unsigned char *workbuf, *wp;
 	unsigned long bufsz, space_avail;
@@ -2137,6 +2138,19 @@
 		__get_user(kcmsg32->cmsg_type, &ucmsg->cmsg_type);
 
 		clen64 = kcmsg32->cmsg_len;
+		if ((clen64 < CMSG_ALIGN(sizeof(*ucmsg)))
+				(clen64 > (orig_cmsg_len + wp - workbuf))) {
+			static int count;
+
+			if (count++ < 20)
+				printk(KERN_WARNING "recvmsg_fixup: "
+					"bad data length %d, level %d, "
+					"type %d, process %d (%s)\n",
+					clen64, kcmsg32->cmsg_level,
+					kcmsg32->cmsg_type,
+					current->pid, current->comm);
+			break;
+		}
 		copy_from_user(CMSG32_DATA(kcmsg32), CMSG_DATA(ucmsg),
 			       clen64 - CMSG_ALIGN(sizeof(*ucmsg)));
 		clen32 = ((clen64 - CMSG_ALIGN(sizeof(*ucmsg))) +
@@ -2222,6 +2236,7 @@
 	struct sockaddr *uaddr;
 	int *uaddr_len;
 	unsigned long cmsg_ptr;
+	__kernel_size_t cmsg_len;
 	int err, total_len, len = 0;
 
 	if(msghdr_from_user32_to_kern(&kern_msg, user_msg))
@@ -2237,6 +2252,7 @@
 	total_len = err;
 
 	cmsg_ptr = (unsigned long) kern_msg.msg_control;
+	cmsg_len = kern_msg.msg_controllen;
 	kern_msg.msg_flags = 0;
 
 	sock = sockfd_lookup(fd, &err);
@@ -2262,7 +2278,8 @@
 				 * to fix it up before we tack on more stuff.
 				 */
 				if((unsigned long) kern_msg.msg_control != cmsg_ptr)
-					cmsg32_recvmsg_fixup(&kern_msg, cmsg_ptr);
+					cmsg32_recvmsg_fixup(&kern_msg,
+							cmsg_ptr, cmsg_len);
 
 				/* Wheee... */
 				if(sock->passcred)
diff -ruN 2.4.30-pre1/arch/ppc64/kernel/sys_ppc32.c 2.4.30-pre1-sfr.2/arch/ppc64/kernel/sys_ppc32.c
--- 2.4.30-pre1/arch/ppc64/kernel/sys_ppc32.c	2005-02-16 10:57:39.000000000 +1100
+++ 2.4.30-pre1-sfr.2/arch/ppc64/kernel/sys_ppc32.c	2005-02-16 11:01:05.000000000 +1100
@@ -3664,7 +3664,8 @@
  *		IPV6_RTHDR	ipv6 routing exthdr	32-bit clean
  *		IPV6_AUTHHDR	ipv6 auth exthdr	32-bit clean
  */
-static void cmsg32_recvmsg_fixup(struct msghdr *kmsg, unsigned long orig_cmsg_uptr)
+static void cmsg32_recvmsg_fixup(struct msghdr *kmsg,
+		unsigned long orig_cmsg_uptr, __kernel_size_t orig_cmsg_len)
 {
 	unsigned char *workbuf, *wp;
 	unsigned long bufsz, space_avail;
@@ -3695,6 +3696,19 @@
 		__get_user(kcmsg32->cmsg_type, &ucmsg->cmsg_type);
 
 		clen64 = kcmsg32->cmsg_len;
+		if ((clen64 < CMSG_ALIGN(sizeof(*ucmsg)))
+				(clen64 > (orig_cmsg_len + wp - workbuf))) {
+			static int count;
+
+			if (count++ < 20)
+				printk(KERN_WARNING "recvmsg_fixup: "
+					"bad data length %d, level %d, "
+					"type %d, process %d (%s)\n",
+					clen64, kcmsg32->cmsg_level,
+					kcmsg32->cmsg_type,
+					current->pid, current->comm);
+			break;
+		}
 		copy_from_user(CMSG32_DATA(kcmsg32), CMSG_DATA(ucmsg),
 			       clen64 - CMSG_ALIGN(sizeof(*ucmsg)));
 		clen32 = ((clen64 - CMSG_ALIGN(sizeof(*ucmsg))) +
@@ -3751,6 +3765,7 @@
 	struct sockaddr *uaddr;
 	int *uaddr_len;
 	unsigned long cmsg_ptr;
+	__kernel_size_t cmsg_len;
 	int err, total_len, len = 0;
 	
 	PPCDBG(PPCDBG_SYS32, "sys32_recvmsg - entered - fd=%x, user_msg@=%p, user_flags=%x \n", fd, user_msg, user_flags);
@@ -3768,6 +3783,7 @@
 	total_len = err;
 
 	cmsg_ptr = (unsigned long) kern_msg.msg_control;
+	cmsg_len = kern_msg.msg_controllen;
 	kern_msg.msg_flags = 0;
 
 	sock = sockfd_lookup(fd, &err);
@@ -3793,7 +3809,8 @@
 				 * to fix it up before we tack on more stuff.
 				 */
 				if((unsigned long) kern_msg.msg_control != cmsg_ptr)
-					cmsg32_recvmsg_fixup(&kern_msg, cmsg_ptr);
+					cmsg32_recvmsg_fixup(&kern_msg,
+							cmsg_ptr, cmsg_len);
 
 				/* Wheee... */
 				if(sock->passcred)
diff -ruN 2.4.30-pre1/arch/s390x/kernel/linux32.c 2.4.30-pre1-sfr.2/arch/s390x/kernel/linux32.c
--- 2.4.30-pre1/arch/s390x/kernel/linux32.c	2005-02-16 10:57:39.000000000 +1100
+++ 2.4.30-pre1-sfr.2/arch/s390x/kernel/linux32.c	2005-02-16 17:18:07.000000000 +1100
@@ -2597,7 +2597,8 @@
  *		IPV6_RTHDR	ipv6 routing exthdr	32-bit clean
  *		IPV6_AUTHHDR	ipv6 auth exthdr	32-bit clean
  */
-static void cmsg32_recvmsg_fixup(struct msghdr *kmsg, unsigned long orig_cmsg_uptr)
+static void cmsg32_recvmsg_fixup(struct msghdr *kmsg,
+		unsigned long orig_cmsg_uptr, __kernel_size_t orig_cmsg_len)
 {
 	unsigned char *workbuf, *wp;
 	unsigned long bufsz, space_avail;
@@ -2628,6 +2629,19 @@
 		__get_user(kcmsg32->cmsg_type, &ucmsg->cmsg_type);
 
 		clen64 = kcmsg32->cmsg_len;
+		if ((clen64 < CMSG_ALIGN(sizeof(*ucmsg)))
+				(clen64 > (orig_cmsg_len + wp - workbuf))) {
+			static int count;
+
+			if (count++ < 20)
+				printk(KERN_WARNING "recvmsg_fixup: "
+					"bad data length %d, level %d, "
+					"type %d, process %d (%s)\n",
+					clen64, kcmsg32->cmsg_level,
+					kcmsg32->cmsg_type,
+					current->pid, current->comm);
+			break;
+		}
 		copy_from_user(CMSG32_DATA(kcmsg32), CMSG_DATA(ucmsg),
 			       clen64 - CMSG_ALIGN(sizeof(*ucmsg)));
 		clen32 = ((clen64 - CMSG_ALIGN(sizeof(*ucmsg))) +
@@ -2887,7 +2901,8 @@
 
 static __inline__ void
 scm_recv32(struct socket *sock, struct msghdr *msg,
-		struct scm_cookie *scm, int flags, unsigned long cmsg_ptr)
+		struct scm_cookie *scm, int flags, unsigned long cmsg_ptr,
+		__kernel_size_t cmsg_len)
 {
 	if(!msg->msg_control)
 	{
@@ -2902,7 +2917,7 @@
 	 * to fix it up before we tack on more stuff.
 	 */
 	if((unsigned long) msg->msg_control != cmsg_ptr)
-		cmsg32_recvmsg_fixup(msg, cmsg_ptr);
+		cmsg32_recvmsg_fixup(msg, cmsg_ptr, cmsg_len);
 	/* Wheee... */
 	if(sock->passcred)
 		put_cmsg32(msg,
@@ -2916,14 +2931,14 @@
 
 static int  
 sock_recvmsg32(struct socket *sock, struct msghdr *msg, int size, int flags,
-               unsigned long cmsg_ptr)
+               unsigned long cmsg_ptr, __kernel_size_t cmsg_len)
 {
 	struct scm_cookie scm;
 
 	memset(&scm, 0, sizeof(scm));
 	size = sock->ops->recvmsg(sock, msg, size, flags, &scm);
 	if (size >= 0)
-		scm_recv32(sock, msg, &scm, flags, cmsg_ptr);
+		scm_recv32(sock, msg, &scm, flags, cmsg_ptr, cmsg_len);
 
 	return size;
 }
@@ -2940,6 +2955,7 @@
 	struct iovec *iov=iovstack;
 	struct msghdr msg_sys;
 	unsigned long cmsg_ptr;
+	__kernel_size_t cmsg_len;
 	int err, iov_size, total_len, len;
 
 	/* kernel mode address */
@@ -2983,11 +2999,12 @@
 	total_len=err;
 
 	cmsg_ptr = (unsigned long)msg_sys.msg_control;
+	cmsg_len = msg_sys.msg_controllen;
 	msg_sys.msg_flags = 0;
 	
 	if (sock->file->f_flags & O_NONBLOCK)
 		flags |= MSG_DONTWAIT;
-	err = sock_recvmsg32(sock, &msg_sys, total_len, flags, cmsg_ptr);
+	err = sock_recvmsg32(sock, &msg_sys, total_len, flags, cmsg_ptr, cmsg_len);
 	if (err < 0)
 		goto out_freeiov;
 	len = err;
diff -ruN 2.4.30-pre1/arch/sparc64/kernel/sys_sparc32.c 2.4.30-pre1-sfr.2/arch/sparc64/kernel/sys_sparc32.c
--- 2.4.30-pre1/arch/sparc64/kernel/sys_sparc32.c	2005-02-16 10:57:39.000000000 +1100
+++ 2.4.30-pre1-sfr.2/arch/sparc64/kernel/sys_sparc32.c	2005-02-16 17:11:18.000000000 +1100
@@ -2647,7 +2647,8 @@
  *		IPV6_RTHDR	ipv6 routing exthdr	32-bit clean
  *		IPV6_AUTHHDR	ipv6 auth exthdr	32-bit clean
  */
-static void cmsg32_recvmsg_fixup(struct msghdr *kmsg, unsigned long orig_cmsg_uptr)
+static void cmsg32_recvmsg_fixup(struct msghdr *kmsg,
+		unsigned long orig_cmsg_uptr, __kernel_size_t orig_cmsg_len)
 {
 	unsigned char *workbuf, *wp;
 	unsigned long bufsz, space_avail;
@@ -2678,6 +2679,19 @@
 		__get_user(kcmsg32->cmsg_type, &ucmsg->cmsg_type);
 
 		clen64 = kcmsg32->cmsg_len;
+		if ((clen64 < CMSG_ALIGN(sizeof(*ucmsg)))
+				(clen64 > (orig_cmsg_len + wp - workbuf))) {
+			static int count;
+
+			if (count++ < 20)
+				printk(KERN_WARNING "recvmsg_fixup: "
+					"bad data length %d, level %d, "
+					"type %d, process %d (%s)\n",
+					clen64, kcmsg32->cmsg_level,
+					kcmsg32->cmsg_type,
+					current->pid, current->comm);
+			break;
+		}
 		if (kcmsg32->cmsg_level == SOL_SOCKET &&
 			kcmsg32->cmsg_type == SO_TIMESTAMP) {
 			struct timeval tv;
@@ -2781,6 +2795,7 @@
 	struct sockaddr *uaddr;
 	int *uaddr_len;
 	unsigned long cmsg_ptr;
+	__kernel_size_t cmsg_len;
 	int err, total_len, len = 0;
 
 	if(msghdr_from_user32_to_kern(&kern_msg, user_msg))
@@ -2796,6 +2811,7 @@
 	total_len = err;
 
 	cmsg_ptr = (unsigned long) kern_msg.msg_control;
+	cmsg_len = kern_msg.msg_controllen;
 	kern_msg.msg_flags = 0;
 
 	sock = sockfd_lookup(fd, &err);
@@ -2821,7 +2837,8 @@
 				 * to fix it up before we tack on more stuff.
 				 */
 				if((unsigned long) kern_msg.msg_control != cmsg_ptr)
-					cmsg32_recvmsg_fixup(&kern_msg, cmsg_ptr);
+					cmsg32_recvmsg_fixup(&kern_msg,
+							cmsg_ptr, cmsg_len);
 
 				/* Wheee... */
 				if(sock->passcred)
diff -ruN 2.4.30-pre1/arch/x86_64/ia32/socket32.c 2.4.30-pre1-sfr.2/arch/x86_64/ia32/socket32.c
--- 2.4.30-pre1/arch/x86_64/ia32/socket32.c	2005-02-16 10:57:04.000000000 +1100
+++ 2.4.30-pre1-sfr.2/arch/x86_64/ia32/socket32.c	2005-02-16 17:13:31.000000000 +1100
@@ -302,7 +302,8 @@
  *		IPV6_RTHDR	ipv6 routing exthdr	32-bit clean
  *		IPV6_AUTHHDR	ipv6 auth exthdr	32-bit clean
  */
-static void cmsg32_recvmsg_fixup(struct msghdr *kmsg, unsigned long orig_cmsg_uptr)
+static void cmsg32_recvmsg_fixup(struct msghdr *kmsg,
+		unsigned long orig_cmsg_uptr, __kernel_size_t orig_cmsg_len)
 {
 	unsigned char *workbuf, *wp;
 	unsigned long bufsz, space_avail;
@@ -333,6 +334,19 @@
 		__get_user(kcmsg32->cmsg_type, &ucmsg->cmsg_type);
 
 		clen64 = kcmsg32->cmsg_len;
+		if ((clen64 < CMSG_ALIGN(sizeof(*ucmsg)))
+				(clen64 > (orig_cmsg_len + wp - workbuf))) {
+			static int count;
+
+			if (count++ < 20)
+				printk(KERN_WARNING "recvmsg_fixup: "
+					"bad data length %d, level %d, "
+					"type %d, process %d (%s)\n",
+					clen64, kcmsg32->cmsg_level,
+					kcmsg32->cmsg_type,
+					current->pid, current->comm);
+			break;
+		}
 		copy_from_user(CMSG32_DATA(kcmsg32), CMSG_DATA(ucmsg),
 			       clen64 - CMSG_ALIGN(sizeof(*ucmsg)));
 		clen32 = ((clen64 - CMSG_ALIGN(sizeof(*ucmsg))) +
@@ -418,6 +432,7 @@
 	struct sockaddr *uaddr;
 	int *uaddr_len;
 	unsigned long cmsg_ptr;
+	__kernel_size_t cmsg_len;
 	int err, total_len, len = 0;
 
 	if(msghdr_from_user32_to_kern(&kern_msg, user_msg))
@@ -433,6 +448,7 @@
 	total_len = err;
 
 	cmsg_ptr = (unsigned long) kern_msg.msg_control;
+	cmsg_len = kern_msg.msg_controllen;
 	kern_msg.msg_flags = 0;
 
 	sock = sockfd_lookup(fd, &err);
@@ -458,7 +474,8 @@
 				 * to fix it up before we tack on more stuff.
 				 */
 				if((unsigned long) kern_msg.msg_control != cmsg_ptr)
-					cmsg32_recvmsg_fixup(&kern_msg, cmsg_ptr);
+					cmsg32_recvmsg_fixup(&kern_msg,
+							cmsg_ptr, cmsg_len);
 
 				/* Wheee... */
 				if(sock->passcred)



More information about the Linuxppc64-dev mailing list