diff options
| author | Zhang Cen <rollkingzzc@gmail.com> | 2026-05-10 23:03:22 +0800 |
|---|---|---|
| committer | David Sterba <dsterba@suse.com> | 2026-06-08 15:53:32 +0200 |
| commit | 42b00a651aa18beb9697a66d932cb390d6d71b8f (patch) | |
| tree | e249690b6486e9efcc878f169e0205c76d3d3003 | |
| parent | f84f833a72c73f33180d98258e35df3272decadc (diff) | |
btrfs: free-space-tree: reject mismatched extent and bitmap items
btrfs_load_free_space_tree() reads FREE_SPACE_INFO once and then chooses
the bitmap or extent loader for all following free-space records until the
next FREE_SPACE_INFO item. Those loaders currently enforce the selected
record type only with ASSERT().
On production builds without CONFIG_BTRFS_ASSERT, a malformed free-space
tree can therefore be decoded in the wrong mode. An EXTENT item can reach
btrfs_free_space_test_bit() as bitmap data, while a BITMAP item can be
added as a full free extent. The latter corrupts the in-memory free-space
cache and the former can read beyond the item payload.
Sanitizer validation reported:
general protection fault
Call trace:
assert_eb_folio_uptodate() (fs/btrfs/extent_io.c:4134)
extent_buffer_test_bit() (?:?)
btrfs_free_space_test_bit() (fs/btrfs/free-space-tree.c:518)
srso_alias_return_thunk() (arch/x86/include/asm/nospec-branch.h:375)
__entry_text_end() (?:?)
__asan_memcpy() (mm/kasan/shadow.c:103)
read_extent_buffer() (?:?)
load_free_space_bitmaps() (fs/btrfs/free-space-tree.c:1548)
btrfs_get_32() (fs/btrfs/free-space-tree.c:?)
btrfs_set_16() (fs/btrfs/free-space-tree.c:?)
kmem_cache_alloc_noprof() (?:?)
btrfs_load_free_space_tree() (fs/btrfs/free-space-tree.c:1685)
load_free_space_tree_for_test() (?:?)
rcu_disable_urgency_upon_qs() (kernel/rcu/tree.c:721)
vprintk_emit() (?:?)
__up_write() (kernel/locking/rwsem.c:1401)
clone_commit_root_for_test() (?:?)
test_extent_as_bitmap_mode_mismatch() (?:?)
kmem_cache_free() (?:?)
btrfs_free_path() (fs/btrfs/free-space-tree.c:1449)
__add_block_group_free_space() (fs/btrfs/free-space-tree.c:20)
run_test() (?:?)
do_raw_spin_unlock() (?:?)
btrfs_test_free_space_tree() (fs/btrfs/tests/free-space-tree-tests.c:547)
btrfs_test_qgroups() (fs/btrfs/tests/qgroup-tests.c:462)
btrfs_run_sanity_tests() (fs/btrfs/free-space-tree.c:?)
init_btrfs_fs() (fs/btrfs/super.c:2690)
do_one_initcall() (init/main.c:1382)
__kasan_kmalloc() (?:?)
rcu_is_watching() (?:?)
do_initcalls() (init/main.c:1457)
kernel_init_freeable() (init/main.c:1674)
kernel_init() (init/main.c:1584)
ret_from_fork() (?:?)
__switch_to() (?:?)
ret_from_fork_asm() (?:?)
Validate every post-info key before decoding it. Reject keys whose type
does not match the mode selected by FREE_SPACE_INFO, and reject keys
whose range extends past the block group, returning -EUCLEAN instead of
feeding the wrong record type to the bitmap or extent decoder.
Also reject zero-length FREE_SPACE_EXTENT items in tree-checker, matching
the existing FREE_SPACE_BITMAP zero-length check. This keeps the loader
range check simple and prevents a zero-length extent item from being a
valid on-disk free-space record.
Reviewed-by: Qu Wenruo <wqu@suse.com>
Signed-off-by: Zhang Cen <rollkingzzc@gmail.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
| -rw-r--r-- | fs/btrfs/free-space-tree.c | 34 | ||||
| -rw-r--r-- | fs/btrfs/tree-checker.c | 4 |
2 files changed, 33 insertions, 5 deletions
diff --git a/fs/btrfs/free-space-tree.c b/fs/btrfs/free-space-tree.c index 556622625412..5e61612f9612 100644 --- a/fs/btrfs/free-space-tree.c +++ b/fs/btrfs/free-space-tree.c @@ -1545,6 +1545,29 @@ int btrfs_remove_block_group_free_space(struct btrfs_trans_handle *trans, return 0; } +static int validate_free_space_key(struct btrfs_block_group *block_group, + const struct btrfs_key *key, u8 expected_type) +{ + const u64 end = btrfs_block_group_end(block_group); + + if (unlikely(key->type != expected_type)) { + btrfs_err(block_group->fs_info, + "block group %llu has unexpected free space key type %u, expected %u", + block_group->start, key->type, expected_type); + return -EUCLEAN; + } + + if (unlikely(key->objectid + key->offset > end)) { + btrfs_err(block_group->fs_info, + "block group %llu has invalid free space key (%llu %u %llu)", + block_group->start, key->objectid, key->type, + key->offset); + return -EUCLEAN; + } + + return 0; +} + static int load_free_space_bitmaps(struct btrfs_caching_control *caching_ctl, struct btrfs_path *path, u32 expected_extent_count) @@ -1576,8 +1599,9 @@ static int load_free_space_bitmaps(struct btrfs_caching_control *caching_ctl, if (key.type == BTRFS_FREE_SPACE_INFO_KEY) break; - ASSERT(key.type == BTRFS_FREE_SPACE_BITMAP_KEY); - ASSERT(key.objectid < end && key.objectid + key.offset <= end); + ret = validate_free_space_key(block_group, &key, BTRFS_FREE_SPACE_BITMAP_KEY); + if (unlikely(ret)) + return ret; offset = key.objectid; while (offset < key.objectid + key.offset) { @@ -1633,7 +1657,6 @@ static int load_free_space_extents(struct btrfs_caching_control *caching_ctl, struct btrfs_fs_info *fs_info = block_group->fs_info; struct btrfs_root *root; struct btrfs_key key; - const u64 end = btrfs_block_group_end(block_group); u64 total_found = 0; u32 extent_count = 0; int ret; @@ -1654,8 +1677,9 @@ static int load_free_space_extents(struct btrfs_caching_control *caching_ctl, if (key.type == BTRFS_FREE_SPACE_INFO_KEY) break; - ASSERT(key.type == BTRFS_FREE_SPACE_EXTENT_KEY); - ASSERT(key.objectid < end && key.objectid + key.offset <= end); + ret = validate_free_space_key(block_group, &key, BTRFS_FREE_SPACE_EXTENT_KEY); + if (unlikely(ret)) + return ret; ret = btrfs_add_new_free_space(block_group, key.objectid, key.objectid + key.offset, diff --git a/fs/btrfs/tree-checker.c b/fs/btrfs/tree-checker.c index 1f15d0793a9c..ec24ffb6641d 100644 --- a/fs/btrfs/tree-checker.c +++ b/fs/btrfs/tree-checker.c @@ -2129,6 +2129,10 @@ static int check_free_space_extent(struct extent_buffer *leaf, struct btrfs_key blocksize, BTRFS_KEY_FMT_VALUE(key)); return -EUCLEAN; } + if (unlikely(key->offset == 0)) { + generic_err(leaf, slot, "free space extent length is 0"); + return -EUCLEAN; + } if (unlikely(btrfs_item_size(leaf, slot) != 0)) { generic_err(leaf, slot, "invalid item size for free space info, has %u expect 0", |
