]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
btrfs: fix a bug that makes encoded write bio larger than expected
authorQu Wenruo <wqu@suse.com>
Thu, 19 Feb 2026 08:21:11 +0000 (18:51 +1030)
committerDavid Sterba <dsterba@suse.com>
Tue, 17 Mar 2026 10:43:07 +0000 (11:43 +0100)
[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 <fdmanana@suse.com>
Signed-off-by: Qu Wenruo <wqu@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
fs/btrfs/compression.c
fs/btrfs/inode.c

index 1e7174ad32e202d98a977b0d58488cb6c4f8928d..ac995ec78e0574cba957218b6b63701a7142e5fa 100644 (file)
@@ -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;
index ed4d19780f22af3d7f538975561f0a6b94b7ec97..8abfe8f0f2d4225ffd0164a779c1b2efe0730db6 100644 (file)
@@ -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;