diff options
-rw-r--r-- | fs/compat.c | 130 | ||||
-rw-r--r-- | include/linux/compat.h | 4 |
2 files changed, 81 insertions, 53 deletions
diff --git a/fs/compat.c b/fs/compat.c index 05448730f840..7b4aabb3bb36 100644 --- a/fs/compat.c +++ b/fs/compat.c | |||
@@ -568,6 +568,79 @@ out: | |||
568 | return ret; | 568 | return ret; |
569 | } | 569 | } |
570 | 570 | ||
571 | /* A write operation does a read from user space and vice versa */ | ||
572 | #define vrfy_dir(type) ((type) == READ ? VERIFY_WRITE : VERIFY_READ) | ||
573 | |||
574 | ssize_t compat_rw_copy_check_uvector(int type, | ||
575 | const struct compat_iovec __user *uvector, unsigned long nr_segs, | ||
576 | unsigned long fast_segs, struct iovec *fast_pointer, | ||
577 | struct iovec **ret_pointer) | ||
578 | { | ||
579 | compat_ssize_t tot_len; | ||
580 | struct iovec *iov = *ret_pointer = fast_pointer; | ||
581 | ssize_t ret = 0; | ||
582 | int seg; | ||
583 | |||
584 | /* | ||
585 | * SuS says "The readv() function *may* fail if the iovcnt argument | ||
586 | * was less than or equal to 0, or greater than {IOV_MAX}. Linux has | ||
587 | * traditionally returned zero for zero segments, so... | ||
588 | */ | ||
589 | if (nr_segs == 0) | ||
590 | goto out; | ||
591 | |||
592 | ret = -EINVAL; | ||
593 | if (nr_segs > UIO_MAXIOV || nr_segs < 0) | ||
594 | goto out; | ||
595 | if (nr_segs > fast_segs) { | ||
596 | ret = -ENOMEM; | ||
597 | iov = kmalloc(nr_segs*sizeof(struct iovec), GFP_KERNEL); | ||
598 | if (iov == NULL) { | ||
599 | *ret_pointer = fast_pointer; | ||
600 | goto out; | ||
601 | } | ||
602 | } | ||
603 | *ret_pointer = iov; | ||
604 | |||
605 | /* | ||
606 | * Single unix specification: | ||
607 | * We should -EINVAL if an element length is not >= 0 and fitting an | ||
608 | * ssize_t. The total length is fitting an ssize_t | ||
609 | * | ||
610 | * Be careful here because iov_len is a size_t not an ssize_t | ||
611 | */ | ||
612 | tot_len = 0; | ||
613 | ret = -EINVAL; | ||
614 | for (seg = 0; seg < nr_segs; seg++) { | ||
615 | compat_ssize_t tmp = tot_len; | ||
616 | compat_uptr_t buf; | ||
617 | compat_ssize_t len; | ||
618 | |||
619 | if (__get_user(len, &uvector->iov_len) || | ||
620 | __get_user(buf, &uvector->iov_base)) { | ||
621 | ret = -EFAULT; | ||
622 | goto out; | ||
623 | } | ||
624 | if (len < 0) /* size_t not fitting in compat_ssize_t .. */ | ||
625 | goto out; | ||
626 | tot_len += len; | ||
627 | if (tot_len < tmp) /* maths overflow on the compat_ssize_t */ | ||
628 | goto out; | ||
629 | if (!access_ok(vrfy_dir(type), buf, len)) { | ||
630 | ret = -EFAULT; | ||
631 | goto out; | ||
632 | } | ||
633 | iov->iov_base = compat_ptr(buf); | ||
634 | iov->iov_len = (compat_size_t) len; | ||
635 | uvector++; | ||
636 | iov++; | ||
637 | } | ||
638 | ret = tot_len; | ||
639 | |||
640 | out: | ||
641 | return ret; | ||
642 | } | ||
643 | |||
571 | static inline long | 644 | static inline long |
572 | copy_iocb(long nr, u32 __user *ptr32, struct iocb __user * __user *ptr64) | 645 | copy_iocb(long nr, u32 __user *ptr32, struct iocb __user * __user *ptr64) |
573 | { | 646 | { |
@@ -1077,70 +1150,21 @@ static ssize_t compat_do_readv_writev(int type, struct file *file, | |||
1077 | { | 1150 | { |
1078 | compat_ssize_t tot_len; | 1151 | compat_ssize_t tot_len; |
1079 | struct iovec iovstack[UIO_FASTIOV]; | 1152 | struct iovec iovstack[UIO_FASTIOV]; |
1080 | struct iovec *iov=iovstack, *vector; | 1153 | struct iovec *iov; |
1081 | ssize_t ret; | 1154 | ssize_t ret; |
1082 | int seg; | ||
1083 | io_fn_t fn; | 1155 | io_fn_t fn; |
1084 | iov_fn_t fnv; | 1156 | iov_fn_t fnv; |
1085 | 1157 | ||
1086 | /* | ||
1087 | * SuS says "The readv() function *may* fail if the iovcnt argument | ||
1088 | * was less than or equal to 0, or greater than {IOV_MAX}. Linux has | ||
1089 | * traditionally returned zero for zero segments, so... | ||
1090 | */ | ||
1091 | ret = 0; | ||
1092 | if (nr_segs == 0) | ||
1093 | goto out; | ||
1094 | |||
1095 | /* | ||
1096 | * First get the "struct iovec" from user memory and | ||
1097 | * verify all the pointers | ||
1098 | */ | ||
1099 | ret = -EINVAL; | 1158 | ret = -EINVAL; |
1100 | if ((nr_segs > UIO_MAXIOV) || (nr_segs <= 0)) | ||
1101 | goto out; | ||
1102 | if (!file->f_op) | 1159 | if (!file->f_op) |
1103 | goto out; | 1160 | goto out; |
1104 | if (nr_segs > UIO_FASTIOV) { | 1161 | |
1105 | ret = -ENOMEM; | ||
1106 | iov = kmalloc(nr_segs*sizeof(struct iovec), GFP_KERNEL); | ||
1107 | if (!iov) | ||
1108 | goto out; | ||
1109 | } | ||
1110 | ret = -EFAULT; | 1162 | ret = -EFAULT; |
1111 | if (!access_ok(VERIFY_READ, uvector, nr_segs*sizeof(*uvector))) | 1163 | if (!access_ok(VERIFY_READ, uvector, nr_segs*sizeof(*uvector))) |
1112 | goto out; | 1164 | goto out; |
1113 | 1165 | ||
1114 | /* | 1166 | tot_len = compat_rw_copy_check_uvector(type, uvector, nr_segs, |
1115 | * Single unix specification: | 1167 | UIO_FASTIOV, iovstack, &iov); |
1116 | * We should -EINVAL if an element length is not >= 0 and fitting an | ||
1117 | * ssize_t. The total length is fitting an ssize_t | ||
1118 | * | ||
1119 | * Be careful here because iov_len is a size_t not an ssize_t | ||
1120 | */ | ||
1121 | tot_len = 0; | ||
1122 | vector = iov; | ||
1123 | ret = -EINVAL; | ||
1124 | for (seg = 0 ; seg < nr_segs; seg++) { | ||
1125 | compat_ssize_t tmp = tot_len; | ||
1126 | compat_ssize_t len; | ||
1127 | compat_uptr_t buf; | ||
1128 | |||
1129 | if (__get_user(len, &uvector->iov_len) || | ||
1130 | __get_user(buf, &uvector->iov_base)) { | ||
1131 | ret = -EFAULT; | ||
1132 | goto out; | ||
1133 | } | ||
1134 | if (len < 0) /* size_t not fitting an compat_ssize_t .. */ | ||
1135 | goto out; | ||
1136 | tot_len += len; | ||
1137 | if (tot_len < tmp) /* maths overflow on the compat_ssize_t */ | ||
1138 | goto out; | ||
1139 | vector->iov_base = compat_ptr(buf); | ||
1140 | vector->iov_len = (compat_size_t) len; | ||
1141 | uvector++; | ||
1142 | vector++; | ||
1143 | } | ||
1144 | if (tot_len == 0) { | 1168 | if (tot_len == 0) { |
1145 | ret = 0; | 1169 | ret = 0; |
1146 | goto out; | 1170 | goto out; |
diff --git a/include/linux/compat.h b/include/linux/compat.h index 717c691ecd8e..168f7daa7bde 100644 --- a/include/linux/compat.h +++ b/include/linux/compat.h | |||
@@ -356,5 +356,9 @@ asmlinkage long compat_sys_newfstatat(unsigned int dfd, char __user * filename, | |||
356 | asmlinkage long compat_sys_openat(unsigned int dfd, const char __user *filename, | 356 | asmlinkage long compat_sys_openat(unsigned int dfd, const char __user *filename, |
357 | int flags, int mode); | 357 | int flags, int mode); |
358 | 358 | ||
359 | extern ssize_t compat_rw_copy_check_uvector(int type, | ||
360 | const struct compat_iovec __user *uvector, unsigned long nr_segs, | ||
361 | unsigned long fast_segs, struct iovec *fast_pointer, | ||
362 | struct iovec **ret_pointer); | ||
359 | #endif /* CONFIG_COMPAT */ | 363 | #endif /* CONFIG_COMPAT */ |
360 | #endif /* _LINUX_COMPAT_H */ | 364 | #endif /* _LINUX_COMPAT_H */ |