aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJeff Layton <jlayton@redhat.com>2014-02-14 07:20:35 -0500
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2014-03-07 00:30:02 -0500
commit9f0afafeb7bdd6c2c2b27a93c803fff975a232be (patch)
tree16599fdfda48741fd3bef5f032b54fc0af5a9a0c
parentf14109420e3144a4d1fca374e4b37232342c9c05 (diff)
cifs: ensure that uncached writes handle unmapped areas correctly
commit 5d81de8e8667da7135d3a32a964087c0faf5483f upstream. It's possible for userland to pass down an iovec via writev() that has a bogus user pointer in it. If that happens and we're doing an uncached write, then we can end up getting less bytes than we expect from the call to iov_iter_copy_from_user. This is CVE-2014-0069 cifs_iovec_write isn't set up to handle that situation however. It'll blindly keep chugging through the page array and not filling those pages with anything useful. Worse yet, we'll later end up with a negative number in wdata->tailsz, which will confuse the sending routines and cause an oops at the very least. Fix this by having the copy phase of cifs_iovec_write stop copying data in this situation and send the last write as a short one. At the same time, we want to avoid sending a zero-length write to the server, so break out of the loop and set rc to -EFAULT if that happens. This also allows us to handle the case where no address in the iovec is valid. [Note: Marking this for stable on v3.4+ kernels, but kernels as old as v2.6.38 may have a similar problem and may need similar fix] Reviewed-by: Pavel Shilovsky <piastry@etersoft.ru> Reported-by: Al Viro <viro@zeniv.linux.org.uk> Signed-off-by: Jeff Layton <jlayton@redhat.com> Signed-off-by: Steve French <smfrench@gmail.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
-rw-r--r--fs/cifs/file.c37
1 files changed, 34 insertions, 3 deletions
diff --git a/fs/cifs/file.c b/fs/cifs/file.c
index c2934f8701da..8b0c656f2ab2 100644
--- a/fs/cifs/file.c
+++ b/fs/cifs/file.c
@@ -2353,7 +2353,7 @@ cifs_iovec_write(struct file *file, const struct iovec *iov,
2353 unsigned long nr_segs, loff_t *poffset) 2353 unsigned long nr_segs, loff_t *poffset)
2354{ 2354{
2355 unsigned long nr_pages, i; 2355 unsigned long nr_pages, i;
2356 size_t copied, len, cur_len; 2356 size_t bytes, copied, len, cur_len;
2357 ssize_t total_written = 0; 2357 ssize_t total_written = 0;
2358 loff_t offset; 2358 loff_t offset;
2359 struct iov_iter it; 2359 struct iov_iter it;
@@ -2408,14 +2408,45 @@ cifs_iovec_write(struct file *file, const struct iovec *iov,
2408 2408
2409 save_len = cur_len; 2409 save_len = cur_len;
2410 for (i = 0; i < nr_pages; i++) { 2410 for (i = 0; i < nr_pages; i++) {
2411 copied = min_t(const size_t, cur_len, PAGE_SIZE); 2411 bytes = min_t(const size_t, cur_len, PAGE_SIZE);
2412 copied = iov_iter_copy_from_user(wdata->pages[i], &it, 2412 copied = iov_iter_copy_from_user(wdata->pages[i], &it,
2413 0, copied); 2413 0, bytes);
2414 cur_len -= copied; 2414 cur_len -= copied;
2415 iov_iter_advance(&it, copied); 2415 iov_iter_advance(&it, copied);
2416 /*
2417 * If we didn't copy as much as we expected, then that
2418 * may mean we trod into an unmapped area. Stop copying
2419 * at that point. On the next pass through the big
2420 * loop, we'll likely end up getting a zero-length
2421 * write and bailing out of it.
2422 */
2423 if (copied < bytes)
2424 break;
2416 } 2425 }
2417 cur_len = save_len - cur_len; 2426 cur_len = save_len - cur_len;
2418 2427
2428 /*
2429 * If we have no data to send, then that probably means that
2430 * the copy above failed altogether. That's most likely because
2431 * the address in the iovec was bogus. Set the rc to -EFAULT,
2432 * free anything we allocated and bail out.
2433 */
2434 if (!cur_len) {
2435 for (i = 0; i < nr_pages; i++)
2436 put_page(wdata->pages[i]);
2437 kfree(wdata);
2438 rc = -EFAULT;
2439 break;
2440 }
2441
2442 /*
2443 * i + 1 now represents the number of pages we actually used in
2444 * the copy phase above. Bring nr_pages down to that, and free
2445 * any pages that we didn't use.
2446 */
2447 for ( ; nr_pages > i + 1; nr_pages--)
2448 put_page(wdata->pages[nr_pages - 1]);
2449
2419 wdata->sync_mode = WB_SYNC_ALL; 2450 wdata->sync_mode = WB_SYNC_ALL;
2420 wdata->nr_pages = nr_pages; 2451 wdata->nr_pages = nr_pages;
2421 wdata->offset = (__u64)offset; 2452 wdata->offset = (__u64)offset;