[BUG][PATCH] 2.4: PPC64: 32 bit sys_recvmsg corruption

Stephen Rothwell sfr at canb.auug.org.au
Tue Feb 22 12:16:27 EST 2005


Hi Marcelo,

[New version with no printk and a bug fixed that noone noticed :-)]

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 -ruNp 2.4.30-pre1/arch/ia64/ia32/sys_ia32.c 2.4.30-pre1-sfr.3/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.3/arch/ia64/ia32/sys_ia32.c	2005-02-22 11:58:42.000000000 +1100
@@ -1649,7 +1649,8 @@ scm_detach_fds32 (struct msghdr *kmsg, s
  *		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,9 @@ cmsg32_recvmsg_fixup (struct msghdr *kms
 			goto fail2;
 
 		clen64 = kcmsg32->cmsg_len;
+		if ((clen64 < CMSG_ALIGN(sizeof(*ucmsg))) ||
+				(clen64 > (orig_cmsg_len + wp - workbuf)))
+			break;
 		copy_from_user(CMSG32_DATA(kcmsg32), CMSG_DATA(ucmsg),
 			       clen64 - CMSG_ALIGN(sizeof(*ucmsg)));
 		clen32 = ((clen64 - CMSG_ALIGN(sizeof(*ucmsg))) +
@@ -1812,6 +1816,7 @@ sys32_recvmsg (int fd, struct msghdr32 *
 	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 +1861,7 @@ sys32_recvmsg (int fd, struct msghdr32 *
 	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 +1888,8 @@ sys32_recvmsg (int fd, struct msghdr32 *
 			 * 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 -ruNp 2.4.30-pre1/arch/mips64/kernel/linux32.c 2.4.30-pre1-sfr.3/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.3/arch/mips64/kernel/linux32.c	2005-02-22 11:58:58.000000000 +1100
@@ -2790,7 +2790,8 @@ static void scm_detach_fds32(struct msgh
  *		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,9 @@ static void cmsg32_recvmsg_fixup(struct 
 		__get_user(kcmsg32->cmsg_type, &ucmsg->cmsg_type);
 
 		clen64 = kcmsg32->cmsg_len;
+		if ((clen64 < CMSG_ALIGN(sizeof(*ucmsg))) ||
+				(clen64 > (orig_cmsg_len + wp - workbuf)))
+			break;
 		copy_from_user(CMSG32_DATA(kcmsg32), CMSG_DATA(ucmsg),
 			       clen64 - CMSG_ALIGN(sizeof(*ucmsg)));
 		clen32 = ((clen64 - CMSG_ALIGN(sizeof(*ucmsg))) +
@@ -2906,6 +2910,7 @@ asmlinkage int sys32_recvmsg(int fd, str
 	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 +2926,7 @@ asmlinkage int sys32_recvmsg(int fd, str
 	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 +2952,8 @@ asmlinkage int sys32_recvmsg(int fd, str
 				 * 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 -ruNp 2.4.30-pre1/arch/parisc/kernel/sys_parisc32.c 2.4.30-pre1-sfr.3/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.3/arch/parisc/kernel/sys_parisc32.c	2005-02-22 11:59:05.000000000 +1100
@@ -2106,7 +2106,8 @@ static void scm_detach_fds32(struct msgh
  *		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,9 @@ static void cmsg32_recvmsg_fixup(struct 
 		__get_user(kcmsg32->cmsg_type, &ucmsg->cmsg_type);
 
 		clen64 = kcmsg32->cmsg_len;
+		if ((clen64 < CMSG_ALIGN(sizeof(*ucmsg))) ||
+				(clen64 > (orig_cmsg_len + wp - workbuf)))
+			break;
 		copy_from_user(CMSG32_DATA(kcmsg32), CMSG_DATA(ucmsg),
 			       clen64 - CMSG_ALIGN(sizeof(*ucmsg)));
 		clen32 = ((clen64 - CMSG_ALIGN(sizeof(*ucmsg))) +
@@ -2222,6 +2226,7 @@ asmlinkage int sys32_recvmsg(int fd, str
 	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 +2242,7 @@ asmlinkage int sys32_recvmsg(int fd, str
 	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 +2268,8 @@ asmlinkage int sys32_recvmsg(int fd, str
 				 * 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 -ruNp 2.4.30-pre1/arch/ppc64/kernel/sys_ppc32.c 2.4.30-pre1-sfr.3/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.3/arch/ppc64/kernel/sys_ppc32.c	2005-02-22 11:59:42.000000000 +1100
@@ -3664,7 +3664,8 @@ static void scm_detach_fds32(struct msgh
  *		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,9 @@ static void cmsg32_recvmsg_fixup(struct 
 		__get_user(kcmsg32->cmsg_type, &ucmsg->cmsg_type);
 
 		clen64 = kcmsg32->cmsg_len;
+		if ((clen64 < CMSG_ALIGN(sizeof(*ucmsg))) ||
+				(clen64 > (orig_cmsg_len + wp - workbuf)))
+			break;
 		copy_from_user(CMSG32_DATA(kcmsg32), CMSG_DATA(ucmsg),
 			       clen64 - CMSG_ALIGN(sizeof(*ucmsg)));
 		clen32 = ((clen64 - CMSG_ALIGN(sizeof(*ucmsg))) +
@@ -3751,6 +3755,7 @@ asmlinkage long sys32_recvmsg(int fd, st
 	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 +3773,7 @@ asmlinkage long sys32_recvmsg(int fd, st
 	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 +3799,8 @@ asmlinkage long sys32_recvmsg(int fd, st
 				 * 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 -ruNp 2.4.30-pre1/arch/s390x/kernel/linux32.c 2.4.30-pre1-sfr.3/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.3/arch/s390x/kernel/linux32.c	2005-02-22 11:59:50.000000000 +1100
@@ -2597,7 +2597,8 @@ static void scm_detach_fds32(struct msgh
  *		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,9 @@ static void cmsg32_recvmsg_fixup(struct 
 		__get_user(kcmsg32->cmsg_type, &ucmsg->cmsg_type);
 
 		clen64 = kcmsg32->cmsg_len;
+		if ((clen64 < CMSG_ALIGN(sizeof(*ucmsg))) ||
+				(clen64 > (orig_cmsg_len + wp - workbuf)))
+			break;
 		copy_from_user(CMSG32_DATA(kcmsg32), CMSG_DATA(ucmsg),
 			       clen64 - CMSG_ALIGN(sizeof(*ucmsg)));
 		clen32 = ((clen64 - CMSG_ALIGN(sizeof(*ucmsg))) +
@@ -2887,7 +2891,8 @@ out:       
 
 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 +2907,7 @@ scm_recv32(struct socket *sock, struct m
 	 * 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 +2921,14 @@ scm_recv32(struct socket *sock, struct m
 
 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 +2945,7 @@ sys32_recvmsg (int fd, struct msghdr32 *
 	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 +2989,12 @@ sys32_recvmsg (int fd, struct msghdr32 *
 	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 -ruNp 2.4.30-pre1/arch/sparc64/kernel/sys_sparc32.c 2.4.30-pre1-sfr.3/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.3/arch/sparc64/kernel/sys_sparc32.c	2005-02-22 11:59:56.000000000 +1100
@@ -2647,7 +2647,8 @@ static void scm_detach_fds32(struct msgh
  *		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,9 @@ static void cmsg32_recvmsg_fixup(struct 
 		__get_user(kcmsg32->cmsg_type, &ucmsg->cmsg_type);
 
 		clen64 = kcmsg32->cmsg_len;
+		if ((clen64 < CMSG_ALIGN(sizeof(*ucmsg))) ||
+				(clen64 > (orig_cmsg_len + wp - workbuf)))
+			break;
 		if (kcmsg32->cmsg_level == SOL_SOCKET &&
 			kcmsg32->cmsg_type == SO_TIMESTAMP) {
 			struct timeval tv;
@@ -2781,6 +2785,7 @@ asmlinkage int sys32_recvmsg(int fd, str
 	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 +2801,7 @@ asmlinkage int sys32_recvmsg(int fd, str
 	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 +2827,8 @@ asmlinkage int sys32_recvmsg(int fd, str
 				 * 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 -ruNp 2.4.30-pre1/arch/x86_64/ia32/socket32.c 2.4.30-pre1-sfr.3/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.3/arch/x86_64/ia32/socket32.c	2005-02-22 12:00:04.000000000 +1100
@@ -302,7 +302,8 @@ static void scm_detach_fds32(struct msgh
  *		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,9 @@ static void cmsg32_recvmsg_fixup(struct 
 		__get_user(kcmsg32->cmsg_type, &ucmsg->cmsg_type);
 
 		clen64 = kcmsg32->cmsg_len;
+		if ((clen64 < CMSG_ALIGN(sizeof(*ucmsg))) ||
+				(clen64 > (orig_cmsg_len + wp - workbuf)))
+			break;
 		copy_from_user(CMSG32_DATA(kcmsg32), CMSG_DATA(ucmsg),
 			       clen64 - CMSG_ALIGN(sizeof(*ucmsg)));
 		clen32 = ((clen64 - CMSG_ALIGN(sizeof(*ucmsg))) +
@@ -418,6 +422,7 @@ asmlinkage long sys32_recvmsg(int fd, st
 	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 +438,7 @@ asmlinkage long sys32_recvmsg(int fd, st
 	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 +464,8 @@ asmlinkage long sys32_recvmsg(int fd, st
 				 * 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)
-------------- next part --------------
A non-text attachment was scrubbed...
Name: not available
Type: application/pgp-signature
Size: 189 bytes
Desc: not available
Url : http://ozlabs.org/pipermail/linuxppc64-dev/attachments/20050222/b70e78af/attachment.pgp 


More information about the Linuxppc64-dev mailing list