diff --git a/include/linux/compat.h b/include/linux/compat.h index 7b02522ddf8e..338e2c619a7f 100644 --- a/include/linux/compat.h +++ b/include/linux/compat.h @@ -91,11 +91,6 @@ static inline long __do_compat_sys##name(__MAP(x,__SC_DECL,__VA_ARGS__)) #endif /* COMPAT_SYSCALL_DEFINEx */ -struct compat_iovec { - compat_uptr_t iov_base; - compat_size_t iov_len; -}; - #ifdef CONFIG_COMPAT #ifndef compat_user_stack_pointer @@ -253,6 +248,11 @@ typedef struct compat_siginfo { } _sifields; } compat_siginfo_t; +struct compat_iovec { + compat_uptr_t iov_base; + compat_size_t iov_len; +}; + struct compat_rlimit { compat_ulong_t rlim_cur; compat_ulong_t rlim_max; @@ -451,6 +451,12 @@ extern long compat_arch_ptrace(struct task_struct *child, compat_long_t request, struct epoll_event; /* fortunately, this one is fixed-layout */ +extern ssize_t compat_rw_copy_check_uvector(int type, + const struct compat_iovec __user *uvector, + unsigned long nr_segs, + unsigned long fast_segs, struct iovec *fast_pointer, + struct iovec **ret_pointer); + extern void __user *compat_alloc_user_space(unsigned long len); int compat_restore_altstack(const compat_stack_t __user *uss); diff --git a/include/linux/fs.h b/include/linux/fs.h index cfb376dc22c6..7010ff2aa11e 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -178,6 +178,14 @@ typedef int (dio_iodone_t)(struct kiocb *iocb, loff_t offset, /* File supports async buffered reads */ #define FMODE_BUF_RASYNC ((__force fmode_t)0x40000000) +/* + * Flag for rw_copy_check_uvector and compat_rw_copy_check_uvector + * that indicates that they should check the contents of the iovec are + * valid, but not check the memory that the iovec elements + * points too. + */ +#define CHECK_IOVEC_ONLY -1 + /* * Attribute flags. These should be or-ed together to figure out what * has been changed! @@ -1889,6 +1897,11 @@ static inline int call_mmap(struct file *file, struct vm_area_struct *vma) return file->f_op->mmap(file, vma); } +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); + extern ssize_t vfs_read(struct file *, char __user *, size_t, loff_t *); extern ssize_t vfs_write(struct file *, const char __user *, size_t, loff_t *); extern ssize_t vfs_readv(struct file *, const struct iovec __user *, diff --git a/include/linux/uio.h b/include/linux/uio.h index bc7e3b3eec98..f14410c678bd 100644 --- a/include/linux/uio.h +++ b/include/linux/uio.h @@ -266,15 +266,9 @@ bool csum_and_copy_from_iter_full(void *addr, size_t bytes, __wsum *csum, struct size_t hash_and_copy_to_iter(const void *addr, size_t bytes, void *hashp, struct iov_iter *i); -struct iovec *iovec_from_user(const struct iovec __user *uvector, - unsigned long nr_segs, unsigned long fast_segs, - struct iovec *fast_iov, bool compat); -ssize_t import_iovec(int type, const struct iovec __user *uvec, - unsigned nr_segs, unsigned fast_segs, struct iovec **iovp, - struct iov_iter *i); -ssize_t __import_iovec(int type, const struct iovec __user *uvec, - unsigned nr_segs, unsigned fast_segs, struct iovec **iovp, - struct iov_iter *i, bool compat); +ssize_t import_iovec(int type, const struct iovec __user * uvector, + unsigned nr_segs, unsigned fast_segs, + struct iovec **iov, struct iov_iter *i); #ifdef CONFIG_COMPAT struct compat_iovec; diff --git a/lib/iov_iter.c b/lib/iov_iter.c index 1b92d6c865e9..4397057da751 100644 --- a/lib/iov_iter.c +++ b/lib/iov_iter.c @@ -7,7 +7,6 @@ #include #include #include -#include #include #include #include @@ -1646,133 +1645,107 @@ const void *dup_iter(struct iov_iter *new, struct iov_iter *old, gfp_t flags) } EXPORT_SYMBOL(dup_iter); -static int copy_compat_iovec_from_user(struct iovec *iov, - const struct iovec __user *uvec, unsigned long nr_segs) -{ - const struct compat_iovec __user *uiov = - (const struct compat_iovec __user *)uvec; - int ret = -EFAULT, i; - - if (!user_access_begin(uvec, nr_segs * sizeof(*uvec))) - return -EFAULT; - - for (i = 0; i < nr_segs; i++) { - compat_uptr_t buf; - compat_ssize_t len; - - unsafe_get_user(len, &uiov[i].iov_len, uaccess_end); - unsafe_get_user(buf, &uiov[i].iov_base, uaccess_end); - - /* check for compat_size_t not fitting in compat_ssize_t .. */ - if (len < 0) { - ret = -EINVAL; - goto uaccess_end; - } - iov[i].iov_base = compat_ptr(buf); - iov[i].iov_len = len; - } - - ret = 0; -uaccess_end: - user_access_end(); - return ret; -} - -static int copy_iovec_from_user(struct iovec *iov, - const struct iovec __user *uvec, unsigned long nr_segs) +/** + * rw_copy_check_uvector() - Copy an array of &struct iovec from userspace + * into the kernel and check that it is valid. + * + * @type: One of %CHECK_IOVEC_ONLY, %READ, or %WRITE. + * @uvector: Pointer to the userspace array. + * @nr_segs: Number of elements in userspace array. + * @fast_segs: Number of elements in @fast_pointer. + * @fast_pointer: Pointer to (usually small on-stack) kernel array. + * @ret_pointer: (output parameter) Pointer to a variable that will point to + * either @fast_pointer, a newly allocated kernel array, or NULL, + * depending on which array was used. + * + * This function copies an array of &struct iovec of @nr_segs from + * userspace into the kernel and checks that each element is valid (e.g. + * it does not point to a kernel address or cause overflow by being too + * large, etc.). + * + * As an optimization, the caller may provide a pointer to a small + * on-stack array in @fast_pointer, typically %UIO_FASTIOV elements long + * (the size of this array, or 0 if unused, should be given in @fast_segs). + * + * @ret_pointer will always point to the array that was used, so the + * caller must take care not to call kfree() on it e.g. in case the + * @fast_pointer array was used and it was allocated on the stack. + * + * Return: The total number of bytes covered by the iovec array on success + * or a negative error code on error. + */ +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; - - if (copy_from_user(iov, uvec, nr_segs * sizeof(*uvec))) - return -EFAULT; - for (seg = 0; seg < nr_segs; seg++) { - if ((ssize_t)iov[seg].iov_len < 0) - return -EINVAL; - } - - return 0; -} - -struct iovec *iovec_from_user(const struct iovec __user *uvec, - unsigned long nr_segs, unsigned long fast_segs, - struct iovec *fast_iov, bool compat) -{ - struct iovec *iov = fast_iov; - int ret; + 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 + * 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) - return iov; - if (nr_segs > UIO_MAXIOV) - return ERR_PTR(-EINVAL); - if (nr_segs > fast_segs) { - iov = kmalloc_array(nr_segs, sizeof(struct iovec), GFP_KERNEL); - if (!iov) - return ERR_PTR(-ENOMEM); - } - - if (compat) - ret = copy_compat_iovec_from_user(iov, uvec, nr_segs); - else - ret = copy_iovec_from_user(iov, uvec, nr_segs); - if (ret) { - if (iov != fast_iov) - kfree(iov); - return ERR_PTR(ret); - } - - return iov; -} - -ssize_t __import_iovec(int type, const struct iovec __user *uvec, - unsigned nr_segs, unsigned fast_segs, struct iovec **iovp, - struct iov_iter *i, bool compat) -{ - ssize_t total_len = 0; - unsigned long seg; - struct iovec *iov; - - iov = iovec_from_user(uvec, nr_segs, fast_segs, *iovp, compat); - if (IS_ERR(iov)) { - *iovp = NULL; - return PTR_ERR(iov); + if (nr_segs == 0) { + ret = 0; + 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. + * 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_array(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. * * Linux caps all read/write calls to MAX_RW_COUNT, and avoids the * overflow case. */ + 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; - if (!access_ok(iov[seg].iov_base, len)) { - if (iov != *iovp) - kfree(iov); - *iovp = NULL; - return -EFAULT; + /* see if we we're about to use an invalid len or if + * it's about to overflow ssize_t */ + if (len < 0) { + ret = -EINVAL; + goto out; } - - if (len > MAX_RW_COUNT - total_len) { - len = MAX_RW_COUNT - total_len; + if (type >= 0 + && unlikely(!access_ok(buf, len))) { + ret = -EFAULT; + goto out; + } + if (len > MAX_RW_COUNT - ret) { + len = MAX_RW_COUNT - ret; iov[seg].iov_len = len; } - total_len += len; + ret += len; } - - iov_iter_init(i, type, iov, nr_segs, total_len); - if (iov == *iovp) - *iovp = NULL; - else - *iovp = iov; - return total_len; +out: + *ret_pointer = iov; + return ret; } /** @@ -1781,10 +1754,10 @@ ssize_t __import_iovec(int type, const struct iovec __user *uvec, * &struct iov_iter iterator to access it. * * @type: One of %READ or %WRITE. - * @uvec: Pointer to the userspace array. + * @uvector: Pointer to the userspace array. * @nr_segs: Number of elements in userspace array. * @fast_segs: Number of elements in @iov. - * @iovp: (input and output parameter) Pointer to pointer to (usually small + * @iov: (input and output parameter) Pointer to pointer to (usually small * on-stack) kernel array. * @i: Pointer to iterator that will be initialized on success. * @@ -1797,21 +1770,120 @@ ssize_t __import_iovec(int type, const struct iovec __user *uvec, * * Return: Negative error code on error, bytes imported on success */ -ssize_t import_iovec(int type, const struct iovec __user *uvec, +ssize_t import_iovec(int type, const struct iovec __user * uvector, unsigned nr_segs, unsigned fast_segs, - struct iovec **iovp, struct iov_iter *i) + struct iovec **iov, struct iov_iter *i) { - return __import_iovec(type, uvec, nr_segs, fast_segs, iovp, i, false); + ssize_t n; + struct iovec *p; + n = rw_copy_check_uvector(type, uvector, nr_segs, fast_segs, + *iov, &p); + if (n < 0) { + if (p != *iov) + kfree(p); + *iov = NULL; + return n; + } + iov_iter_init(i, type, p, nr_segs, n); + *iov = p == *iov ? NULL : p; + return n; } EXPORT_SYMBOL(import_iovec); #ifdef CONFIG_COMPAT -ssize_t compat_import_iovec(int type, const struct compat_iovec __user *uvec, - unsigned nr_segs, unsigned fast_segs, struct iovec **iovp, - struct iov_iter *i) +#include + +ssize_t compat_rw_copy_check_uvector(int type, + const struct compat_iovec __user *uvector, + unsigned long nr_segs, unsigned long fast_segs, + struct iovec *fast_pointer, struct iovec **ret_pointer) { - return __import_iovec(type, (const struct iovec __user *)uvec, nr_segs, - fast_segs, iovp, i, true); + compat_ssize_t tot_len; + struct iovec *iov = *ret_pointer = fast_pointer; + ssize_t ret = 0; + int seg; + + /* + * 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) + goto out; + + ret = -EINVAL; + if (nr_segs > UIO_MAXIOV) + goto out; + if (nr_segs > fast_segs) { + ret = -ENOMEM; + iov = kmalloc_array(nr_segs, sizeof(struct iovec), GFP_KERNEL); + if (iov == NULL) + goto out; + } + *ret_pointer = iov; + + ret = -EFAULT; + if (!access_ok(uvector, nr_segs*sizeof(*uvector))) + goto out; + + /* + * Single unix specification: + * We should -EINVAL if an element length is not >= 0 and fitting an + * ssize_t. + * + * In Linux, the total length is limited to MAX_RW_COUNT, there is + * no overflow possibility. + */ + tot_len = 0; + ret = -EINVAL; + for (seg = 0; seg < nr_segs; seg++) { + compat_uptr_t buf; + compat_ssize_t len; + + if (__get_user(len, &uvector->iov_len) || + __get_user(buf, &uvector->iov_base)) { + ret = -EFAULT; + goto out; + } + if (len < 0) /* size_t not fitting in compat_ssize_t .. */ + goto out; + if (type >= 0 && + !access_ok(compat_ptr(buf), len)) { + ret = -EFAULT; + goto out; + } + if (len > MAX_RW_COUNT - tot_len) + len = MAX_RW_COUNT - tot_len; + tot_len += len; + iov->iov_base = compat_ptr(buf); + iov->iov_len = (compat_size_t) len; + uvector++; + iov++; + } + ret = tot_len; + +out: + return ret; +} + +ssize_t compat_import_iovec(int type, + const struct compat_iovec __user * uvector, + unsigned nr_segs, unsigned fast_segs, + struct iovec **iov, struct iov_iter *i) +{ + ssize_t n; + struct iovec *p; + n = compat_rw_copy_check_uvector(type, uvector, nr_segs, fast_segs, + *iov, &p); + if (n < 0) { + if (p != *iov) + kfree(p); + *iov = NULL; + return n; + } + iov_iter_init(i, type, p, nr_segs, n); + *iov = p == *iov ? NULL : p; + return n; } EXPORT_SYMBOL(compat_import_iovec); #endif diff --git a/mm/process_vm_access.c b/mm/process_vm_access.c index 5e728c20c2be..29c052099aff 100644 --- a/mm/process_vm_access.c +++ b/mm/process_vm_access.c @@ -276,17 +276,20 @@ static ssize_t process_vm_rw(pid_t pid, if (rc < 0) return rc; if (!iov_iter_count(&iter)) - goto free_iov_l; - iov_r = iovec_from_user(rvec, riovcnt, UIO_FASTIOV, iovstack_r, false); - if (IS_ERR(iov_r)) { - rc = PTR_ERR(iov_r); - goto free_iov_l; - } + goto free_iovecs; + + rc = rw_copy_check_uvector(CHECK_IOVEC_ONLY, rvec, riovcnt, UIO_FASTIOV, + iovstack_r, &iov_r); + if (rc <= 0) + goto free_iovecs; + rc = process_vm_rw_core(pid, &iter, iov_r, riovcnt, flags, vm_write); + +free_iovecs: if (iov_r != iovstack_r) kfree(iov_r); -free_iov_l: kfree(iov_l); + return rc; } @@ -330,17 +333,18 @@ compat_process_vm_rw(compat_pid_t pid, if (rc < 0) return rc; if (!iov_iter_count(&iter)) - goto free_iov_l; - iov_r = iovec_from_user((const struct iovec __user *)rvec, riovcnt, - UIO_FASTIOV, iovstack_r, true); - if (IS_ERR(iov_r)) { - rc = PTR_ERR(iov_r); - goto free_iov_l; - } + goto free_iovecs; + rc = compat_rw_copy_check_uvector(CHECK_IOVEC_ONLY, rvec, riovcnt, + UIO_FASTIOV, iovstack_r, + &iov_r); + if (rc <= 0) + goto free_iovecs; + rc = process_vm_rw_core(pid, &iter, iov_r, riovcnt, flags, vm_write); + +free_iovecs: if (iov_r != iovstack_r) kfree(iov_r); -free_iov_l: kfree(iov_l); return rc; }