[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