[PATCH] PPC64: 32 bit sys_recvmsg corruption

Linux Kernel Mailing List <[email protected]> Mon, 21 Feb 2005 11:42:07 +0000
Newsgroups gmane.linux.kernel.commits.2-4
Message-ID <[email protected]>
ChangeSet 1.1579, 2005/02/21 08:42:07-03:00, [email protected]

	[PATCH] PPC64: 32 bit sys_recvmsg corruption
	
	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.
	
	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                    [email protected]
	http://www.canb.auug.org.au/~sfr/



 ia64/ia32/sys_ia32.c         |   21 +++++++++++++++++++--
 mips64/kernel/linux32.c      |   21 +++++++++++++++++++--
 parisc/kernel/sys_parisc32.c |   21 +++++++++++++++++++--
 ppc64/kernel/sys_ppc32.c     |   21 +++++++++++++++++++--
 s390x/kernel/linux32.c       |   29 +++++++++++++++++++++++------
 sparc64/kernel/sys_sparc32.c |   21 +++++++++++++++++++--
 x86_64/ia32/socket32.c       |   21 +++++++++++++++++++--
 7 files changed, 137 insertions(+), 18 deletions(-)


diff -Nru a/arch/ia64/ia32/sys_ia32.c b/arch/ia64/ia32/sys_ia32.c
--- a/arch/ia64/ia32/sys_ia32.c	2005-02-21 15:04:59 -08:00
+++ b/arch/ia64/ia32/sys_ia32.c	2005-02-21 15:04:59 -08:00
@@ -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 -Nru a/arch/mips64/kernel/linux32.c b/arch/mips64/kernel/linux32.c
--- a/arch/mips64/kernel/linux32.c	2005-02-21 15:04:59 -08:00
+++ b/arch/mips64/kernel/linux32.c	2005-02-21 15:04:59 -08:00
@@ -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 -Nru a/arch/parisc/kernel/sys_parisc32.c b/arch/parisc/kernel/sys_parisc32.c
--- a/arch/parisc/kernel/sys_parisc32.c	2005-02-21 15:04:59 -08:00
+++ b/arch/parisc/kernel/sys_parisc32.c	2005-02-21 15:04:59 -08:00
@@ -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 -Nru a/arch/ppc64/kernel/sys_ppc32.c b/arch/ppc64/kernel/sys_ppc32.c
--- a/arch/ppc64/kernel/sys_ppc32.c	2005-02-21 15:04:59 -08:00
+++ b/arch/ppc64/kernel/sys_ppc32.c	2005-02-21 15:04:59 -08:00
@@ -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 -Nru a/arch/s390x/kernel/linux32.c b/arch/s390x/kernel/linux32.c
--- a/arch/s390x/kernel/linux32.c	2005-02-21 15:04:59 -08:00
+++ b/arch/s390x/kernel/linux32.c	2005-02-21 15:04:59 -08:00
@@ -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 -Nru a/arch/sparc64/kernel/sys_sparc32.c b/arch/sparc64/kernel/sys_sparc32.c
--- a/arch/sparc64/kernel/sys_sparc32.c	2005-02-21 15:04:59 -08:00
+++ b/arch/sparc64/kernel/sys_sparc32.c	2005-02-21 15:04:59 -08:00
@@ -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 -Nru a/arch/x86_64/ia32/socket32.c b/arch/x86_64/ia32/socket32.c
--- a/arch/x86_64/ia32/socket32.c	2005-02-21 15:04:59 -08:00
+++ b/arch/x86_64/ia32/socket32.c	2005-02-21 15:04:59 -08:00
@@ -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)