* Re: [PATCH] btrfs: free-space-tree: reject mismatched extent and bitmap items
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>
` (2 subsequent siblings)
3 siblings, 0 replies; 8+ messages in thread
From: Qu Wenruo @ 2026-05-10 8:11 UTC (permalink / raw)
To: Zhang Cen, Chris Mason, David Sterba
Cc: linux-btrfs, linux-kernel, zerocling0077, 2045gemini
在 2026/5/10 17:19, Zhang Cen 写道:
> btrfs_load_free_space_tree() picks bitmap or extent mode from the
> FREE_SPACE_INFO flags and then lets load_free_space_bitmaps() or
> load_free_space_extents() walk the following records until the next info
> item. Those walkers only verify the record type and range with ASSERT(),
> so production builds can decode an EXTENT item as bitmap data or accept
> a BITMAP item as a whole free extent.
>
> Add a shared runtime check for each post-info key and call it from both
> loaders before decoding the current record. Reject keys whose type does
> not match the mode selected by FREE_SPACE_INFO and keys whose range falls
> outside the block group with -EUCLEAN, instead of reaching
> btrfs_free_space_test_bit() or btrfs_add_new_free_space() with an
> unexpected record.
>
> Sanitizer validation reported a fatal fault in extent_buffer_test_bit()
> (fs/btrfs/extent_io.c:4313) through btrfs_free_space_test_bit()
> (fs/btrfs/free-space-tree.c:518), reached from load_free_space_bitmaps()
> (fs/btrfs/free-space-tree.c:1603) after an extent item was decoded as
> bitmap data.
>
> 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>
>
> ---
> diff --git a/fs/btrfs/free-space-tree.c b/fs/btrfs/free-space-tree.c
> index 472b3060e5ac..e7fed8041eb1 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 (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 (key->objectid >= end || key->offset > end - key->objectid) {
The later half "key->offset > end - key->objectid" is unsafe and very
hard to read.
"end - key->objectid" can underflow.
Change it to "key->objectid + key->offset > end" will be easier to read.
Furthermore, "key->offset" should never be zero, thus in that case
a single "key->objectid + key->offset > end" will be more than enough.
For the key->offset != 0 part, it can be validated inside tree-checker.
> + 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 (ret)
Please use unlikely() for every validate_free_space_key() failure.
^ permalink raw reply [flat|nested] 8+ messages in thread[parent not found: <qu-fstree-20260510-161100@local>]
* Re: [PATCH] btrfs: free-space-tree: reject mismatched extent and bitmap items
[not found] ` <qu-fstree-20260510-161100@local>
@ 2026-05-10 14:37 ` Zhang Cen
0 siblings, 0 replies; 8+ messages in thread
From: Zhang Cen @ 2026-05-10 14:37 UTC (permalink / raw)
To: Qu Wenruo
Cc: Chris Mason, David Sterba, linux-btrfs, linux-kernel,
zerocling0077, 2045gemini, Zhang Cen
On Sun, May 10, 2026 at 04:11:00PM +0800, Qu Wenruo wrote:
> The later half "key->offset > end - key->objectid" is unsafe and very
> hard to read.
>
> "end - key->objectid" can underflow.
>
> Change it to "key->objectid + key->offset > end" will be easier to read.
>
> Furthermore, "key->offset" should never be zero, thus in that case
> a single "key->objectid + key->offset > end" will be more than enough.
>
> For the key->offset != 0 part, it can be validated inside tree-checker.
>
> Please use unlikely() for every validate_free_space_key() failure.
Thanks for the review.
You're right, the range check should not be written around
end - key->objectid. I'll change the loader-side range check to use
key->objectid + key->offset > end, add unlikely() for the validator
failure paths, and add tree-checker validation for zero-length free-space
extent items, matching the existing zero-length bitmap item check.
Thanks,
Zhang
^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH v2] btrfs: free-space-tree: reject mismatched extent and bitmap items
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:42 ` Zhang Cen
2026-05-10 14:44 ` Cen Zhang
2026-05-10 15:03 ` [PATCH v3] " Zhang Cen
3 siblings, 1 reply; 8+ messages in thread
From: Zhang Cen @ 2026-05-10 14:42 UTC (permalink / raw)
To: Chris Mason, David Sterba
Cc: linux-btrfs, linux-kernel, Qu Wenruo, zerocling0077, 2045gemini,
Zhang Cen
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
^ permalink raw reply related [flat|nested] 8+ messages in thread* Re: [PATCH v2] btrfs: free-space-tree: reject mismatched extent and bitmap items
2026-05-10 14:42 ` [PATCH v2] " Zhang Cen
@ 2026-05-10 14:44 ` Cen Zhang
0 siblings, 0 replies; 8+ messages in thread
From: Cen Zhang @ 2026-05-10 14:44 UTC (permalink / raw)
To: Chris Mason, David Sterba
Cc: linux-btrfs, linux-kernel, Qu Wenruo, zerocling0077, 2045gemini
I'm so sorry, I sent the wrong message.
Best regards,
Zhang Cen
Zhang Cen <rollkingzzc@gmail.com> 于2026年5月10日周日 22:42写道:
>
> 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
^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH v3] btrfs: free-space-tree: reject mismatched extent and bitmap items
2026-05-10 7:49 [PATCH] btrfs: free-space-tree: reject mismatched extent and bitmap items Zhang Cen
` (2 preceding siblings ...)
2026-05-10 14:42 ` [PATCH v2] " Zhang Cen
@ 2026-05-10 15:03 ` Zhang Cen
2026-05-10 22:18 ` Qu Wenruo
3 siblings, 1 reply; 8+ messages in thread
From: Zhang Cen @ 2026-05-10 15:03 UTC (permalink / raw)
To: Zhang Cen, Chris Mason, David Sterba
Cc: linux-btrfs, linux-kernel, Qu Wenruo, zerocling0077, 2045gemini
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 v2:
- Regenerate the mail-ready patch without the nested mbox wrapper.
- No code changes beyond the v2 fix.
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
^ permalink raw reply related [flat|nested] 8+ messages in thread* Re: [PATCH v3] btrfs: free-space-tree: reject mismatched extent and bitmap items
2026-05-10 15:03 ` [PATCH v3] " Zhang Cen
@ 2026-05-10 22:18 ` Qu Wenruo
2026-05-11 1:57 ` Cen Zhang
0 siblings, 1 reply; 8+ messages in thread
From: Qu Wenruo @ 2026-05-10 22:18 UTC (permalink / raw)
To: Zhang Cen, Chris Mason, David Sterba
Cc: linux-btrfs, linux-kernel, zerocling0077, 2045gemini
在 2026/5/11 00:33, Zhang Cen 写道:
> 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 v2:
> - Regenerate the mail-ready patch without the nested mbox wrapper.
> - No code changes beyond the v2 fix.
If you want to put a changelog, please include all modification, not
only the last modification but from the very beginning.
A proper example:
https://lore.kernel.org/linux-btrfs/335133ce1989ac89a6de007d4db05f5f4a6c1be2.1775491985.git.boris@bur.io/
Otherwise looks good to me.
Reviewed-by: Qu Wenruo <wqu@suse.com>
I'll give it a full fstests run before merging.
Thanks,
Qu
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH v3] btrfs: free-space-tree: reject mismatched extent and bitmap items
2026-05-10 22:18 ` Qu Wenruo
@ 2026-05-11 1:57 ` Cen Zhang
0 siblings, 0 replies; 8+ messages in thread
From: Cen Zhang @ 2026-05-11 1:57 UTC (permalink / raw)
To: Qu Wenruo
Cc: Chris Mason, David Sterba, linux-btrfs, linux-kernel,
zerocling0077, 2045gemini
>
> If you want to put a changelog, please include all modification, not
> only the last modification but from the very beginning.
>
> A proper example:
>
> https://lore.kernel.org/linux-btrfs/335133ce1989ac89a6de007d4db05f5f4a6c1be2.1775491985.git.boris@bur.io/
>
> Otherwise looks good to me.
>
> Reviewed-by: Qu Wenruo <wqu@suse.com>
>
> I'll give it a full fstests run before merging.
>
Understood, thanks. I will include the full changelog from the
beginning in future.
Thank you for the review and guidence.
Thanks,
Zhang Cen
^ permalink raw reply [flat|nested] 8+ messages in thread