From: Zhang Cen <rollkingzzc@gmail.com>
To: Chris Mason <clm@fb.com>, David Sterba <dsterba@suse.com>
Cc: linux-btrfs@vger.kernel.org, linux-kernel@vger.kernel.org,
Qu Wenruo <wqu@suse.com>,
zerocling0077@gmail.com, 2045gemini@gmail.com,
Zhang Cen <rollkingzzc@gmail.com>
Subject: [PATCH v2] btrfs: free-space-tree: reject mismatched extent and bitmap items
Date: Sun, 10 May 2026 22:42:11 +0800 [thread overview]
Message-ID: <20260510144211.3722703-1-rollkingzzc@gmail.com> (raw)
In-Reply-To: <20260510074943.2644334-1-rollkingzzc@gmail.com>
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.
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.
Sanitizer validation reported:
Oops: general protection fault, probably for non-canonical address 0xdffffc0000000001: 0000 [#1] SMP KASAN NOPTI
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() (?:?)
Signed-off-by: Zhang Cen <rollkingzzc@gmail.com>
---
From ccb16e1f1b9490dc3d76e219cc2d9fe8cdd3afa7 Mon Sep 17 00:00:00 2001
Message-ID: <ccb16e1f1b9490dc3d76e219cc2d9fe8cdd3afa7.1778403060.git.rollkingzzc@gmail.com>
In-Reply-To: <20260510074943.2644334-1-rollkingzzc@gmail.com>
References: <20260510074943.2644334-1-rollkingzzc@gmail.com>
From: Zhang Cen <rollkingzzc@gmail.com>
Date: Sun, 10 May 2026 16:51:00 +0800
Subject: [PATCH v2] btrfs: free-space-tree: reject mismatched extent and
bitmap items
To: Chris Mason <clm@fb.com>,
David Sterba <dsterba@suse.com>
Cc: linux-btrfs@vger.kernel.org,
linux-kernel@vger.kernel.org,
Qu Wenruo <wqu@suse.com>,
zerocling0077@gmail.com,
2045gemini@gmail.com,
Zhang Cen <rollkingzzc@gmail.com>
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.
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.
A malformed extent-as-bitmap record was observed as a KASAN fault in
extent_buffer_test_bit() (fs/btrfs/extent_io.c:4313), reached through
btrfs_free_space_test_bit() (fs/btrfs/free-space-tree.c:518) from
load_free_space_bitmaps() (fs/btrfs/free-space-tree.c:1603).
Signed-off-by: Zhang Cen <rollkingzzc@gmail.com>
---
Changes since v1:
- Use unlikely() on validate_free_space_key() failure checks.
- Replace the loader range check with key->objectid + key->offset > end.
- Reject zero-length free-space extent items in tree-checker.
fs/btrfs/free-space-tree.c | 37 ++++++++++++++++++++++++++++++++-----
fs/btrfs/tree-checker.c | 4 ++++
2 files changed, 36 insertions(+), 5 deletions(-)
diff --git a/fs/btrfs/free-space-tree.c b/fs/btrfs/free-space-tree.c
index 472b3060e5ac..c1af70761919 100644
--- a/fs/btrfs/free-space-tree.c
+++ b/fs/btrfs/free-space-tree.c
@@ -1545,6 +1545,30 @@ 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 +1600,10 @@ 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 +1659,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 +1679,10 @@ 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",
--
2.43.0
next prev parent reply other threads:[~2026-05-10 14:42 UTC|newest]
Thread overview: 10+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-05-10 7:49 [PATCH] btrfs: free-space-tree: reject mismatched extent and bitmap items Zhang Cen
2026-05-10 8:11 ` Qu Wenruo
[not found] ` <qu-fstree-20260510-161100@local>
2026-05-10 14:37 ` Zhang Cen
2026-05-10 14:42 ` Zhang Cen [this message]
2026-05-10 14:44 ` [PATCH v2] " Cen Zhang
2026-05-10 15:03 ` [PATCH v3] " Zhang Cen
2026-05-10 22:18 ` Qu Wenruo
2026-05-11 1:57 ` Cen Zhang
2026-05-12 15:18 ` David Sterba
2026-05-12 22:00 ` Qu Wenruo
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260510144211.3722703-1-rollkingzzc@gmail.com \
--to=rollkingzzc@gmail.com \
--cc=2045gemini@gmail.com \
--cc=clm@fb.com \
--cc=dsterba@suse.com \
--cc=linux-btrfs@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=wqu@suse.com \
--cc=zerocling0077@gmail.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.