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

iov_iter: Fix iov_iter_get_pages*() for folio_queue

p9_get_mapped_pages() uses iov_iter_get_pages_alloc2() to extract pages
from an iterator when performing a zero-copy request and under some
circumstances, this crashes with odd page errors[1], for example, I see:

page: refcount:0 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0xbcf0
flags: 0x2000000000000000(zone=1)
...
page dumped because: VM_BUG_ON_FOLIO(((unsigned int) folio_ref_count(folio) + 127u <= 127u))
------------[ cut here ]------------
kernel BUG at include/linux/mm.h:1444!

This is because, unlike in iov_iter_extract_folioq_pages(), the
iter_folioq_get_pages() helper function doesn't skip the current folio
when iov_offset points to the end of it, but rather extracts the next
page beyond the end of the folio and adds it to the list. Reading will
then clobber the contents of this page, leading to system corruption,
and if the page is not in use, put_page() may try to clean up the unused
page.

This can be worked around by copying the iterator before each
extraction[2] and using iov_iter_advance() on the original as the
advance function steps over the page we're at the end of.

Fix this by skipping the page extraction if we're at the end of the
folio.

This was reproduced in the ktest environment[3] by forcing 9p to use the
fscache caching mode and then reading a file through 9p.

Fixes: db0aa2e9566f ("mm: Define struct folio_queue and ITER_FOLIOQ to handle a sequence of folios")
Reported-by: Antony Antony <antony@phenome.org>
Closes: https://lore.kernel.org/r/ZxFQw4OI9rrc7UYc@Antony2201.local/
Signed-off-by: David Howells <dhowells@redhat.com>
cc: Eric Van Hensbergen <ericvh@kernel.org>
cc: Latchesar Ionkov <lucho@ionkov.net>
cc: Dominique Martinet <asmadeus@codewreck.org>
cc: Christian Schoenebeck <linux_oss@crudebyte.com>
cc: v9fs@lists.linux.dev
cc: netfs@lists.linux.dev
cc: linux-fsdevel@vger.kernel.org
Link: https://lore.kernel.org/r/ZxFEi1Tod43pD6JC@moon.secunet.de/ [1]
Link: https://lore.kernel.org/r/2299159.1729543103@warthog.procyon.org.uk/ [2]
Link: https://github.com/koverstreet/ktest.git [3]
Tested-by: Antony Antony <antony.antony@secunet.com>
Link: https://lore.kernel.org/r/3327438.1729678025@warthog.procyon.org.uk
Signed-off-by: Christian Brauner <brauner@kernel.org>

authored by

David Howells and committed by
Christian Brauner
e65a0dc1 247d65fb

+11 -8
+11 -8
lib/iov_iter.c
··· 1021 1021 size_t offset = iov_offset, fsize = folioq_folio_size(folioq, slot); 1022 1022 size_t part = PAGE_SIZE - offset % PAGE_SIZE; 1023 1023 1024 - part = umin(part, umin(maxsize - extracted, fsize - offset)); 1025 - count -= part; 1026 - iov_offset += part; 1027 - extracted += part; 1024 + if (offset < fsize) { 1025 + part = umin(part, umin(maxsize - extracted, fsize - offset)); 1026 + count -= part; 1027 + iov_offset += part; 1028 + extracted += part; 1028 1029 1029 - *pages = folio_page(folio, offset / PAGE_SIZE); 1030 - get_page(*pages); 1031 - pages++; 1032 - maxpages--; 1030 + *pages = folio_page(folio, offset / PAGE_SIZE); 1031 + get_page(*pages); 1032 + pages++; 1033 + maxpages--; 1034 + } 1035 + 1033 1036 if (maxpages == 0 || extracted >= maxsize) 1034 1037 break; 1035 1038