[PATCH v2] btrfs: reject empty non-root tree blocks at read time

ZhengYuan Huang
2026-04-09T08:11:56Z

[BUG]
A corrupted tree can contain an empty non-root tree block linked from
its parent. If that block is later used by normal tree balancing,
btrfs can hit:

kernel BUG at fs/btrfs/ctree.c:3388!
Oops: invalid opcode: 0000 [#1] SMP KASAN NOPTI
RIP: 0010:__push_leaf_left+0x11f8/0x1610 fs/btrfs/ctree.c:3388
Code: ff48c1ea 03803c02 000f85bd 00000048
Call Trace:
push_leaf_left+0x3b3/0x540 fs/btrfs/ctree.c:3511
btrfs_del_items+0x74d/0xf10 fs/btrfs/ctree.c:4541
btrfs_del_csums+0x44d/0xa50 fs/btrfs/file-item.c:969
do_free_extent_accounting fs/btrfs/extent-tree.c:2984 [inline]
__btrfs_free_extent.isra.0+0xded/0x41d0 fs/btrfs/extent-tree.c:3372
run_delayed_data_ref fs/btrfs/extent-tree.c:1599 [inline]
run_one_delayed_ref fs/btrfs/extent-tree.c:1779 [inline]
btrfs_run_delayed_refs_for_head fs/btrfs/extent-tree.c:1972 [inline]
__btrfs_run_delayed_refs+0x86e/0x39a0 fs/btrfs/extent-tree.c:2047
btrfs_run_delayed_refs+0x181/0x420 fs/btrfs/extent-tree.c:2159
btrfs_commit_transaction+0xc9b/0x3d90 fs/btrfs/transaction.c:2211
btrfs_sync_fs+0xf0/0x630 fs/btrfs/super.c:1057
sync_fs_one_sb fs/sync.c:84 [inline]
sync_fs_one_sb+0xf4/0x140 fs/sync.c:80
__iterate_supers+0x1be/0x290 fs/super.c:923
iterate_supers+0x24/0x40 fs/super.c:938
ksys_sync+0xb4/0x160 fs/sync.c:104
__do_sys_sync+0x13/0x20 fs/sync.c:113
...

[CAUSE]
The old btrfs_verify_level_key() path rejected tree blocks with
nritems == 0 whenever the parent check provided a first key.
Commit 947a629988f1 ("btrfs: move tree block parentness check into
validate_extent_buffer()") moved the parentness checks into the read-time
validation path, but it dropped that guard. This lets an empty non-root
tree block pass read-time validation even though slot 0 must exist if the
parent provides a first key.

[FIX]
Restore the nritems == 0 rejection in btrfs_validate_extent_buffer()
when check->has_first_key is set. This rejects empty non-root tree
blocks as soon as they are read from disk, before later btree
operations can hit BUG_ONs while trying to use them.

Fixes: 947a629988f1 ("btrfs: move tree block parentness check into validate_extent_buffer()")
Signed-off-by: ZhengYuan Huang <gality369@gmail.com>
---
v2:
- Move the corruption check from push_leaf_left() to read-time validation
- Restore the old nritems == 0 guard before reading slot 0

fs/btrfs/disk-io.c | 9 +++++++++
1 file changed, 9 insertions(+)

diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c
index 0aa7e5d1b05f..ab2044d83155 100644
--- a/fs/btrfs/disk-io.c
+++ b/fs/btrfs/disk-io.c
@@ -430,6 +430,15 @@ int btrfs_validate_extent_buffer(struct extent_buffer *eb,
const struct btrfs_key *expect_key = &check->first_key;
struct btrfs_key found_key;

+ /* We have @first_key, so this @eb must have at least one item. */
+ if (unlikely(btrfs_header_nritems(eb) == 0)) {
+ btrfs_err(fs_info,
+ "invalid tree nritems, bytenr=%llu nritems=0 expect >0",
+ eb->start);
+ ret = -EUCLEAN;
+ goto out;
+ }
+
if (found_level)
btrfs_node_key_to_cpu(eb, &found_key, 0);
else
--
2.43.0