CVE-2008-7316: Improper Input Validation (CWE-20)
value: mm/filemap.c in the Linux kernel before 2.6.25 allows local users to cause a denial of service (infinite loop) via a writev system call that triggers an iovec of zero length, followed by a page fault for an iovec of nonzero length.
CWE description: The product receives input or data, but it does
        not validate or incorrectly validates that the input has the
        properties that are required to process the data safely and
        correctly.
CVSS v3 base score: 5.5
Published: 2016-05-02T10:59Z
Repository: https://github.com/torvalds/linux
Language: C
Files changed: mm/filemap.c
Commit date: 2008-02-03 07:55:39 +1100
Commit message: fix writev regression: pan hanging unkillable and un-straceable

Frederik Himpe reported an unkillable and un-straceable pan process.

Zero length iovecs can go into an infinite loop in writev, because the
iovec iterator does not always advance over them.

The sequence required to trigger this is not trivial. I think it
requires that a zero-length iovec be followed by a non-zero-length iovec
which causes a pagefault in the atomic usercopy. This causes the writev
code to drop back into single-segment copy mode, which then tries to
copy the 0 bytes of the zero-length iovec; a zero length copy looks like
a failure though, so it loops.

Put a test into iov_iter_advance to catch zero-length iovecs. We could
just put the test in the fallback path, but I feel it is more robust to
skip over zero-length iovecs throughout the code (iovec iterator may be
used in filesystems too, so it should be robust).

Signed-off-by: Nick Piggin <npiggin@suse.de>
Signed-off-by: Ingo Molnar <mingo@elte.hu>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>

Diff: diff --git a/mm/filemap.c b/mm/filemap.c
index 89ce6fe5f8..76bea88cbe 100644
--- a/mm/filemap.c
+++ b/mm/filemap.c
@@ -1748,11 +1748,15 @@ static void __iov_iter_advance_iov(struct iov_iter *i, size_t bytes)
 		i->iov_offset += bytes;
 	} else {
 		const struct iovec *iov = i->iov;
 		size_t base = i->iov_offset;
 
-		while (bytes) {
+		/*
+		 * The !iov->iov_len check ensures we skip over unlikely
+		 * zero-length segments.
+		 */
+		while (bytes || !iov->iov_len) {
 			int copy = min(bytes, iov->iov_len - base);
 
 			bytes -= copy;
 			base += copy;
 			if (iov->iov_len == base) {
@@ -2266,10 +2270,11 @@ static ssize_t generic_perform_write(struct file *file,
 			break;
 		copied = status;
 
 		cond_resched();
 
+		iov_iter_advance(i, copied);
 		if (unlikely(copied == 0)) {
 			/*
 			 * If we were unable to copy any data at all, we must
 			 * fall back to a single segment length write.
 			 *
@@ -2279,11 +2284,10 @@ static ssize_t generic_perform_write(struct file *file,
 			 */
 			bytes = min_t(unsigned long, PAGE_CACHE_SIZE - offset,
 						iov_iter_single_seg_count(i));
 			goto again;
 		}
-		iov_iter_advance(i, copied);
 		pos += copied;
 		written += copied;
 
 		balance_dirty_pages_ratelimited(mapping);
 

Vulnerable code: 		while (bytes) {
		iov_iter_advance(i, copied);
Fixed code: 		/*
		 * The !iov->iov_len check ensures we skip over unlikely
		 * zero-length segments.
		 */
		while (bytes || !iov->iov_len) {
		iov_iter_advance(i, copied);