btrfs: Handle owner mismatch gracefully when walking up tree
authorQu Wenruo <wqu@suse.com>
Tue, 21 Aug 2018 01:42:03 +0000 (09:42 +0800)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Tue, 13 Nov 2018 19:15:13 +0000 (11:15 -0800)
commit 65c6e82becec33731f48786e5a30f98662c86b16 upstream.

[BUG]
When mounting certain crafted image, btrfs will trigger kernel BUG_ON()
when trying to recover balance:

  kernel BUG at fs/btrfs/extent-tree.c:8956!
  invalid opcode: 0000 [#1] PREEMPT SMP NOPTI
  CPU: 1 PID: 662 Comm: mount Not tainted 4.18.0-rc1-custom+ #10
  RIP: 0010:walk_up_proc+0x336/0x480 [btrfs]
  RSP: 0018:ffffb53540c9b890 EFLAGS: 00010202
  Call Trace:
   walk_up_tree+0x172/0x1f0 [btrfs]
   btrfs_drop_snapshot+0x3a4/0x830 [btrfs]
   merge_reloc_roots+0xe1/0x1d0 [btrfs]
   btrfs_recover_relocation+0x3ea/0x420 [btrfs]
   open_ctree+0x1af3/0x1dd0 [btrfs]
   btrfs_mount_root+0x66b/0x740 [btrfs]
   mount_fs+0x3b/0x16a
   vfs_kern_mount.part.9+0x54/0x140
   btrfs_mount+0x16d/0x890 [btrfs]
   mount_fs+0x3b/0x16a
   vfs_kern_mount.part.9+0x54/0x140
   do_mount+0x1fd/0xda0
   ksys_mount+0xba/0xd0
   __x64_sys_mount+0x21/0x30
   do_syscall_64+0x60/0x210
   entry_SYSCALL_64_after_hwframe+0x49/0xbe

[CAUSE]
Extent tree corruption.  In this particular case, reloc tree root's
owner is DATA_RELOC_TREE (should be TREE_RELOC), thus its backref is
corrupted and we failed the owner check in walk_up_tree().

[FIX]
It's pretty hard to take care of every extent tree corruption, but at
least we can remove such BUG_ON() and exit more gracefully.

And since in this particular image, DATA_RELOC_TREE and TREE_RELOC share
the same root (which is obviously invalid), we needs to make
__del_reloc_root() more robust to detect such invalid sharing to avoid
possible NULL dereference as root->node can be NULL in this case.

Link: https://bugzilla.kernel.org/show_bug.cgi?id=200411
Reported-by: Xu Wen <wen.xu@gatech.edu>
CC: stable@vger.kernel.org # 4.4+
Signed-off-by: Qu Wenruo <wqu@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
fs/btrfs/extent-tree.c
fs/btrfs/relocation.c

index f96f72659693a185530200d30afd7d687c436ed1..1fe62225bc31f7668500e64b53ee6341365f12aa 100644 (file)
@@ -9028,15 +9028,14 @@ static noinline int walk_up_proc(struct btrfs_trans_handle *trans,
        if (eb == root->node) {
                if (wc->flags[level] & BTRFS_BLOCK_FLAG_FULL_BACKREF)
                        parent = eb->start;
-               else
-                       BUG_ON(root->root_key.objectid !=
-                              btrfs_header_owner(eb));
+               else if (root->root_key.objectid != btrfs_header_owner(eb))
+                       goto owner_mismatch;
        } else {
                if (wc->flags[level + 1] & BTRFS_BLOCK_FLAG_FULL_BACKREF)
                        parent = path->nodes[level + 1]->start;
-               else
-                       BUG_ON(root->root_key.objectid !=
-                              btrfs_header_owner(path->nodes[level + 1]));
+               else if (root->root_key.objectid !=
+                        btrfs_header_owner(path->nodes[level + 1]))
+                       goto owner_mismatch;
        }
 
        btrfs_free_tree_block(trans, root, eb, parent, wc->refs[level] == 1);
@@ -9044,6 +9043,11 @@ out:
        wc->refs[level] = 0;
        wc->flags[level] = 0;
        return 0;
+
+owner_mismatch:
+       btrfs_err_rl(fs_info, "unexpected tree owner, have %llu expect %llu",
+                    btrfs_header_owner(eb), root->root_key.objectid);
+       return -EUCLEAN;
 }
 
 static noinline int walk_down_tree(struct btrfs_trans_handle *trans,
@@ -9097,6 +9101,8 @@ static noinline int walk_up_tree(struct btrfs_trans_handle *trans,
                        ret = walk_up_proc(trans, root, path, wc);
                        if (ret > 0)
                                return 0;
+                       if (ret < 0)
+                               return ret;
 
                        if (path->locks[level]) {
                                btrfs_tree_unlock_rw(path->nodes[level],
index b80b03e0c5d3f3f2b70b4cb7bef64450c889a38a..eeae2c3ab17e5094699a79614ea7c48e09994027 100644 (file)
@@ -1334,7 +1334,7 @@ static void __del_reloc_root(struct btrfs_root *root)
        struct mapping_node *node = NULL;
        struct reloc_control *rc = fs_info->reloc_ctl;
 
-       if (rc) {
+       if (rc && root->node) {
                spin_lock(&rc->reloc_root_tree.lock);
                rb_node = tree_search(&rc->reloc_root_tree.rb_root,
                                      root->node->start);