diff options
-rw-r--r-- | fs/compat.c | 12 | ||||
-rw-r--r-- | fs/read_write.c | 62 | ||||
-rw-r--r-- | include/linux/fs.h | 1 |
3 files changed, 40 insertions, 35 deletions
diff --git a/fs/compat.c b/fs/compat.c index 52cfeb61da77..ff66c0d7583d 100644 --- a/fs/compat.c +++ b/fs/compat.c | |||
@@ -606,14 +606,14 @@ ssize_t compat_rw_copy_check_uvector(int type, | |||
606 | /* | 606 | /* |
607 | * Single unix specification: | 607 | * Single unix specification: |
608 | * We should -EINVAL if an element length is not >= 0 and fitting an | 608 | * We should -EINVAL if an element length is not >= 0 and fitting an |
609 | * ssize_t. The total length is fitting an ssize_t | 609 | * ssize_t. |
610 | * | 610 | * |
611 | * Be careful here because iov_len is a size_t not an ssize_t | 611 | * In Linux, the total length is limited to MAX_RW_COUNT, there is |
612 | * no overflow possibility. | ||
612 | */ | 613 | */ |
613 | tot_len = 0; | 614 | tot_len = 0; |
614 | ret = -EINVAL; | 615 | ret = -EINVAL; |
615 | for (seg = 0; seg < nr_segs; seg++) { | 616 | for (seg = 0; seg < nr_segs; seg++) { |
616 | compat_ssize_t tmp = tot_len; | ||
617 | compat_uptr_t buf; | 617 | compat_uptr_t buf; |
618 | compat_ssize_t len; | 618 | compat_ssize_t len; |
619 | 619 | ||
@@ -624,13 +624,13 @@ ssize_t compat_rw_copy_check_uvector(int type, | |||
624 | } | 624 | } |
625 | if (len < 0) /* size_t not fitting in compat_ssize_t .. */ | 625 | if (len < 0) /* size_t not fitting in compat_ssize_t .. */ |
626 | goto out; | 626 | goto out; |
627 | tot_len += len; | ||
628 | if (tot_len < tmp) /* maths overflow on the compat_ssize_t */ | ||
629 | goto out; | ||
630 | if (!access_ok(vrfy_dir(type), compat_ptr(buf), len)) { | 627 | if (!access_ok(vrfy_dir(type), compat_ptr(buf), len)) { |
631 | ret = -EFAULT; | 628 | ret = -EFAULT; |
632 | goto out; | 629 | goto out; |
633 | } | 630 | } |
631 | if (len > MAX_RW_COUNT - tot_len) | ||
632 | len = MAX_RW_COUNT - tot_len; | ||
633 | tot_len += len; | ||
634 | iov->iov_base = compat_ptr(buf); | 634 | iov->iov_base = compat_ptr(buf); |
635 | iov->iov_len = (compat_size_t) len; | 635 | iov->iov_len = (compat_size_t) len; |
636 | uvector++; | 636 | uvector++; |
diff --git a/fs/read_write.c b/fs/read_write.c index 9cd9d148105d..431a0ed610c8 100644 --- a/fs/read_write.c +++ b/fs/read_write.c | |||
@@ -243,8 +243,6 @@ bad: | |||
243 | * them to something that fits in "int" so that others | 243 | * them to something that fits in "int" so that others |
244 | * won't have to do range checks all the time. | 244 | * won't have to do range checks all the time. |
245 | */ | 245 | */ |
246 | #define MAX_RW_COUNT (INT_MAX & PAGE_CACHE_MASK) | ||
247 | |||
248 | int rw_verify_area(int read_write, struct file *file, loff_t *ppos, size_t count) | 246 | int rw_verify_area(int read_write, struct file *file, loff_t *ppos, size_t count) |
249 | { | 247 | { |
250 | struct inode *inode; | 248 | struct inode *inode; |
@@ -584,65 +582,71 @@ ssize_t rw_copy_check_uvector(int type, const struct iovec __user * uvector, | |||
584 | unsigned long nr_segs, unsigned long fast_segs, | 582 | unsigned long nr_segs, unsigned long fast_segs, |
585 | struct iovec *fast_pointer, | 583 | struct iovec *fast_pointer, |
586 | struct iovec **ret_pointer) | 584 | struct iovec **ret_pointer) |
587 | { | 585 | { |
588 | unsigned long seg; | 586 | unsigned long seg; |
589 | ssize_t ret; | 587 | ssize_t ret; |
590 | struct iovec *iov = fast_pointer; | 588 | struct iovec *iov = fast_pointer; |
591 | 589 | ||
592 | /* | 590 | /* |
593 | * SuS says "The readv() function *may* fail if the iovcnt argument | 591 | * SuS says "The readv() function *may* fail if the iovcnt argument |
594 | * was less than or equal to 0, or greater than {IOV_MAX}. Linux has | 592 | * was less than or equal to 0, or greater than {IOV_MAX}. Linux has |
595 | * traditionally returned zero for zero segments, so... | 593 | * traditionally returned zero for zero segments, so... |
596 | */ | 594 | */ |
597 | if (nr_segs == 0) { | 595 | if (nr_segs == 0) { |
598 | ret = 0; | 596 | ret = 0; |
599 | goto out; | 597 | goto out; |
600 | } | 598 | } |
601 | 599 | ||
602 | /* | 600 | /* |
603 | * First get the "struct iovec" from user memory and | 601 | * First get the "struct iovec" from user memory and |
604 | * verify all the pointers | 602 | * verify all the pointers |
605 | */ | 603 | */ |
606 | if (nr_segs > UIO_MAXIOV) { | 604 | if (nr_segs > UIO_MAXIOV) { |
607 | ret = -EINVAL; | 605 | ret = -EINVAL; |
608 | goto out; | 606 | goto out; |
609 | } | 607 | } |
610 | if (nr_segs > fast_segs) { | 608 | if (nr_segs > fast_segs) { |
611 | iov = kmalloc(nr_segs*sizeof(struct iovec), GFP_KERNEL); | 609 | iov = kmalloc(nr_segs*sizeof(struct iovec), GFP_KERNEL); |
612 | if (iov == NULL) { | 610 | if (iov == NULL) { |
613 | ret = -ENOMEM; | 611 | ret = -ENOMEM; |
614 | goto out; | 612 | goto out; |
615 | } | 613 | } |
616 | } | 614 | } |
617 | if (copy_from_user(iov, uvector, nr_segs*sizeof(*uvector))) { | 615 | if (copy_from_user(iov, uvector, nr_segs*sizeof(*uvector))) { |
618 | ret = -EFAULT; | 616 | ret = -EFAULT; |
619 | goto out; | 617 | goto out; |
620 | } | 618 | } |
621 | 619 | ||
622 | /* | 620 | /* |
623 | * According to the Single Unix Specification we should return EINVAL | 621 | * According to the Single Unix Specification we should return EINVAL |
624 | * if an element length is < 0 when cast to ssize_t or if the | 622 | * if an element length is < 0 when cast to ssize_t or if the |
625 | * total length would overflow the ssize_t return value of the | 623 | * total length would overflow the ssize_t return value of the |
626 | * system call. | 624 | * system call. |
627 | */ | 625 | * |
626 | * Linux caps all read/write calls to MAX_RW_COUNT, and avoids the | ||
627 | * overflow case. | ||
628 | */ | ||
628 | ret = 0; | 629 | ret = 0; |
629 | for (seg = 0; seg < nr_segs; seg++) { | 630 | for (seg = 0; seg < nr_segs; seg++) { |
630 | void __user *buf = iov[seg].iov_base; | 631 | void __user *buf = iov[seg].iov_base; |
631 | ssize_t len = (ssize_t)iov[seg].iov_len; | 632 | ssize_t len = (ssize_t)iov[seg].iov_len; |
632 | 633 | ||
633 | /* see if we we're about to use an invalid len or if | 634 | /* see if we we're about to use an invalid len or if |
634 | * it's about to overflow ssize_t */ | 635 | * it's about to overflow ssize_t */ |
635 | if (len < 0 || (ret + len < ret)) { | 636 | if (len < 0) { |
636 | ret = -EINVAL; | 637 | ret = -EINVAL; |
637 | goto out; | 638 | goto out; |
638 | } | 639 | } |
639 | if (unlikely(!access_ok(vrfy_dir(type), buf, len))) { | 640 | if (unlikely(!access_ok(vrfy_dir(type), buf, len))) { |
640 | ret = -EFAULT; | 641 | ret = -EFAULT; |
641 | goto out; | 642 | goto out; |
643 | } | ||
644 | if (len > MAX_RW_COUNT - ret) { | ||
645 | len = MAX_RW_COUNT - ret; | ||
646 | iov[seg].iov_len = len; | ||
642 | } | 647 | } |
643 | |||
644 | ret += len; | 648 | ret += len; |
645 | } | 649 | } |
646 | out: | 650 | out: |
647 | *ret_pointer = iov; | 651 | *ret_pointer = iov; |
648 | return ret; | 652 | return ret; |
diff --git a/include/linux/fs.h b/include/linux/fs.h index 4d07902bc50c..7b7b507ffa1c 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h | |||
@@ -1867,6 +1867,7 @@ extern int current_umask(void); | |||
1867 | /* /sys/fs */ | 1867 | /* /sys/fs */ |
1868 | extern struct kobject *fs_kobj; | 1868 | extern struct kobject *fs_kobj; |
1869 | 1869 | ||
1870 | #define MAX_RW_COUNT (INT_MAX & PAGE_CACHE_MASK) | ||
1870 | extern int rw_verify_area(int, struct file *, loff_t *, size_t); | 1871 | extern int rw_verify_area(int, struct file *, loff_t *, size_t); |
1871 | 1872 | ||
1872 | #define FLOCK_VERIFY_READ 1 | 1873 | #define FLOCK_VERIFY_READ 1 |