From: Qu Wenruo Date: Thu, 19 Feb 2026 08:21:11 +0000 (+1030) Subject: btrfs: fix a bug that makes encoded write bio larger than expected X-Git-Tag: v7.0-rc5~13^2~6 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=65ee6061388b334c341fd37c22ec9149417f6ccf;p=thirdparty%2Flinux.git btrfs: fix a bug that makes encoded write bio larger than expected [BUG] When running btrfs/284 with 64K page size and 4K fs block size, the following ASSERT() can be triggered: assertion failed: cb->bbio.bio.bi_iter.bi_size == disk_num_bytes :: 0, in inode.c:9991 ------------[ cut here ]------------ kernel BUG at inode.c:9991! Internal error: Oops - BUG: 00000000f2000800 [#1] SMP CPU: 5 UID: 0 PID: 6787 Comm: btrfs Tainted: G OE 6.19.0-rc8-custom+ #1 PREEMPT(voluntary) Hardware name: QEMU KVM Virtual Machine, BIOS unknown 2/2/2022 pc : btrfs_do_encoded_write+0x9b0/0x9c0 [btrfs] lr : btrfs_do_encoded_write+0x9b0/0x9c0 [btrfs] Call trace: btrfs_do_encoded_write+0x9b0/0x9c0 [btrfs] (P) btrfs_do_write_iter+0x1d8/0x208 [btrfs] btrfs_ioctl_encoded_write+0x3c8/0x6d0 [btrfs] btrfs_ioctl+0xeb0/0x2b60 [btrfs] __arm64_sys_ioctl+0xac/0x110 invoke_syscall.constprop.0+0x64/0xe8 el0_svc_common.constprop.0+0x40/0xe8 do_el0_svc+0x24/0x38 el0_svc+0x3c/0x1b8 el0t_64_sync_handler+0xa0/0xe8 el0t_64_sync+0x1a4/0x1a8 Code: 91180021 90001080 9111a000 94039d54 (d4210000) ---[ end trace 0000000000000000 ]--- [CAUSE] After commit e1bc83f8b157 ("btrfs: get rid of compressed_folios[] usage for encoded writes"), the encoded write is changed to copy the content from the iov into a folio, and queue the folio into the compressed bio. However we always queue the full folio into the compressed bio, which can make the compressed bio larger than the on-disk extent, if the folio size is larger than the fs block size. Although we have an ASSERT() to catch such problem, for kernels without CONFIG_BTRFS_ASSERT, such larger than expected bio will just be submitted, possibly overwrite the next data extent, causing data corruption. [FIX] Instead of blindly queuing the full folio into the compressed bio, only queue the rounded up range, which is the old behavior before that offending commit. This also means we no longer need to zero the tailing range until the folio end (but still to the block boundary), as such range will not be submitted anyway. And since we're here, add a final ASSERT() into btrfs_submit_compressed_write() as the last safety net for kernels with btrfs assertions enabled Fixes: e1bc83f8b157 ("btrfs: get rid of compressed_folios[] usage for encoded writes") Reviewed-by: Filipe Manana Signed-off-by: Qu Wenruo Signed-off-by: David Sterba --- diff --git a/fs/btrfs/compression.c b/fs/btrfs/compression.c index 1e7174ad32e2..ac995ec78e05 100644 --- a/fs/btrfs/compression.c +++ b/fs/btrfs/compression.c @@ -324,6 +324,7 @@ void btrfs_submit_compressed_write(struct btrfs_ordered_extent *ordered, cb->start = ordered->file_offset; cb->len = ordered->num_bytes; + ASSERT(cb->bbio.bio.bi_iter.bi_size == ordered->disk_num_bytes); cb->compressed_len = ordered->disk_num_bytes; cb->bbio.bio.bi_iter.bi_sector = ordered->disk_bytenr >> SECTOR_SHIFT; cb->bbio.ordered = ordered; diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c index ed4d19780f22..8abfe8f0f2d4 100644 --- a/fs/btrfs/inode.c +++ b/fs/btrfs/inode.c @@ -9889,6 +9889,7 @@ ssize_t btrfs_do_encoded_write(struct kiocb *iocb, struct iov_iter *from, int compression; size_t orig_count; const u32 min_folio_size = btrfs_min_folio_size(fs_info); + const u32 blocksize = fs_info->sectorsize; u64 start, end; u64 num_bytes, ram_bytes, disk_num_bytes; struct btrfs_key ins; @@ -9999,9 +10000,9 @@ ssize_t btrfs_do_encoded_write(struct kiocb *iocb, struct iov_iter *from, ret = -EFAULT; goto out_cb; } - if (bytes < min_folio_size) - folio_zero_range(folio, bytes, min_folio_size - bytes); - ret = bio_add_folio(&cb->bbio.bio, folio, folio_size(folio), 0); + if (!IS_ALIGNED(bytes, blocksize)) + folio_zero_range(folio, bytes, round_up(bytes, blocksize) - bytes); + ret = bio_add_folio(&cb->bbio.bio, folio, round_up(bytes, blocksize), 0); if (unlikely(!ret)) { folio_put(folio); ret = -EINVAL;