verify_iovec() has the following piece of code that allows overflow
of iov_len values in an iovec.

        for (ct = 0; ct < m->msg_iovlen; ct++) {
                err += iov[ct].iov_len;
                /*
                 * Goal is not to verify user data, but to prevent returning
                 * negative value, which is interpreted as errno.
                 * Overflow is still possible, but it is harmless.
                 */
                if (err < 0)
                        return -EMSGSIZE;
        }

The comment specifically says that overflow is harmless, but I don't see any
valid reason to allow overflow.

Also, iov_len is of type size_t which is a 64-bit value on 64-bit systems,
but we store and return the overall iovec length in an int.

This patch changes the return type of verify_iovec() and verify_compat_iovec()
to ssize_t and also validates that each iov_len value and the sum of all
the iov_len values do not overflow ssize_t. In case of a overflow, EINVAL is
returned.

The man pages for sendmsg/recvmsg also list this failure mode. 
  http://www.die.net/doc/linux/man/man3/sendmsg.3.html
    EINVAL
        The sum of the iov_len values overflows an ssize_t

This is based on some earlier patches from Chris Wright and Andrew Morton that
were incomplete.

Signed-off-by: Sridhar Samudrala <[EMAIL PROTECTED]>

diff --git a/include/linux/socket.h b/include/linux/socket.h
index 3614090..25645b0 100644
--- a/include/linux/socket.h
+++ b/include/linux/socket.h
@@ -294,7 +294,7 @@ extern int csum_partial_copy_fromiovecen
                                          int offset, 
                                          unsigned int len, int *csump);
 
-extern int verify_iovec(struct msghdr *m, struct iovec *iov, char *address, 
int mode);
+extern ssize_t verify_iovec(struct msghdr *m, struct iovec *iov, char 
*address, int mode);
 extern int memcpy_toiovec(struct iovec *v, unsigned char *kdata, int len);
 extern int move_addr_to_user(void *kaddr, int klen, void __user *uaddr, int 
__user *ulen);
 extern int move_addr_to_kernel(void __user *uaddr, int ulen, void *kaddr);
diff --git a/include/net/compat.h b/include/net/compat.h
index 9859b60..c30d648 100644
--- a/include/net/compat.h
+++ b/include/net/compat.h
@@ -31,7 +31,7 @@ #define compat_msghdr msghdr          /* to avoi
 #endif /* defined(CONFIG_COMPAT) */
 
 extern int get_compat_msghdr(struct msghdr *, struct compat_msghdr __user *);
-extern int verify_compat_iovec(struct msghdr *, struct iovec *, char *, int);
+extern ssize_t verify_compat_iovec(struct msghdr *, struct iovec *, char *, 
int);
 extern asmlinkage long compat_sys_sendmsg(int,struct compat_msghdr __user 
*,unsigned);
 extern asmlinkage long compat_sys_recvmsg(int,struct compat_msghdr __user 
*,unsigned);
 extern asmlinkage long compat_sys_getsockopt(int, int, int, char __user *, int 
__user *);
diff --git a/net/compat.c b/net/compat.c
index d5d69fa..f5d1a96 100644
--- a/net/compat.c
+++ b/net/compat.c
@@ -29,22 +29,29 @@ #include <net/sock.h>
 #include <asm/uaccess.h>
 #include <net/compat.h>
 
