Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

compat: factor out compat_rw_copy_check_uvector from compat_do_readv_writev

It was reported in http://lkml.org/lkml/2010/3/8/309 that 32 bit readv and
writev AIO operations were not functioning properly. It turns out that
the code to convert the 32bit io vectors to 64 bits was never written.
The results of that can be pretty bad, but in my testing, it mostly ended
up in generating EFAULT as we walked off the list of I/O vectors provided.

This patch set fixes the problem in my environment. are greatly
appreciated.

This patch:

Factor out code that will be used by both compat_do_readv_writev and the
compat aio submission code paths.

Signed-off-by: Jeff Moyer <jmoyer@redhat.com>
Reported-by: Michael Tokarev <mjt@tls.msk.ru>
Cc: Zach Brown <zach.brown@oracle.com>
Cc: <stable@kernel.org> [2.6.35.1]
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>

authored by

Jeff Moyer and committed by
Linus Torvalds
b8373363 5b530fc1

+81 -53
+77 -53
fs/compat.c
··· 568 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 644 static inline long 572 645 copy_iocb(long nr, u32 __user *ptr32, struct iocb __user * __user *ptr64) 573 646 { ··· 1150 1077 { 1151 1078 compat_ssize_t tot_len; 1152 1079 struct iovec iovstack[UIO_FASTIOV]; 1153 - struct iovec *iov=iovstack, *vector; 1080 + struct iovec *iov; 1154 1081 ssize_t ret; 1155 - int seg; 1156 1082 io_fn_t fn; 1157 1083 iov_fn_t fnv; 1158 1084 1159 - /* 1160 - * SuS says "The readv() function *may* fail if the iovcnt argument 1161 - * was less than or equal to 0, or greater than {IOV_MAX}. Linux has 1162 - * traditionally returned zero for zero segments, so... 1163 - */ 1164 - ret = 0; 1165 - if (nr_segs == 0) 1166 - goto out; 1167 - 1168 - /* 1169 - * First get the "struct iovec" from user memory and 1170 - * verify all the pointers 1171 - */ 1172 1085 ret = -EINVAL; 1173 - if ((nr_segs > UIO_MAXIOV) || (nr_segs <= 0)) 1174 - goto out; 1175 1086 if (!file->f_op) 1176 1087 goto out; 1177 - if (nr_segs > UIO_FASTIOV) { 1178 - ret = -ENOMEM; 1179 - iov = kmalloc(nr_segs*sizeof(struct iovec), GFP_KERNEL); 1180 - if (!iov) 1181 - goto out; 1182 - } 1088 + 1183 1089 ret = -EFAULT; 1184 1090 if (!access_ok(VERIFY_READ, uvector, nr_segs*sizeof(*uvector))) 1185 1091 goto out; 1186 1092 1187 - /* 1188 - * Single unix specification: 1189 - * We should -EINVAL if an element length is not >= 0 and fitting an 1190 - * ssize_t. The total length is fitting an ssize_t 1191 - * 1192 - * Be careful here because iov_len is a size_t not an ssize_t 1193 - */ 1194 - tot_len = 0; 1195 - vector = iov; 1196 - ret = -EINVAL; 1197 - for (seg = 0 ; seg < nr_segs; seg++) { 1198 - compat_ssize_t tmp = tot_len; 1199 - compat_ssize_t len; 1200 - compat_uptr_t buf; 1201 - 1202 - if (__get_user(len, &uvector->iov_len) || 1203 - __get_user(buf, &uvector->iov_base)) { 1204 - ret = -EFAULT; 1205 - goto out; 1206 - } 1207 - if (len < 0) /* size_t not fitting an compat_ssize_t .. */ 1208 - goto out; 1209 - tot_len += len; 1210 - if (tot_len < tmp) /* maths overflow on the compat_ssize_t */ 1211 - goto out; 1212 - vector->iov_base = compat_ptr(buf); 1213 - vector->iov_len = (compat_size_t) len; 1214 - uvector++; 1215 - vector++; 1216 - } 1093 + tot_len = compat_rw_copy_check_uvector(type, uvector, nr_segs, 1094 + UIO_FASTIOV, iovstack, &iov); 1217 1095 if (tot_len == 0) { 1218 1096 ret = 0; 1219 1097 goto out;
+4
include/linux/compat.h
··· 356 356 asmlinkage long compat_sys_openat(unsigned int dfd, const char __user *filename, 357 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 363 #endif /* CONFIG_COMPAT */ 360 364 #endif /* _LINUX_COMPAT_H */