diff options
| author | Christian Brauner <brauner@kernel.org> | 2026-10-29 13:25:25 +0100 |
|---|---|---|
| committer | Christian Brauner <brauner@kernel.org> | 2026-10-29 13:25:25 +0100 |
| commit | d4a952a954c81f123cd32f2ebba0c6c9fdd41a05 (patch) | |
| tree | 8b97e768a2f4990bf13f7c73e4c73c5b193d06ff | |
| parent | 0f4a9e682f7fa5becc3af7204606d9bf4780e425 (diff) | |
| parent | eb8ab8c23e71c6ae77dedc45de2a57a61558e5d5 (diff) | |
| download | linux-d4a952a954c81f123cd32f2ebba0c6c9fdd41a05.tar.gz linux-d4a952a954c81f123cd32f2ebba0c6c9fdd41a05.zip | |
Merge patch series "iomap: fixes for short copies and integrity bio sizes"
Christian Brauner <brauner@kernel.org> says:
Two iomap fixes that came out of Josef's AI bug scans.
The first one is old. For a buffered write into a hole, an unwritten
extent or past EOF __iomap_write_begin() marks the zeroed blocks
uptodate before the copy from userspace. If the copy comes up short the
uncopied part of the write stays behind in an uptodate block:
ftruncate(fd, 4096);
pwrite(fd, p, 3000, 100); /* 16 bytes at p, then PROT_NONE */
pread(fd, buf, 4096, 0);
That returns 2984 bytes of whatever was in the page before. Willy
reported this in 2024 and Zhang Yi posted a fix but it never got merged.
I went with the same fix. Josef's version zeroed the partially written
blocks completely and only marked those uptodate. That works too but
it's a lot more code for the same result.
Willy suggested back then to drop the uptodate marking from
__iomap_write_begin() completely. I didn't do that. Blocks that were
read from disk are valid and keeping them uptodate means a retried short
copy doesn't have to read them again.
The second one is from v7.1. When the filesystem handles the integrity
metadata the bio size check wraps if a single chunk is larger than
max_integrity_io_size(). That limit is capped by the maximum segment
size, so it's 64 KiB on lpfc and qla2xxx, and XFS writeback of a large
preallocated file happily builds a single 300 MiB bio and overruns the
2 MiB integrity buffer.
Note that a single chunk above the limit still gets a bio of its own.
That's fine with 4k pages. But with 64k pages a folio can be 128 MiB
which needs 4 MiB of PI with 16 bytes of metadata per 512 byte interval.
That's not new though and needs a separate fix.
Both are tested in a VM. Without the first patch the short copy
reproducer leaks on XFS with 4k and 1k blocks and on ext4 with buffered
iomap, and an fsx-like checker with faulting user buffers fails within
a few hundred operations. With it both pass. The second patch was tested
with scsi_debug DIF/DIX patched to a 64 KiB maximum segment size.
* patches from https://patch.msgid.link/20261029-work-iomap-ai-fixes-v1-0-7eda63bc1b13@kernel.org:
iomap: keep large chunks from defeating the bio cap
iomap: don't mark unwritten parts of a folio uptodate
Link: https://patch.msgid.link/20261029-work-iomap-ai-fixes-v1-0-7eda63bc1b13@kernel.org
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
| -rw-r--r-- | fs/iomap/bio.c | 2 | ||||
| -rw-r--r-- | fs/iomap/buffered-io.c | 3 | ||||
| -rw-r--r-- | fs/iomap/internal.h | 9 | ||||
| -rw-r--r-- | fs/iomap/ioend.c | 3 |
4 files changed, 13 insertions, 4 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/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; 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; |