-static inline int iov_from_user_compat_to_kern(struct iovec *kiov,
+static inline ssize_t iov_from_user_compat_to_kern(struct iovec *kiov,
                                          struct compat_iovec __user *uiov32,
                                          int niov)
 {
-       int tot_len = 0;
+       compat_ssize_t tot_len = 0;
 
        while(niov > 0) {
                compat_uptr_t buf;
-               compat_size_t len;
+               compat_ssize_t len;
 
                if(get_user(len, &uiov32->iov_len) ||
                   get_user(buf, &uiov32->iov_base)) {
                        tot_len = -EFAULT;
                        break;
                }
+               /* Validate that each iov_len value and the sum of all the
+                * iov_len values do not overflow ssize_t.
+                */
+               if (len < 0)
+                       return -EINVAL; 
                tot_len += len;
+               if (tot_len < 0)
+                       return -EINVAL;
                kiov->iov_base = compat_ptr(buf);
                kiov->iov_len = (__kernel_size_t) len;
                uiov32++;
@@ -74,10 +81,10 @@ int get_compat_msghdr(struct msghdr *kms
 }
 
 /* I've named the args so it is easy to tell whose space the pointers are in. 
*/
-int verify_compat_iovec(struct msghdr *kern_msg, struct iovec *kern_iov,
+ssize_t verify_compat_iovec(struct msghdr *kern_msg, struct iovec *kern_iov,
                   char *kern_address, int mode)
 {
-       int tot_len;
+       ssize_t tot_len;
 
        if(kern_msg->msg_namelen) {
                if(mode==VERIFY_READ) {
diff --git a/net/core/iovec.c b/net/core/iovec.c
index 65e4b56..14b0f8f 100644
--- a/net/core/iovec.c
+++ b/net/core/iovec.c
@@ -37,9 +37,10 @@ #include <net/sock.h>
  *     in any case.
  */
 
-int verify_iovec(struct msghdr *m, struct iovec *iov, char *address, int mode)
+ssize_t verify_iovec(struct msghdr *m, struct iovec *iov, char *address, int 
mode)
 {
        int size, err, ct;
+       ssize_t tot_len = 0;
        
        if (m->msg_namelen) {
                if (mode == VERIFY_READ) {
@@ -61,17 +62,22 @@ int verify_iovec(struct msghdr *m, struc
        err = 0;
 
        for (ct = 0; ct < m->msg_iovlen; ct++) {
-               err += iov[ct].iov_len;
+               ssize_t len;
+
                /*
-                * Goal is not to verify user data, but to prevent returning
-                * negative value, which is interpreted as errno.
-                * Overflow is still possible, but it is harmless.
+                * Goal is not to verify user data, but to prevent the cases 
+                * where an iov_len value or the sum of all iov_len values
+                * overflows ssize_t.
                 */
-               if (err < 0)
-                       return -EMSGSIZE;
+               len = (ssize_t)iov[ct].iov_len;
+               if (len < 0)
+                       return -EINVAL;
+               tot_len += len;
+               if (tot_len < 0)
+                       return -EINVAL;
        }
 
-       return err;
+       return tot_len;
 }
 
 /*
diff --git a/net/socket.c b/net/socket.c
index b4848ce..7848180 100644
--- a/net/socket.c
+++ b/net/socket.c
@@ -1766,7 +1766,8 @@ asmlinkage long sys_sendmsg(int fd, stru
                        /* 20 is size of ipv6_pktinfo */
        unsigned char *ctl_buf = ctl;
        struct msghdr msg_sys;
-       int err, ctl_len, iov_size, total_len;
+       int err, ctl_len, iov_size;
+       ssize_t total_len;
        int fput_needed;
        
        err = -EFAULT;
@@ -1796,12 +1797,13 @@ asmlinkage long sys_sendmsg(int fd, stru
 
        /* This will also move the address data into kernel space */
        if (MSG_CMSG_COMPAT & flags) {
-               err = verify_compat_iovec(&msg_sys, iov, address, VERIFY_READ);
+               total_len = verify_compat_iovec(&msg_sys, iov, address, 
VERIFY_READ);
        } else
-               err = verify_iovec(&msg_sys, iov, address, VERIFY_READ);
-       if (err < 0) 
+               total_len = verify_iovec(&msg_sys, iov, address, VERIFY_READ);
+       if (total_len < 0) {
+               err = total_len;
                goto out_freeiov;
-       total_len = err;
+       }
 
        err = -ENOBUFS;
 
@@ -1861,7 +1863,8 @@ asmlinkage long sys_recvmsg(int fd, stru
        struct iovec *iov=iovstack;
        struct msghdr msg_sys;
        unsigned long cmsg_ptr;
-       int err, iov_size, total_len, len;
+       int err, iov_size, len;
+       ssize_t total_len;
        int fput_needed;
 
        /* kernel mode address */
@@ -1903,12 +1906,13 @@ asmlinkage long sys_recvmsg(int fd, stru
        uaddr = (void __user *) msg_sys.msg_name;
        uaddr_len = COMPAT_NAMELEN(msg);
        if (MSG_CMSG_COMPAT & flags) {
-               err = verify_compat_iovec(&msg_sys, iov, addr, VERIFY_WRITE);
+               total_len = verify_compat_iovec(&msg_sys, iov, addr, 
VERIFY_WRITE);
        } else
-               err = verify_iovec(&msg_sys, iov, addr, VERIFY_WRITE);
-       if (err < 0)
+               total_len = verify_iovec(&msg_sys, iov, addr, VERIFY_WRITE);
+       if (total_len < 0) {
+               err = total_len;
                goto out_freeiov;
-       total_len=err;
+       }
 
        cmsg_ptr = (unsigned long)msg_sys.msg_control;
        msg_sys.msg_flags = 0;


-
To unsubscribe from this list: send the line "unsubscribe netdev" in
the body of a message to [EMAIL PROTECTED]
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to