diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2010-10-29 13:36:49 -0400 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2010-10-29 13:36:49 -0400 |
commit | 435f49a518c78eec8e2edbbadd912737246cbe20 (patch) | |
tree | 106df2617d42ace231e2fa9fcf1e0fd1075874ea /fs/read_write.c | |
parent | f56f44001cb5b40089deac094dbb74e5c9f64d81 (diff) |
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>
Diffstat (limited to 'fs/read_write.c')
-rw-r--r-- | fs/read_write.c | 62 |
1 files changed, 33 insertions, 29 deletions
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; |