aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorZhang Cen <rollkingzzc@gmail.com>2026-05-10 23:03:22 +0800
committerDavid Sterba <dsterba@suse.com>2026-06-08 15:53:32 +0200
commit42b00a651aa18beb9697a66d932cb390d6d71b8f (patch)
treee249690b6486e9efcc878f169e0205c76d3d3003
parentf84f833a72c73f33180d98258e35df3272decadc (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.c34
-rw-r--r--fs/btrfs/tree-checker.c4
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",