mirror of
https://github.com/Fishwaldo/linux-bl808.git
synced 2025-04-01 11:54:10 +00:00
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: Linus Torvalds <torvalds@linux-foundation.org>
This commit is contained in:
parent
f56f44001c
commit
435f49a518
3 changed files with 40 additions and 35 deletions
12
fs/compat.c
12
fs/compat.c
|
@ -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,
|
||||||
}
|
}
|
||||||
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 @@ bad:
|
||||||
* 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,
|
||||||
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);
|
||||||
/* /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
|
||||||
|
|
Loading…
Add table
Reference in a new issue