From a4193b4af359db84b2cdf3a040752861984c636b Mon Sep 17 00:00:00 2001 From: Christian Brauner Date: Thu, 29 Oct 2026 11:34:48 +0100 Subject: [PATCH 1/2] iomap: don't mark unwritten parts of a folio uptodate For a buffered write into a hole, an unwritten or new extent, or past EOF __iomap_write_begin() zeroes the parts of the blocks outside of the write and then marks the blocks uptodate. That includes the range of the write itself which still holds whatever was in the folio before. It only becomes valid once the copy from userspace has filled it in. But the copy can come up short and __iomap_write_end() only rejects a short copy if the folio isn't uptodate. If the zeroed blocks were all that was missing the folio is uptodate by then, the short copy is accepted, and the rest of the range is exposed to readers and can end up on disk. A source buffer that ends in an unmapped page is enough: ftruncate(fd, 4096); pwrite(fd, p, 3000, 100); /* 16 bytes at p, then PROT_NONE */ pread(fd, buf, 4096, 0); The write returns 16 and the read returns 2984 bytes of whatever was in that page before. If other blocks of the folio aren't uptodate the short copy is rejected but the zeroed blocks keep their uptodate bits and the retry with the copied length leaves the same bytes behind. Only mark blocks uptodate in __iomap_write_begin() after reading them. __iomap_write_end() marks the zeroed blocks uptodate once the copy has succeeded and rejects a short copy. Willy reported this back in 2024 (cf. [1]) and Zhang Yi posted the same fix (cf. [2]) but it never got merged. [1]: https://lore.kernel.org/all/ZqsN5ouQTEc1KAzV@casper.infradead.org [2]: https://lore.kernel.org/all/20240812121159.3775074-6-yi.zhang@huaweicloud.com Fixes: 9dc55f1389f9 ("iomap: add support for sub-pagesize buffered I/O without buffer heads") Reported-by: Josef Bacik Cc: stable@vger.kernel.org Link: https://patch.msgid.link/20261029-work-iomap-ai-fixes-v1-1-7eda63bc1b13@kernel.org Signed-off-by: Christian Brauner (Amutable) --- fs/iomap/buffered-io.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fs/iomap/buffered-io.c b/fs/iomap/buffered-io.c index 678f4c5329e7..a6ab6f31cbbf 100644 --- a/fs/iomap/buffered-io.c +++ b/fs/iomap/buffered-io.c @@ -920,6 +920,7 @@ static int __iomap_write_begin(const struct iomap_iter *iter, if (iomap_block_needs_zeroing(iter, block_start)) { if (WARN_ON_ONCE(iter->flags & IOMAP_UNSHARE)) return -EIO; + /* uptodate once copied, see __iomap_write_end() */ folio_zero_segments(folio, poff, from, to, poff + plen); } else { const struct iomap *iomap = iomap_iter_srcmap(iter); @@ -943,8 +944,8 @@ static int __iomap_write_begin(const struct iomap_iter *iter, if (iomap->flags & IOMAP_F_ZERO_TAIL) folio_zero_segment(folio, to, poff + plen); + iomap_set_range_uptodate(folio, poff, plen); } - iomap_set_range_uptodate(folio, poff, plen); } while ((block_start += plen) < block_end); return 0; From eb8ab8c23e71c6ae77dedc45de2a57a61558e5d5 Mon Sep 17 00:00:00 2001 From: Josef Bacik Date: Thu, 29 Oct 2026 11:34:49 +0100 Subject: [PATCH 2/2] iomap: keep large chunks from defeating the bio cap For integrity I/O a bio must not grow beyond max_integrity_io_size() because the integrity buffer is a single allocation of at most 2 MiB. Writeback and buffered reads check that before adding to a bio: bio->bi_iter.bi_size > iomap_max_bio_size(iomap) - len The subtraction is done in size_t and wraps if len alone is above the limit. The check then never fires and the bio grows until it runs out of bio_vecs. That's easy to hit. The limit is also capped by the maximum segment size which is 64 KiB for SCSI hosts that don't set it, such as lpfc and qla2xxx, and XFS uses folios of up to 2 MiB. Writeback of a preallocated 300 MiB file then builds a single bio that needs 4.7 MiB of PI with 512 byte sectors and 8 byte tuples. That's more than kmalloc() can do, so bio_integrity_alloc_buf() falls back to a 2 MiB mempool element and generating the PI writes 2.7 MiB past its end. Check the length on its own first. A chunk above the limit still gets a bio of its own which is never larger than a folio. Fixes: 0b10a370529c ("iomap: support T10 protection information") Cc: stable@vger.kernel.org # v7.1+ Signed-off-by: Josef Bacik Link: https://patch.msgid.link/20261029-work-iomap-ai-fixes-v1-2-7eda63bc1b13@kernel.org Reviewed-by: Christoph Hellwig Signed-off-by: Christian Brauner (Amutable) --- fs/iomap/bio.c | 2 +- fs/iomap/internal.h | 9 +++++++++ fs/iomap/ioend.c | 3 +-- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/fs/iomap/bio.c b/fs/iomap/bio.c index 153b779949e9..256564ebe238 100644 --- a/fs/iomap/bio.c +++ b/fs/iomap/bio.c @@ -109,7 +109,7 @@ int iomap_bio_read_folio_range(const struct iomap_iter *iter, if (!bio || bio_end_sector(bio) != iomap_sector(&iter->iomap, iter->pos) || - bio->bi_iter.bi_size > iomap_max_bio_size(&iter->iomap) - plen || + !iomap_bio_has_room(&iter->iomap, bio, plen) || !bio_add_folio(bio, folio, plen, offset_in_folio(folio, iter->pos))) iomap_read_alloc_bio(iter, ctx, plen); return 0; diff --git a/fs/iomap/internal.h b/fs/iomap/internal.h index 74e898b196dc..e69b3ac02cc2 100644 --- a/fs/iomap/internal.h +++ b/fs/iomap/internal.h @@ -17,6 +17,15 @@ static inline size_t iomap_max_bio_size(const struct iomap *iomap) return BIO_MAX_SIZE; } +/* Can @len more bytes go into @bio? */ +static inline bool iomap_bio_has_room(const struct iomap *iomap, + const struct bio *bio, size_t len) +{ + size_t max_size = iomap_max_bio_size(iomap); + + return len <= max_size && bio->bi_iter.bi_size <= max_size - len; +} + u32 iomap_finish_ioend_buffered_read(struct iomap_ioend *ioend); u32 iomap_finish_ioend_direct(struct iomap_ioend *ioend); diff --git a/fs/iomap/ioend.c b/fs/iomap/ioend.c index 92b89fbf95b9..4da248ff99f8 100644 --- a/fs/iomap/ioend.c +++ b/fs/iomap/ioend.c @@ -133,8 +133,7 @@ static bool iomap_can_add_to_ioend(struct iomap_writepage_ctx *wpc, loff_t pos, { struct iomap_ioend *ioend = wpc->wb_ctx; - if (ioend->io_bio.bi_iter.bi_size > - iomap_max_bio_size(&wpc->iomap) - map_len) + if (!iomap_bio_has_room(&wpc->iomap, &ioend->io_bio, map_len)) return false; if (ioend_flags & IOMAP_IOEND_BOUNDARY) return false;