Merge branch 'devel' of master.kernel.org:/home/rmk/linux-2.6-serial
[pandora-kernel.git] / fs / read_write.c
index 4ed839b..f792000 100644 (file)
@@ -511,6 +511,74 @@ ssize_t do_loop_readv_writev(struct file *filp, struct iovec *iov,
 /* A write operation does a read from user space and vice versa */
 #define vrfy_dir(type) ((type) == READ ? VERIFY_WRITE : VERIFY_READ)
 
+ssize_t rw_copy_check_uvector(int type, const struct iovec __user * uvector,
+                             unsigned long nr_segs, unsigned long fast_segs,
+                             struct iovec *fast_pointer,
+                             struct iovec **ret_pointer)
+  {
+       unsigned long seg;
+       ssize_t ret;
+       struct iovec *iov = fast_pointer;
+
+       /*
+        * SuS says "The readv() function *may* fail if the iovcnt argument
+        * was less than or equal to 0, or greater than {IOV_MAX}.  Linux has
+        * traditionally returned zero for zero segments, so...
+        */
+       if (nr_segs == 0) {
+               ret = 0;
+               goto out;
+       }
+
+       /*
+        * First get the "struct iovec" from user memory and
+        * verify all the pointers
+        */
+       if (nr_segs > UIO_MAXIOV) {
+               ret = -EINVAL;
+               goto out;
+       }
+       if (nr_segs > fast_segs) {
+               iov = kmalloc(nr_segs*sizeof(struct iovec), GFP_KERNEL);
+               if (iov == NULL) {
+                       ret = -ENOMEM;
+                       goto out;
+               }
+       }
+       if (copy_from_user(iov, uvector, nr_segs*sizeof(*uvector))) {
+               ret = -EFAULT;
+               goto out;
+       }
+
+       /*
+        * According to the Single Unix Specification we should return EINVAL
+        * if an element length is < 0 when cast to ssize_t or if the
+        * total length would overflow the ssize_t return value of the
+        * system call.
+        */
+       ret = 0;
+       for (seg = 0; seg < nr_segs; seg++) {
+               void __user *buf = iov[seg].iov_base;
+               ssize_t len = (ssize_t)iov[seg].iov_len;
+
+               /* see if we we're about to use an invalid len or if
+                * it's about to overflow ssize_t */
+               if (len < 0 || (ret + len < ret)) {
+                       ret = -EINVAL;
+                       goto out;
+               }
+               if (unlikely(!access_ok(vrfy_dir(type), buf, len))) {
+                       ret = -EFAULT;
+                       goto out;
+               }
+
+               ret += len;
+       }
+out:
+       *ret_pointer = iov;
+       return ret;
+}
+
 static ssize_t do_readv_writev(int type, struct file *file,
                               const struct iovec __user * uvector,
                               unsigned long nr_segs, loff_t *pos)
@@ -519,64 +587,20 @@ static ssize_t do_readv_writev(int type, struct file *file,
        struct iovec iovstack[UIO_FASTIOV];
        struct iovec *iov = iovstack;
        ssize_t ret;
-       int seg;
        io_fn_t fn;
        iov_fn_t fnv;
 
-       /*
-        * SuS says "The readv() function *may* fail if the iovcnt argument
-        * was less than or equal to 0, or greater than {IOV_MAX}.  Linux has
-        * traditionally returned zero for zero segments, so...
-        */
-       ret = 0;
-       if (nr_segs == 0)
-               goto out;
-
-       /*
-        * First get the "struct iovec" from user memory and
-        * verify all the pointers
-        */
-       ret = -EINVAL;
-       if (nr_segs > UIO_MAXIOV)
-               goto out;
-       if (!file->f_op)
-               goto out;
-       if (nr_segs > UIO_FASTIOV) {
-               ret = -ENOMEM;
-               iov = kmalloc(nr_segs*sizeof(struct iovec), GFP_KERNEL);
-               if (!iov)
-                       goto out;
-       }
-       ret = -EFAULT;
-       if (copy_from_user(iov, uvector, nr_segs*sizeof(*uvector)))
+       if (!file->f_op) {
+               ret = -EINVAL;
                goto out;
+       }
 
-       /*
-        * Single unix specification:
-        * We should -EINVAL if an element length is not >= 0 and fitting an
-        * ssize_t.  The total length is fitting an ssize_t
-        *
-        * Be careful here because iov_len is a size_t not an ssize_t
-        */
-       tot_len = 0;
-       ret = -EINVAL;
-       for (seg = 0; seg < nr_segs; seg++) {
-               void __user *buf = iov[seg].iov_base;
-               ssize_t len = (ssize_t)iov[seg].iov_len;
-
-               if (len < 0)    /* size_t not fitting an ssize_t .. */
-                       goto out;
-               if (unlikely(!access_ok(vrfy_dir(type), buf, len)))
-                       goto Efault;
-               tot_len += len;
-               if ((ssize_t)tot_len < 0) /* maths overflow on the ssize_t */
-                       goto out;
-       }
-       if (tot_len == 0) {
-               ret = 0;
+       ret = rw_copy_check_uvector(type, uvector, nr_segs,
+                       ARRAY_SIZE(iovstack), iovstack, &iov);
+       if (ret <= 0)
                goto out;
-       }
 
+       tot_len = ret;
        ret = rw_verify_area(type, file, pos, tot_len);
        if (ret < 0)
                goto out;
@@ -609,9 +633,6 @@ out:
                        fsnotify_modify(file->f_dentry);
        }
        return ret;
-Efault:
-       ret = -EFAULT;
-       goto out;
 }
 
 ssize_t vfs_readv(struct file *file, const struct iovec __user *vec,