Revert "iov_iter: refactor rw_copy_check_uvector and import_iovec"

This reverts commit bfdc59701d.

Bug: 171539436
Signed-off-by: Greg Kroah-Hartman <gregkh@google.com>
Change-Id: Ibf840a8fb38cb7d5888fb1bb1313647edf0b336e
This commit is contained in:
Greg Kroah-Hartman
2020-10-23 10:20:58 +02:00
parent 836219141f
commit d59c65253c
5 changed files with 236 additions and 147 deletions

View File

@@ -91,11 +91,6 @@
static inline long __do_compat_sys##name(__MAP(x,__SC_DECL,__VA_ARGS__)) static inline long __do_compat_sys##name(__MAP(x,__SC_DECL,__VA_ARGS__))
#endif /* COMPAT_SYSCALL_DEFINEx */ #endif /* COMPAT_SYSCALL_DEFINEx */
struct compat_iovec {
compat_uptr_t iov_base;
compat_size_t iov_len;
};
#ifdef CONFIG_COMPAT #ifdef CONFIG_COMPAT
#ifndef compat_user_stack_pointer #ifndef compat_user_stack_pointer
@@ -253,6 +248,11 @@ typedef struct compat_siginfo {
} _sifields; } _sifields;
} compat_siginfo_t; } compat_siginfo_t;
struct compat_iovec {
compat_uptr_t iov_base;
compat_size_t iov_len;
};
struct compat_rlimit { struct compat_rlimit {
compat_ulong_t rlim_cur; compat_ulong_t rlim_cur;
compat_ulong_t rlim_max; 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 */ 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); extern void __user *compat_alloc_user_space(unsigned long len);
int compat_restore_altstack(const compat_stack_t __user *uss); int compat_restore_altstack(const compat_stack_t __user *uss);

View File

@@ -178,6 +178,14 @@ typedef int (dio_iodone_t)(struct kiocb *iocb, loff_t offset,
/* File supports async buffered reads */ /* File supports async buffered reads */
#define FMODE_BUF_RASYNC ((__force fmode_t)0x40000000) #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 * Attribute flags. These should be or-ed together to figure out what
* has been changed! * 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); 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_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_write(struct file *, const char __user *, size_t, loff_t *);
extern ssize_t vfs_readv(struct file *, const struct iovec __user *, extern ssize_t vfs_readv(struct file *, const struct iovec __user *,

View File

@@ -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, size_t hash_and_copy_to_iter(const void *addr, size_t bytes, void *hashp,
struct iov_iter *i); struct iov_iter *i);
struct iovec *iovec_from_user(const struct iovec __user *uvector, ssize_t import_iovec(int type, const struct iovec __user * uvector,
unsigned long nr_segs, unsigned long fast_segs, unsigned nr_segs, unsigned fast_segs,
struct iovec *fast_iov, bool compat); struct iovec **iov, 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);
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 #ifdef CONFIG_COMPAT
struct compat_iovec; struct compat_iovec;

View File

@@ -7,7 +7,6 @@
#include <linux/slab.h> #include <linux/slab.h>
#include <linux/vmalloc.h> #include <linux/vmalloc.h>
#include <linux/splice.h> #include <linux/splice.h>
#include <linux/compat.h>
#include <net/checksum.h> #include <net/checksum.h>
#include <linux/scatterlist.h> #include <linux/scatterlist.h>
#include <linux/instrumented.h> #include <linux/instrumented.h>
@@ -1646,133 +1645,107 @@ const void *dup_iter(struct iov_iter *new, struct iov_iter *old, gfp_t flags)
} }
EXPORT_SYMBOL(dup_iter); EXPORT_SYMBOL(dup_iter);
static int copy_compat_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.
const struct compat_iovec __user *uiov = *
(const struct compat_iovec __user *)uvec; * @type: One of %CHECK_IOVEC_ONLY, %READ, or %WRITE.
int ret = -EFAULT, i; * @uvector: Pointer to the userspace array.
* @nr_segs: Number of elements in userspace array.
if (!user_access_begin(uvec, nr_segs * sizeof(*uvec))) * @fast_segs: Number of elements in @fast_pointer.
return -EFAULT; * @fast_pointer: Pointer to (usually small on-stack) kernel array.
* @ret_pointer: (output parameter) Pointer to a variable that will point to
for (i = 0; i < nr_segs; i++) { * either @fast_pointer, a newly allocated kernel array, or NULL,
compat_uptr_t buf; * depending on which array was used.
compat_ssize_t len; *
* This function copies an array of &struct iovec of @nr_segs from
unsafe_get_user(len, &uiov[i].iov_len, uaccess_end); * userspace into the kernel and checks that each element is valid (e.g.
unsafe_get_user(buf, &uiov[i].iov_base, uaccess_end); * it does not point to a kernel address or cause overflow by being too
* large, etc.).
/* check for compat_size_t not fitting in compat_ssize_t .. */ *
if (len < 0) { * As an optimization, the caller may provide a pointer to a small
ret = -EINVAL; * on-stack array in @fast_pointer, typically %UIO_FASTIOV elements long
goto uaccess_end; * (the size of this array, or 0 if unused, should be given in @fast_segs).
} *
iov[i].iov_base = compat_ptr(buf); * @ret_pointer will always point to the array that was used, so the
iov[i].iov_len = len; * 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.
*
ret = 0; * Return: The total number of bytes covered by the iovec array on success
uaccess_end: * or a negative error code on error.
user_access_end(); */
return ret; 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_iovec_from_user(struct iovec *iov,
const struct iovec __user *uvec, unsigned long nr_segs)
{ {
unsigned long seg; unsigned long seg;
ssize_t ret;
if (copy_from_user(iov, uvec, nr_segs * sizeof(*uvec))) struct iovec *iov = fast_pointer;
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 * SuS says "The readv() function *may* fail if the iovcnt argument
* less than or equal to 0, or greater than {IOV_MAX}. Linux has * was less than or equal to 0, or greater than {IOV_MAX}. Linux has
* traditionally returned zero for zero segments, so... * traditionally returned zero for zero segments, so...
*/ */
if (nr_segs == 0) if (nr_segs == 0) {
return iov; ret = 0;
if (nr_segs > UIO_MAXIOV) goto out;
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);
} }
/* /*
* According to the Single Unix Specification we should return EINVAL if * First get the "struct iovec" from user memory and
* an element length is < 0 when cast to ssize_t or if the total length * verify all the pointers
* would overflow the ssize_t return value of the system call. */
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 * Linux caps all read/write calls to MAX_RW_COUNT, and avoids the
* overflow case. * overflow case.
*/ */
ret = 0;
for (seg = 0; seg < nr_segs; seg++) { for (seg = 0; seg < nr_segs; seg++) {
void __user *buf = iov[seg].iov_base;
ssize_t len = (ssize_t)iov[seg].iov_len; ssize_t len = (ssize_t)iov[seg].iov_len;
if (!access_ok(iov[seg].iov_base, len)) { /* see if we we're about to use an invalid len or if
if (iov != *iovp) * it's about to overflow ssize_t */
kfree(iov); if (len < 0) {
*iovp = NULL; ret = -EINVAL;
return -EFAULT; goto out;
} }
if (type >= 0
if (len > MAX_RW_COUNT - total_len) { && unlikely(!access_ok(buf, len))) {
len = MAX_RW_COUNT - total_len; ret = -EFAULT;
goto out;
}
if (len > MAX_RW_COUNT - ret) {
len = MAX_RW_COUNT - ret;
iov[seg].iov_len = len; iov[seg].iov_len = len;
} }
total_len += len; ret += len;
} }
out:
iov_iter_init(i, type, iov, nr_segs, total_len); *ret_pointer = iov;
if (iov == *iovp) return ret;
*iovp = NULL;
else
*iovp = iov;
return total_len;
} }
/** /**
@@ -1781,10 +1754,10 @@ ssize_t __import_iovec(int type, const struct iovec __user *uvec,
* &struct iov_iter iterator to access it. * &struct iov_iter iterator to access it.
* *
* @type: One of %READ or %WRITE. * @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. * @nr_segs: Number of elements in userspace array.
* @fast_segs: Number of elements in @iov. * @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. * on-stack) kernel array.
* @i: Pointer to iterator that will be initialized on success. * @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 * 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, 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); EXPORT_SYMBOL(import_iovec);
#ifdef CONFIG_COMPAT #ifdef CONFIG_COMPAT
ssize_t compat_import_iovec(int type, const struct compat_iovec __user *uvec, #include <linux/compat.h>
unsigned nr_segs, unsigned fast_segs, struct iovec **iovp,
struct iov_iter *i) 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, compat_ssize_t tot_len;
fast_segs, iovp, i, true); 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); EXPORT_SYMBOL(compat_import_iovec);
#endif #endif

View File

@@ -276,17 +276,20 @@ static ssize_t process_vm_rw(pid_t pid,
if (rc < 0) if (rc < 0)
return rc; return rc;
if (!iov_iter_count(&iter)) if (!iov_iter_count(&iter))
goto free_iov_l; goto free_iovecs;
iov_r = iovec_from_user(rvec, riovcnt, UIO_FASTIOV, iovstack_r, false);
if (IS_ERR(iov_r)) { rc = rw_copy_check_uvector(CHECK_IOVEC_ONLY, rvec, riovcnt, UIO_FASTIOV,
rc = PTR_ERR(iov_r); iovstack_r, &iov_r);
goto free_iov_l; if (rc <= 0)
} goto free_iovecs;
rc = process_vm_rw_core(pid, &iter, iov_r, riovcnt, flags, vm_write); rc = process_vm_rw_core(pid, &iter, iov_r, riovcnt, flags, vm_write);
free_iovecs:
if (iov_r != iovstack_r) if (iov_r != iovstack_r)
kfree(iov_r); kfree(iov_r);
free_iov_l:
kfree(iov_l); kfree(iov_l);
return rc; return rc;
} }
@@ -330,17 +333,18 @@ compat_process_vm_rw(compat_pid_t pid,
if (rc < 0) if (rc < 0)
return rc; return rc;
if (!iov_iter_count(&iter)) if (!iov_iter_count(&iter))
goto free_iov_l; goto free_iovecs;
iov_r = iovec_from_user((const struct iovec __user *)rvec, riovcnt, rc = compat_rw_copy_check_uvector(CHECK_IOVEC_ONLY, rvec, riovcnt,
UIO_FASTIOV, iovstack_r, true); UIO_FASTIOV, iovstack_r,
if (IS_ERR(iov_r)) { &iov_r);
rc = PTR_ERR(iov_r); if (rc <= 0)
goto free_iov_l; goto free_iovecs;
}
rc = process_vm_rw_core(pid, &iter, iov_r, riovcnt, flags, vm_write); rc = process_vm_rw_core(pid, &iter, iov_r, riovcnt, flags, vm_write);
free_iovecs:
if (iov_r != iovstack_r) if (iov_r != iovstack_r)
kfree(iov_r); kfree(iov_r);
free_iov_l:
kfree(iov_l); kfree(iov_l);
return rc; return rc;
} }