Commit 435f49a5 authored by Linus Torvalds's avatar Linus Torvalds

readv/writev: do the same MAX_RW_COUNT truncation that read/write does

We used to protect against overflow, but rather than return an error, do
what read/write does, namely to limit the total size to MAX_RW_COUNT.
This is not only more consistent, but it also means that any broken
low-level read/write routine that still keeps counts in 'int' can't
break.
Signed-off-by: default avatarLinus Torvalds <torvalds@linux-foundation.org>
parent f56f4400
...@@ -606,14 +606,14 @@ ssize_t compat_rw_copy_check_uvector(int type, ...@@ -606,14 +606,14 @@ ssize_t compat_rw_copy_check_uvector(int type,
/* /*
* Single unix specification: * Single unix specification:
* We should -EINVAL if an element length is not >= 0 and fitting an * We should -EINVAL if an element length is not >= 0 and fitting an
* ssize_t. The total length is fitting an ssize_t * ssize_t.
* *
* Be careful here because iov_len is a size_t not an ssize_t * In Linux, the total length is limited to MAX_RW_COUNT, there is
* no overflow possibility.
*/ */
tot_len = 0; tot_len = 0;
ret = -EINVAL; ret = -EINVAL;
for (seg = 0; seg < nr_segs; seg++) { for (seg = 0; seg < nr_segs; seg++) {
compat_ssize_t tmp = tot_len;
compat_uptr_t buf; compat_uptr_t buf;
compat_ssize_t len; compat_ssize_t len;
...@@ -624,13 +624,13 @@ ssize_t compat_rw_copy_check_uvector(int type, ...@@ -624,13 +624,13 @@ ssize_t compat_rw_copy_check_uvector(int type,
} }
if (len < 0) /* size_t not fitting in compat_ssize_t .. */ if (len < 0) /* size_t not fitting in compat_ssize_t .. */
goto out; goto out;
tot_len += len;
if (tot_len < tmp) /* maths overflow on the compat_ssize_t */
goto out;
if (!access_ok(vrfy_dir(type), compat_ptr(buf), len)) { if (!access_ok(vrfy_dir(type), compat_ptr(buf), len)) {
ret = -EFAULT; ret = -EFAULT;
goto out; 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_base = compat_ptr(buf);
iov->iov_len = (compat_size_t) len; iov->iov_len = (compat_size_t) len;
uvector++; uvector++;
......
...@@ -243,8 +243,6 @@ SYSCALL_DEFINE5(llseek, unsigned int, fd, unsigned long, offset_high, ...@@ -243,8 +243,6 @@ SYSCALL_DEFINE5(llseek, unsigned int, fd, unsigned long, offset_high,
* them to something that fits in "int" so that others * them to something that fits in "int" so that others
* won't have to do range checks all the time. * won't have to do range checks all the time.
*/ */
#define MAX_RW_COUNT (INT_MAX & PAGE_CACHE_MASK)
int rw_verify_area(int read_write, struct file *file, loff_t *ppos, size_t count) int rw_verify_area(int read_write, struct file *file, loff_t *ppos, size_t count)
{ {
struct inode *inode; struct inode *inode;
...@@ -584,65 +582,71 @@ ssize_t rw_copy_check_uvector(int type, const struct iovec __user * uvector, ...@@ -584,65 +582,71 @@ ssize_t rw_copy_check_uvector(int type, const struct iovec __user * uvector,
unsigned long nr_segs, unsigned long fast_segs, unsigned long nr_segs, unsigned long fast_segs,
struct iovec *fast_pointer, struct iovec *fast_pointer,
struct iovec **ret_pointer) struct iovec **ret_pointer)
{ {
unsigned long seg; unsigned long seg;
ssize_t ret; ssize_t ret;
struct iovec *iov = fast_pointer; struct iovec *iov = fast_pointer;
/* /*
* SuS says "The readv() function *may* fail if the iovcnt argument * 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 * 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) {
ret = 0; ret = 0;
goto out; goto out;
} }
/* /*
* First get the "struct iovec" from user memory and * First get the "struct iovec" from user memory and
* verify all the pointers * verify all the pointers
*/ */
if (nr_segs > UIO_MAXIOV) { if (nr_segs > UIO_MAXIOV) {
ret = -EINVAL; ret = -EINVAL;
goto out; goto out;
} }
if (nr_segs > fast_segs) { if (nr_segs > fast_segs) {
iov = kmalloc(nr_segs*sizeof(struct iovec), GFP_KERNEL); iov = kmalloc(nr_segs*sizeof(struct iovec), GFP_KERNEL);
if (iov == NULL) { if (iov == NULL) {
ret = -ENOMEM; ret = -ENOMEM;
goto out; goto out;
} }
} }
if (copy_from_user(iov, uvector, nr_segs*sizeof(*uvector))) { if (copy_from_user(iov, uvector, nr_segs*sizeof(*uvector))) {
ret = -EFAULT; ret = -EFAULT;
goto out; goto out;
} }
/* /*
* According to the Single Unix Specification we should return EINVAL * According to the Single Unix Specification we should return EINVAL
* if an element length is < 0 when cast to ssize_t or if the * 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 * total length would overflow the ssize_t return value of the
* system call. * system call.
*/ *
* Linux caps all read/write calls to MAX_RW_COUNT, and avoids the
* overflow case.
*/
ret = 0; ret = 0;
for (seg = 0; seg < nr_segs; seg++) { for (seg = 0; seg < nr_segs; seg++) {
void __user *buf = iov[seg].iov_base; 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;
/* see if we we're about to use an invalid len or if /* see if we we're about to use an invalid len or if
* it's about to overflow ssize_t */ * it's about to overflow ssize_t */
if (len < 0 || (ret + len < ret)) { if (len < 0) {
ret = -EINVAL; ret = -EINVAL;
goto out; goto out;
} }
if (unlikely(!access_ok(vrfy_dir(type), buf, len))) { if (unlikely(!access_ok(vrfy_dir(type), buf, len))) {
ret = -EFAULT; ret = -EFAULT;
goto out; goto out;
}
if (len > MAX_RW_COUNT - ret) {
len = MAX_RW_COUNT - ret;
iov[seg].iov_len = len;
} }
ret += len; ret += len;
} }
out: out:
*ret_pointer = iov; *ret_pointer = iov;
return ret; return ret;
......
...@@ -1867,6 +1867,7 @@ extern int current_umask(void); ...@@ -1867,6 +1867,7 @@ extern int current_umask(void);
/* /sys/fs */ /* /sys/fs */
extern struct kobject *fs_kobj; extern struct kobject *fs_kobj;
#define MAX_RW_COUNT (INT_MAX & PAGE_CACHE_MASK)
extern int rw_verify_area(int, struct file *, loff_t *, size_t); extern int rw_verify_area(int, struct file *, loff_t *, size_t);
#define FLOCK_VERIFY_READ 1 #define FLOCK_VERIFY_READ 1
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment