Linux Btrfs filesystem development
 help / color / mirror / Atom feed
* [PATCH] btrfs: free-space-tree: reject mismatched extent and bitmap items
@ 2026-05-10  7:49 Zhang Cen
  2026-05-10  8:11 ` Qu Wenruo
                   ` (3 more replies)
  0 siblings, 4 replies; 8+ messages in thread
From: Zhang Cen @ 2026-05-10  7:49 UTC (permalink / raw)
  To: Chris Mason, David Sterba
  Cc: linux-btrfs, linux-kernel, zerocling0077, 2045gemini, 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) {
+		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)
+			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 (ret)
+			return ret;
 
 		ret = btrfs_add_new_free_space(block_group, key.objectid,
 					       key.objectid + key.offset,

^ permalink raw reply related	[flat|nested] 8+ messages in thread

* 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

* 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

end of thread, other threads:[~2026-05-11  1:57 UTC | newest]

Thread overview: 8+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
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 ` [PATCH v2] " Zhang Cen
2026-05-10 14:44   ` 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

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox