From 7f03a417fc75fbe31a06199d8b609b8f313b5301 Mon Sep 17 00:00:00 2001 From: Qu Wenruo Date: Mon, 20 Jul 2026 19:19:41 +0930 Subject: [PATCH 1/5] btrfs: disable large folios for systems with highmem [BUG] There is a bug report that on 32bit systems (i686), btrfs crashes when trying to do zstd compression: BUG: unable to handle page fault for address: fffbc000 #PF: supervisor read access in kernel mode #PF: error_code(0x0000) - not-present page CPU: 0 UID: 0 PID: 61 Comm: kworker/u8:5 Tainted: G N 7.2.0-rc3-P3 #2 PREEMPTLAZY Hardware name: LENOVO 2007F2G/2007F2G, BIOS 79ETE7WW (2.27 ) 03/21/2011 Workqueue: btrfs-delalloc btrfs_work_helper EIP: ZSTD_compressStream2+0x221/0x5fc Call Trace: ZSTD_compressStream+0xd/0x48 zstd_compress_stream+0x8/0x10 zstd_compress_bio+0x20a/0x564 btrfs_compress_bio+0x94/0xc0 compress_file_range+0x20a/0x380 btrfs_work_helper+0xc1/0x1b4 process_scheduled_works+0x15f/0x204 worker_thread+0x10c/0x178 kthread+0xe1/0xe8 ret_from_fork+0x1d/0x14c ret_from_fork_asm+0x12/0x18 entry_INT80_32+0xf0/0xf0 CR2: 00000000fffbc000 ---[ end trace 0000000000000000 ]--- [CAUSE] Inside zstd_compress_bio(), we assume the whole page cache folio can be mapped in one go. However that assumption is not true on systems with CONFIG_HIGHMEM, the pages of the large folio can be in HIGHMEM, which needs to be mapped before access. Meanwhile zstd_compress_bio() only map the page of a large folio where the start filepos is, the remaining pages are not mapped, and accessing the remaining pages will trigger the above crash. [FIX] Do not enable large folios when the kernel has CONFIG_HIGHMEM enabled. This is the same handling for bs > ps support. Link: https://github.com/kdave/btrfs-progs/issues/1146 Reported-by: Erhard Furtner Fixes: 9bce95edb1b4 ("btrfs: move large data folios out of experimental features") Reviewed-by: Jeff Layton Signed-off-by: Qu Wenruo Reviewed-by: David Sterba Signed-off-by: David Sterba --- fs/btrfs/disk-io.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c index 36332df9a0f1..87385b97c81b 100644 --- a/fs/btrfs/disk-io.c +++ b/fs/btrfs/disk-io.c @@ -3468,7 +3468,15 @@ int __cold open_ctree(struct super_block *sb, struct btrfs_fs_devices *fs_device fs_info->sectorsize = sectorsize; fs_info->sectorsize_bits = ilog2(sectorsize); fs_info->block_min_order = ilog2(round_up(sectorsize, PAGE_SIZE) >> PAGE_SHIFT); - fs_info->block_max_order = calc_block_max_order(fs_info->sectorsize_bits); + /* + * For HIGHMEM, a large folio cannot be mapped in one go, breaking a lot + * of basic assumptions for btrfs IOs. + * Disable large folios for such 32-bit systems. + */ + if (IS_ENABLED(CONFIG_HIGHMEM)) + fs_info->block_max_order = fs_info->block_min_order; + else + fs_info->block_max_order = calc_block_max_order(fs_info->sectorsize_bits); fs_info->csums_per_leaf = BTRFS_MAX_ITEM_SIZE(fs_info) / fs_info->csum_size; fs_info->stripesize = stripesize; fs_info->fs_devices->fs_info = fs_info; From 0fa78ef637deb5dbe341582f88553a4bce496de0 Mon Sep 17 00:00:00 2001 From: David Lee Date: Wed, 15 Jul 2026 09:05:14 +0000 Subject: [PATCH 2/5] btrfs: lzo: reject inline extents without valid headers [BUG] For a crafted btrfs image, the following KASAN can be triggered when reading an inline lzo compressed file extent: BUG: KASAN: slab-out-of-bounds in lzo_decompress+0x57d/0x700 Read of size 4 at addr ffff888006f2e644 by task btrfs_lzo_inlin/77 Call Trace: dump_stack_lvl+0x5b/0x70 print_report+0xd1/0x610 kasan_report+0xe0/0x110 __asan_report_load_n_noabort+0x13/0x20 lzo_decompress+0x57d/0x700 btrfs_decompress+0x140/0x1c0 uncompress_inline+0x147/0x1b0 btrfs_get_extent+0xb23/0x10a0 btrfs_do_readpage.constprop.0+0x538/0x1ac0 btrfs_readahead+0x32f/0x5f0 read_pages+0x16f/0x850 page_cache_ra_unbounded+0x296/0x490 do_page_cache_ra+0xd9/0x130 page_cache_sync_ra+0x3ee/0x6f0 filemap_get_pages+0x306/0x15c0 filemap_read+0x329/0xd00 btrfs_file_read_iter+0x1f8/0x2b0 vfs_read+0x4ef/0x720 ksys_read+0xf8/0x1d0 __x64_sys_read+0x71/0xb0 x64_sys_call+0x1ab0/0x1b70 do_syscall_64+0x61/0x470 entry_SYSCALL_64_after_hwframe+0x4b/0x53 [CAUSE] For an inline lzo compressed file extent, there should always be one lzo header, recording the total length of the compressed data, followed by one segment header, recording the compressed lzo payload. But if a crafted inline lzo compressed file extent contains only an lzo header, without the segment header or payload, lzo_decompress() will still try to read the segment header, causing a read beyond the item boundary. Furthermore if the inline lzo compressed file extent is the first item of the leaf, it will be at the extent buffer boundary. The above out-of-boundary read will go beyond the extent buffer boundary, triggering the above KASAN report. [FIX] Validate the total length of the inlined lzo compressed file extent, to make sure there is at least one LZO header and one segment header, and a non-zero payload. Fixes: a6fa6fae40ec ("btrfs: Add lzo compression support") Assisted-by: Codex:gpt-5.5 Signed-off-by: David Lee [ Rework the commit message to remove slop ] Signed-off-by: Qu Wenruo Signed-off-by: David Sterba --- fs/btrfs/lzo.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/fs/btrfs/lzo.c b/fs/btrfs/lzo.c index 1531adb117d1..2f0996692da0 100644 --- a/fs/btrfs/lzo.c +++ b/fs/btrfs/lzo.c @@ -552,9 +552,10 @@ int lzo_decompress(struct list_head *ws, const u8 *data_in, size_t max_segment_len = workspace_buf_length(fs_info); int ret; - if (unlikely(srclen < LZO_LEN || srclen > max_segment_len + LZO_LEN * 2)) { + if (unlikely(srclen <= LZO_LEN * 2 || + srclen > max_segment_len + LZO_LEN * 2)) { btrfs_err(fs_info, "invalid lzo header length, has %zu expect (%u, %zu)", - srclen, LZO_LEN, max_segment_len + LZO_LEN * 2); + srclen, LZO_LEN * 2, max_segment_len + LZO_LEN * 2); return -EUCLEAN; } From d2a4e4e626b2f4670b69b430c357f03f53eb6632 Mon Sep 17 00:00:00 2001 From: Dmitry Antipov Date: Mon, 27 Jul 2026 14:53:52 +0300 Subject: [PATCH 3/5] btrfs: fix memory leak in btrfs_do_encoded_write() Local fuzzing of 6.12.94 has found the following memory leak: Unreferenced object 0xffff888018050a80 (size 64): comm "syz.0.17", pid 10297, jiffies 4294953601 hex dump (first 32 bytes): 00 10 00 00 00 00 00 00 01 00 00 00 00 00 00 00 ................ 10 0a 05 18 80 88 ff ff 10 0a 05 18 80 88 ff ff ................ backtrace (crc a8a6fc29): kmemleak_alloc_recursive include/linux/kmemleak.h:42 [inline] slab_post_alloc_hook mm/slub.c:4152 [inline] slab_alloc_node mm/slub.c:4197 [inline] __kmalloc_cache_noprof+0x168/0x2c0 mm/slub.c:4358 kmalloc_noprof include/linux/slab.h:878 [inline] extent_changeset_alloc fs/btrfs/extent_io.h:207 [inline] qgroup_reserve_data+0x1c5/0x7d0 fs/btrfs/qgroup.c:4305 btrfs_qgroup_reserve_data+0x2e/0xb0 fs/btrfs/qgroup.c:4355 btrfs_do_encoded_write+0x92e/0x1040 fs/btrfs/inode.c:9746 btrfs_encoded_write fs/btrfs/file.c:1482 [inline] btrfs_do_write_iter+0x280/0x610 fs/btrfs/file.c:1507 btrfs_ioctl_encoded_write+0x3d6/0x490 fs/btrfs/ioctl.c:4738 btrfs_ioctl+0x6f9/0xc90 fs/btrfs/ioctl.c:-1 vfs_ioctl fs/ioctl.c:51 [inline] __do_sys_ioctl fs/ioctl.c:906 [inline] __se_sys_ioctl+0xf9/0x170 fs/ioctl.c:892 do_syscall_x64 arch/x86/entry/common.c:47 [inline] do_syscall_64+0xbe/0x1a0 arch/x86/entry/common.c:78 entry_SYSCALL_64_after_hwframe+0x77/0x7f Unreferenced object 0xffff888018050a00 (size 64): comm "syz.0.17", pid 10297, jiffies 4294953601 hex dump (first 32 bytes): 00 00 00 00 00 00 00 00 ff 0f 00 00 00 00 00 00 ................ 90 0a 05 18 80 88 ff ff 90 0a 05 18 80 88 ff ff ................ backtrace (crc cb5c9580): kmemleak_alloc_recursive include/linux/kmemleak.h:42 [inline] slab_post_alloc_hook mm/slub.c:4152 [inline] slab_alloc_node mm/slub.c:4197 [inline] __kmalloc_cache_noprof+0x168/0x2c0 mm/slub.c:4358 kmalloc_noprof include/linux/slab.h:878 [inline] kzalloc_noprof include/linux/slab.h:1014 [inline] ulist_prealloc+0x9c/0x110 fs/btrfs/ulist.c:114 extent_changeset_prealloc fs/btrfs/extent_io.h:217 [inline] __set_extent_bit+0x16b/0x1a70 fs/btrfs/extent-io-tree.c:1086 set_record_extent_bits+0x50/0x90 fs/btrfs/extent-io-tree.c:1821 qgroup_reserve_data+0x274/0x7d0 fs/btrfs/qgroup.c:4312 btrfs_qgroup_reserve_data+0x2e/0xb0 fs/btrfs/qgroup.c:4355 btrfs_do_encoded_write+0x92e/0x1040 fs/btrfs/inode.c:9746 btrfs_encoded_write fs/btrfs/file.c:1482 [inline] btrfs_do_write_iter+0x280/0x610 fs/btrfs/file.c:1507 btrfs_ioctl_encoded_write+0x3d6/0x490 fs/btrfs/ioctl.c:4738 btrfs_ioctl+0x6f9/0xc90 fs/btrfs/ioctl.c:-1 vfs_ioctl fs/ioctl.c:51 [inline] __do_sys_ioctl fs/ioctl.c:906 [inline] __se_sys_ioctl+0xf9/0x170 fs/ioctl.c:892 do_syscall_x64 arch/x86/entry/common.c:47 [inline] do_syscall_64+0xbe/0x1a0 arch/x86/entry/common.c:78 entry_SYSCALL_64_after_hwframe+0x77/0x7f Fix this by freeing an extent changeset before returning from btrfs_do_encoded_write(). Fixes: 7c0c7269f7b5 ("btrfs: add BTRFS_IOC_ENCODED_WRITE") Reviewed-by: Filipe Manana Signed-off-by: Dmitry Antipov Signed-off-by: Filipe Manana Reviewed-by: David Sterba Signed-off-by: David Sterba --- fs/btrfs/inode.c | 1 + 1 file changed, 1 insertion(+) diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c index 04ea10b61bbb..a443b8e6824d 100644 --- a/fs/btrfs/inode.c +++ b/fs/btrfs/inode.c @@ -9954,6 +9954,7 @@ ssize_t btrfs_do_encoded_write(struct kiocb *iocb, struct iov_iter *from, if (cb) cleanup_compressed_bio(cb); out: + extent_changeset_free(data_reserved); if (ret >= 0) iocb->ki_pos += encoded->len; return ret; From 4c375ac546ea667e619ab77d34cca6edcab7c448 Mon Sep 17 00:00:00 2001 From: Qu Wenruo Date: Thu, 30 Jul 2026 16:37:39 +0930 Subject: [PATCH 4/5] btrfs: disable bs > ps support if no transparent hugepage support Btrfs relies on mapping_set_folio_order_range() to set the minimal folio order for all its data inodes, but that function will be no-op if transparent hugepage is not enabled. Guard the bs > ps support behind CONFIG_TRANSPARENT_HUGEPAGE, just like all other filesystems. Fixes: 98077f7f2180 ("btrfs: enable experimental bs > ps support") Reviewed-by: Filipe Manana Signed-off-by: Qu Wenruo Signed-off-by: David Sterba --- fs/btrfs/Kconfig | 3 ++- fs/btrfs/fs.c | 12 ++++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/fs/btrfs/Kconfig b/fs/btrfs/Kconfig index 9de04c37e11a..4b10d78ed99b 100644 --- a/fs/btrfs/Kconfig +++ b/fs/btrfs/Kconfig @@ -106,7 +106,8 @@ config BTRFS_EXPERIMENTAL - extent tree v2 - complex rework of extent tracking - - block size > page size support + - block size > page size support - needs transparent huge page and + non-HIGHMEM system - huge folios for data - folios can be as large as 2MiB now diff --git a/fs/btrfs/fs.c b/fs/btrfs/fs.c index 14d83565cdee..dcf12979af33 100644 --- a/fs/btrfs/fs.c +++ b/fs/btrfs/fs.c @@ -166,9 +166,17 @@ bool __attribute_const__ btrfs_supported_blocksize(u32 blocksize) * * Considering HIGHMEM is such a pain to deal with and it's going * to be deprecated eventually, just reject HIGHMEM && bs > ps cases. + * + * Finally, for bs > ps cases, we need to set the minimal folio order, + * which requires transparent hugepage. */ - if (IS_ENABLED(CONFIG_HIGHMEM) && blocksize > PAGE_SIZE) - return false; + if (blocksize > PAGE_SIZE) { + if (IS_ENABLED(CONFIG_HIGHMEM)) + return false; + + if (!IS_ENABLED(CONFIG_TRANSPARENT_HUGEPAGE)) + return false; + } return true; #endif return false; From 0ef349734a93227b45f65fc50a3311d1cc5f03e9 Mon Sep 17 00:00:00 2001 From: Qu Wenruo Date: Fri, 31 Jul 2026 10:14:49 +0930 Subject: [PATCH 5/5] btrfs: initialize inode mapping flags for cached inodes [BUG] When running generic/795 with 8K block size, 4K page size, the test always fails, triggering some ASSERT()s related to folio size: 795 (241074): drop_caches: 3 assertion failed: IS_ALIGNED(start, blocksize) && IS_ALIGNED(end + 1, blocksize), in extent_io.c:1404 (blocksize=8192 root=262 ino=258 start=16826368 end=16830463 mapping min order=0) ------------[ cut here ]------------ kernel BUG at extent_io.c:1404! Oops: invalid opcode: 0000 [#1] SMP CPU: 8 UID: 0 PID: 241105 Comm: fsstress Tainted: G OE 7.2.0-rc5-custom+ #442 PREEMPT(full) f4bfb352566f3949f29c233ce6f735050a03b245 Tainted: [O]=OOT_MODULE, [E]=UNSIGNED_MODULE Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS unknown 02/02/2022 RIP: 0010:assert_folio_range.cold+0x3d/0x3f [btrfs] Call Trace: btrfs_read_folio+0x9e/0x170 [btrfs 4cd1dd93b341b8ef766643f9512f4a86259567a3] prepare_one_folio.constprop.0+0x104/0x2a0 [btrfs 4cd1dd93b341b8ef766643f9512f4a86259567a3] btrfs_buffered_write+0x285/0xa50 [btrfs 4cd1dd93b341b8ef766643f9512f4a86259567a3] btrfs_do_write_iter+0x1aa/0x210 [btrfs 4cd1dd93b341b8ef766643f9512f4a86259567a3] iter_file_splice_write+0x31a/0x540 direct_splice_actor+0x53/0x170 splice_direct_to_actor+0xe9/0x240 do_splice_direct+0x76/0xb0 vfs_copy_file_range+0x1fd/0x630 __x64_sys_copy_file_range+0xf9/0x220 do_syscall_64+0xe1/0x790 entry_SYSCALL_64_after_hwframe+0x4b/0x53 ---[ end trace 0000000000000000 ]--- The ASSERT() itself is added by a later patch. The crash is triggered with that new debug patch, and without this fix. [CAUSE] In the above case, the start 16826368 is properly 8K aligned, but the end (16830463 + 1) is not 8K aligned. Furthermore the mapping's minimal folio order is 0, not the expected 1 for 8K block size with 4K page size. So this means some inodes do not have btrfs_set_inode_mapping_order() called on it. The missing btrfs_set_inode_mapping_order() call happens for cached inodes, through the following events: - btrfs_create_new_inode() called for inode X Which properly sets minimal folio order for the VFS inode. - btrfs_update_inode() called for inode X Which calls btrfs_delayed_update_inode() to create a delayed_node into root->delayed_nodes xarray. - Drop cache/memory pressure, evicting in-memory inode X Which evicted the inode X, but delayed_node is still in root->delayed_nodes for future reuse. - btrfs_iget() for inode X called again btrfs_iget() |- btrfs_iget_locked() | |- iget5_locked_rcu() | Which creates a new vfs_inode for btrfs, whose mapping still | has the minimal order as 0. | |- btrfs_read_locked_inode() |- btrfs_fill_inode() | |- btrfs_get_delayed_node() | Which found out the previous node, and use that delayed | node to initialize the new inode. | |- filled = true; |- if (filled) goto cache_index; Which skips the btrfs_update_inode_mapping_flags() and btrfs_set_inode_mapping_order() calls. So the inode still has minimal folio order set as 0, not the required 1. Thus later page cache read will get a folio whose size is smaller than block size, as the mapping has its minimal folio order set as 0 not 1, then trigger the ASSERT(). [FIX] Move the btrfs_update_inode_mapping_flags() and btrfs_set_inode_mapping_order() calls under cache_index label, so that the mapping flags and minimal folio order is always set no matter if we have a cached inode. Assisted-by: LLM (analysis) Fixes: ecde48a1a6b3 ("btrfs: expose per-inode stable writes flag") Fixes: cc38d178ff33 ("btrfs: enable large data folio support under CONFIG_BTRFS_EXPERIMENTAL") Reviewed-by: Filipe Manana Signed-off-by: Qu Wenruo Signed-off-by: David Sterba --- fs/btrfs/inode.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c index a443b8e6824d..afea512c2219 100644 --- a/fs/btrfs/inode.c +++ b/fs/btrfs/inode.c @@ -3938,10 +3938,11 @@ static int btrfs_read_locked_inode(struct btrfs_inode *inode, struct btrfs_path btrfs_inode_split_flags(btrfs_inode_flags(leaf, inode_item), &inode->flags, &inode->ro_flags); + +cache_index: btrfs_update_inode_mapping_flags(inode); btrfs_set_inode_mapping_order(inode); -cache_index: /* * If we were modified in the current generation and evicted from memory * and then re-read we need to do a full sync since we don't have any