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

iov_iter: iterate_folioq: fix handling of offset >= folio size

It's apparently possible to get an iov advanced all the way up to the end
of the current page we're looking at, e.g.

(gdb) p *iter
$24 = {iter_type = 4 '\004', nofault = false, data_source = false, iov_offset = 4096, {__ubuf_iovec = {
iov_base = 0xffff88800f5bc000, iov_len = 655}, {{__iov = 0xffff88800f5bc000, kvec = 0xffff88800f5bc000,
bvec = 0xffff88800f5bc000, folioq = 0xffff88800f5bc000, xarray = 0xffff88800f5bc000,
ubuf = 0xffff88800f5bc000}, count = 655}}, {nr_segs = 2, folioq_slot = 2 '\002', xarray_start = 2}}

Where iov_offset is 4k with 4k-sized folios

This should have been fine because we're only in the 2nd slot and there's
another one after this, but iterate_folioq should not try to map a folio
that skips the whole size, and more importantly part here does not end up
zero (because 'PAGE_SIZE - skip % PAGE_SIZE' ends up PAGE_SIZE and not
zero..), so skip forward to the "advance to next folio" code

Link: https://lkml.kernel.org/r/20250813-iot_iter_folio-v3-0-a0ffad2b665a@codewreck.org
Link: https://lkml.kernel.org/r/20250813-iot_iter_folio-v3-1-a0ffad2b665a@codewreck.org
Signed-off-by: Dominique Martinet <asmadeus@codewreck.org>
Fixes: db0aa2e9566f ("mm: Define struct folio_queue and ITER_FOLIOQ to handle a sequence of folios")
Reported-by: Maximilian Bosch <maximilian@mbosch.me>
Reported-by: Ryan Lahfa <ryan@lahfa.xyz>
Reported-by: Christian Theune <ct@flyingcircus.io>
Reported-by: Arnout Engelen <arnout@bzzt.net>
Link: https://lkml.kernel.org/r/D4LHHUNLG79Y.12PI0X6BEHRHW@mbosch.me/
Acked-by: David Howells <dhowells@redhat.com>
Cc: Al Viro <viro@zeniv.linux.org.uk>
Cc: Christian Brauner <brauner@kernel.org>
Cc: Matthew Wilcox (Oracle) <willy@infradead.org>
Cc: <stable@vger.kernel.org> [6.12+]
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>

authored by

Dominique Martinet and committed by
Andrew Morton
808471dd 0cc2a488

+11 -9
+11 -9
include/linux/iov_iter.h
··· 160 160 161 161 do { 162 162 struct folio *folio = folioq_folio(folioq, slot); 163 - size_t part, remain, consumed; 163 + size_t part, remain = 0, consumed; 164 164 size_t fsize; 165 165 void *base; 166 166 ··· 168 168 break; 169 169 170 170 fsize = folioq_folio_size(folioq, slot); 171 - base = kmap_local_folio(folio, skip); 172 - part = umin(len, PAGE_SIZE - skip % PAGE_SIZE); 173 - remain = step(base, progress, part, priv, priv2); 174 - kunmap_local(base); 175 - consumed = part - remain; 176 - len -= consumed; 177 - progress += consumed; 178 - skip += consumed; 171 + if (skip < fsize) { 172 + base = kmap_local_folio(folio, skip); 173 + part = umin(len, PAGE_SIZE - skip % PAGE_SIZE); 174 + remain = step(base, progress, part, priv, priv2); 175 + kunmap_local(base); 176 + consumed = part - remain; 177 + len -= consumed; 178 + progress += consumed; 179 + skip += consumed; 180 + } 179 181 if (skip >= fsize) { 180 182 skip = 0; 181 183 slot++;