btrfs: reject invalid reloc tree root keys with stack dump
authorQu Wenruo <wqu@suse.com>
Thu, 3 Aug 2023 09:20:43 +0000 (17:20 +0800)
committerDavid Sterba <dsterba@suse.com>
Thu, 10 Aug 2023 15:14:42 +0000 (17:14 +0200)
[BUG]
Syzbot reported a crash that an ASSERT() got triggered inside
prepare_to_merge().

That ASSERT() makes sure the reloc tree is properly pointed back by its
subvolume tree.

[CAUSE]
After more debugging output, it turns out we had an invalid reloc tree:

  BTRFS error (device loop1): reloc tree mismatch, root 8 has no reloc root, expect reloc root key (-8, 132, 8) gen 17

Note the above root key is (TREE_RELOC_OBJECTID, ROOT_ITEM,
QUOTA_TREE_OBJECTID), meaning it's a reloc tree for quota tree.

But reloc trees can only exist for subvolumes, as for non-subvolume
trees, we just COW the involved tree block, no need to create a reloc
tree since those tree blocks won't be shared with other trees.

Only subvolumes tree can share tree blocks with other trees (thus they
have BTRFS_ROOT_SHAREABLE flag).

Thus this new debug output proves my previous assumption that corrupted
on-disk data can trigger that ASSERT().

[FIX]
Besides the dedicated fix and the graceful exit, also let tree-checker to
check such root keys, to make sure reloc trees can only exist for subvolumes.

CC: stable@vger.kernel.org # 5.15+
Reported-by: syzbot+ae97a827ae1c3336bbb4@syzkaller.appspotmail.com
Reviewed-by: Filipe Manana <fdmanana@suse.com>
Signed-off-by: Qu Wenruo <wqu@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
fs/btrfs/disk-io.c
fs/btrfs/tree-checker.c

index 11b1ac716f3b1a569c3333ea07f082c33df3f11a..a9a2c5446c1804db8b9bbac82612615a41ce380b 100644 (file)
@@ -1103,7 +1103,8 @@ static int btrfs_init_fs_root(struct btrfs_root *root, dev_t anon_dev)
        btrfs_drew_lock_init(&root->snapshot_lock);
 
        if (root->root_key.objectid != BTRFS_TREE_LOG_OBJECTID &&
-           !btrfs_is_data_reloc_root(root)) {
+           !btrfs_is_data_reloc_root(root) &&
+           is_fstree(root->root_key.objectid)) {
                set_bit(BTRFS_ROOT_SHAREABLE, &root->state);
                btrfs_check_and_init_root_item(&root->root_item);
        }
index 351ba9e90675d30abca33467ce9f10c711268eda..11d81e39ef4e8f92a8352238705bdd8034c467d0 100644 (file)
@@ -446,6 +446,20 @@ static int check_root_key(struct extent_buffer *leaf, struct btrfs_key *key,
        btrfs_item_key_to_cpu(leaf, &item_key, slot);
        is_root_item = (item_key.type == BTRFS_ROOT_ITEM_KEY);
 
+       /*
+        * Bad rootid for reloc trees.
+        *
+        * Reloc trees are only for subvolume trees, other trees only need
+        * to be COWed to be relocated.
+        */
+       if (unlikely(is_root_item && key->objectid == BTRFS_TREE_RELOC_OBJECTID &&
+                    !is_fstree(key->offset))) {
+               generic_err(leaf, slot,
+               "invalid reloc tree for root %lld, root id is not a subvolume tree",
+                           key->offset);
+               return -EUCLEAN;
+       }
+
        /* No such tree id */
        if (unlikely(key->objectid == 0)) {
                if (is_root_item)