diff mbox series

[v2,3/3] btrfs: reject invalid reloc tree root keys with stack dump

Message ID a15ff1e397312309c554482e55d929488e22dcca.1690970028.git.wqu@suse.com (mailing list archive)
State New, archived
Headers show
Series btrfs: fix an ASSERT() triggered inside prepare_to_merge() | expand

Commit Message

Qu Wenruo Aug. 2, 2023, 10:02 a.m. UTC
[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.

Reported-by: syzbot+ae97a827ae1c3336bbb4@syzkaller.appspotmail.com
Signed-off-by: Qu Wenruo <wqu@suse.com>
---
 fs/btrfs/disk-io.c      |  3 ++-
 fs/btrfs/tree-checker.c | 15 +++++++++++++++
 2 files changed, 17 insertions(+), 1 deletion(-)

Comments

Filipe Manana Aug. 2, 2023, 12:11 p.m. UTC | #1
On Wed, Aug 2, 2023 at 12:24 PM Qu Wenruo <wqu@suse.com> wrote:
>
> [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.
>
> Reported-by: syzbot+ae97a827ae1c3336bbb4@syzkaller.appspotmail.com
> Signed-off-by: Qu Wenruo <wqu@suse.com>
> ---
>  fs/btrfs/disk-io.c      |  3 ++-
>  fs/btrfs/tree-checker.c | 15 +++++++++++++++
>  2 files changed, 17 insertions(+), 1 deletion(-)
>
> diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c
> index 5fd336c597e9..a01eac963075 100644
> --- a/fs/btrfs/disk-io.c
> +++ b/fs/btrfs/disk-io.c
> @@ -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);
>         }
> diff --git a/fs/btrfs/tree-checker.c b/fs/btrfs/tree-checker.c
> index 038dfa8f1788..dabb86314a4c 100644
> --- a/fs/btrfs/tree-checker.c
> +++ b/fs/btrfs/tree-checker.c
> @@ -446,6 +446,21 @@ 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 needs
> +        * a COW to be relocated.

... 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);
> +               dump_stack();

Same logic as for places that do WARN_ON() or dump leaves: the
dump_stack() should come before the error message.

Other than that, it looks good to me:

Reviewed-by: Filipe Manana <fdmanana@suse.com>

Thanks.

> +               return -EUCLEAN;
> +       }
> +
>         /* No such tree id */
>         if (unlikely(key->objectid == 0)) {
>                 if (is_root_item)
> --
> 2.41.0
>
Qu Wenruo Aug. 2, 2023, 9:32 p.m. UTC | #2
On 2023/8/2 20:11, Filipe Manana wrote:
> On Wed, Aug 2, 2023 at 12:24 PM Qu Wenruo <wqu@suse.com> wrote:
>>
>> [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.
>>
>> Reported-by: syzbot+ae97a827ae1c3336bbb4@syzkaller.appspotmail.com
>> Signed-off-by: Qu Wenruo <wqu@suse.com>
>> ---
>>   fs/btrfs/disk-io.c      |  3 ++-
>>   fs/btrfs/tree-checker.c | 15 +++++++++++++++
>>   2 files changed, 17 insertions(+), 1 deletion(-)
>>
>> diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c
>> index 5fd336c597e9..a01eac963075 100644
>> --- a/fs/btrfs/disk-io.c
>> +++ b/fs/btrfs/disk-io.c
>> @@ -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);
>>          }
>> diff --git a/fs/btrfs/tree-checker.c b/fs/btrfs/tree-checker.c
>> index 038dfa8f1788..dabb86314a4c 100644
>> --- a/fs/btrfs/tree-checker.c
>> +++ b/fs/btrfs/tree-checker.c
>> @@ -446,6 +446,21 @@ 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 needs
>> +        * a COW to be relocated.
>
> ... 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);
>> +               dump_stack();
>
> Same logic as for places that do WARN_ON() or dump leaves: the
> dump_stack() should come before the error message.

Oh my bad, the dump_stack() is only for debug purpose, should be removed.

Thanks,
Qu
>
> Other than that, it looks good to me:
>
> Reviewed-by: Filipe Manana <fdmanana@suse.com>
>
> Thanks.
>
>> +               return -EUCLEAN;
>> +       }
>> +
>>          /* No such tree id */
>>          if (unlikely(key->objectid == 0)) {
>>                  if (is_root_item)
>> --
>> 2.41.0
>>
diff mbox series

Patch

diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c
index 5fd336c597e9..a01eac963075 100644
--- a/fs/btrfs/disk-io.c
+++ b/fs/btrfs/disk-io.c
@@ -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);
 	}
diff --git a/fs/btrfs/tree-checker.c b/fs/btrfs/tree-checker.c
index 038dfa8f1788..dabb86314a4c 100644
--- a/fs/btrfs/tree-checker.c
+++ b/fs/btrfs/tree-checker.c
@@ -446,6 +446,21 @@  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 needs
+	 * a COW 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);
+		dump_stack();
+		return -EUCLEAN;
+	}
+
 	/* No such tree id */
 	if (unlikely(key->objectid == 0)) {
 		if (is_root_item)