Revert "Revert "iov_iter: refactor rw_copy_check_uvector and import_iovec""
This reverts commit d59c65253c
.
Bug: 171539436
Signed-off-by: Greg Kroah-Hartman <gregkh@google.com>
Change-Id: I0322dc5061506c74efda0d2340b7305e662fabb6
This commit is contained in:
@@ -91,6 +91,11 @@
|
||||
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
|
||||
@@ -248,11 +253,6 @@ 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,12 +451,6 @@ 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);
|
||||
|
@@ -178,14 +178,6 @@ 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!
|
||||
@@ -1900,11 +1892,6 @@ 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_copy_file_range(struct file *, loff_t , struct file *,
|
||||
|
@@ -266,9 +266,15 @@ 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);
|
||||
|
||||
ssize_t import_iovec(int type, const struct iovec __user * uvector,
|
||||
unsigned nr_segs, unsigned fast_segs,
|
||||
struct iovec **iov, 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);
|
||||
|
||||
#ifdef CONFIG_COMPAT
|
||||
struct compat_iovec;
|
||||
|
300
lib/iov_iter.c
300
lib/iov_iter.c
@@ -8,6 +8,7 @@
|
||||
#include <linux/slab.h>
|
||||
#include <linux/vmalloc.h>
|
||||
#include <linux/splice.h>
|
||||
#include <linux/compat.h>
|
||||
#include <net/checksum.h>
|
||||
#include <linux/scatterlist.h>
|
||||
#include <linux/instrumented.h>
|
||||
@@ -1650,107 +1651,133 @@ const void *dup_iter(struct iov_iter *new, struct iov_iter *old, gfp_t flags)
|
||||
}
|
||||
EXPORT_SYMBOL(dup_iter);
|
||||
|
||||
/**
|
||||
* 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)
|
||||
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)
|
||||
{
|
||||
unsigned long seg;
|
||||
ssize_t ret;
|
||||
struct iovec *iov = fast_pointer;
|
||||
|
||||
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;
|
||||
|
||||
/*
|
||||
* 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) {
|
||||
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 == 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 == NULL) {
|
||||
ret = -ENOMEM;
|
||||
goto out;
|
||||
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);
|
||||
}
|
||||
if (copy_from_user(iov, uvector, nr_segs*sizeof(*uvector))) {
|
||||
ret = -EFAULT;
|
||||
goto out;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/*
|
||||
* 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.
|
||||
* 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;
|
||||
|
||||
/* 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 (!access_ok(iov[seg].iov_base, len)) {
|
||||
if (iov != *iovp)
|
||||
kfree(iov);
|
||||
*iovp = NULL;
|
||||
return -EFAULT;
|
||||
}
|
||||
if (type >= 0
|
||||
&& unlikely(!access_ok(buf, len))) {
|
||||
ret = -EFAULT;
|
||||
goto out;
|
||||
}
|
||||
if (len > MAX_RW_COUNT - ret) {
|
||||
len = MAX_RW_COUNT - ret;
|
||||
|
||||
if (len > MAX_RW_COUNT - total_len) {
|
||||
len = MAX_RW_COUNT - total_len;
|
||||
iov[seg].iov_len = len;
|
||||
}
|
||||
ret += len;
|
||||
total_len += len;
|
||||
}
|
||||
out:
|
||||
*ret_pointer = iov;
|
||||
return ret;
|
||||
|
||||
iov_iter_init(i, type, iov, nr_segs, total_len);
|
||||
if (iov == *iovp)
|
||||
*iovp = NULL;
|
||||
else
|
||||
*iovp = iov;
|
||||
return total_len;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1759,10 +1786,10 @@ out:
|
||||
* &struct iov_iter iterator to access it.
|
||||
*
|
||||
* @type: One of %READ or %WRITE.
|
||||
* @uvector: Pointer to the userspace array.
|
||||
* @uvec: Pointer to the userspace array.
|
||||
* @nr_segs: Number of elements in userspace array.
|
||||
* @fast_segs: Number of elements in @iov.
|
||||
* @iov: (input and output parameter) Pointer to pointer to (usually small
|
||||
* @iovp: (input and output parameter) Pointer to pointer to (usually small
|
||||
* on-stack) kernel array.
|
||||
* @i: Pointer to iterator that will be initialized on success.
|
||||
*
|
||||
@@ -1775,120 +1802,21 @@ out:
|
||||
*
|
||||
* Return: Negative error code on error, bytes imported on success
|
||||
*/
|
||||
ssize_t import_iovec(int type, const struct iovec __user * uvector,
|
||||
ssize_t import_iovec(int type, const struct iovec __user *uvec,
|
||||
unsigned nr_segs, unsigned fast_segs,
|
||||
struct iovec **iov, struct iov_iter *i)
|
||||
struct iovec **iovp, struct iov_iter *i)
|
||||
{
|
||||
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;
|
||||
return __import_iovec(type, uvec, nr_segs, fast_segs, iovp, i, false);
|
||||
}
|
||||
EXPORT_SYMBOL(import_iovec);
|
||||
|
||||
#ifdef CONFIG_COMPAT
|
||||
#include <linux/compat.h>
|
||||
|
||||
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)
|
||||
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)
|
||||
{
|
||||
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;
|
||||
return __import_iovec(type, (const struct iovec __user *)uvec, nr_segs,
|
||||
fast_segs, iovp, i, true);
|
||||
}
|
||||
EXPORT_SYMBOL(compat_import_iovec);
|
||||
#endif
|
||||
|
@@ -276,20 +276,17 @@ static ssize_t process_vm_rw(pid_t pid,
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
if (!iov_iter_count(&iter))
|
||||
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;
|
||||
|
||||
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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -333,18 +330,17 @@ compat_process_vm_rw(compat_pid_t pid,
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
if (!iov_iter_count(&iter))
|
||||
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;
|
||||
|
||||
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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
Reference in New Issue
Block a user