diff --git a/fs/btrfs/relocation.c b/fs/btrfs/relocation.c index 0d63d117db59..a8d0acb0ad35 100644 --- a/fs/btrfs/relocation.c +++ b/fs/btrfs/relocation.c @@ -1518,6 +1518,17 @@ static int insert_dirty_subvol(struct btrfs_trans_handle *trans, return 0; } +static void clear_reloc_root(struct btrfs_root *root) +{ + root->reloc_root = NULL; + /* + * Need barrier to ensure clear_bit() only happens after + * root->reloc_root = NULL. Pairs with have_reloc_root(). + */ + smp_wmb(); + clear_bit(BTRFS_ROOT_DEAD_RELOC_TREE, &root->state); +} + static int clean_dirty_subvols(struct reloc_control *rc) { struct btrfs_root *root; @@ -1532,13 +1543,7 @@ static int clean_dirty_subvols(struct reloc_control *rc) struct btrfs_root *reloc_root = root->reloc_root; list_del_init(&root->reloc_dirty_list); - root->reloc_root = NULL; - /* - * Need barrier to ensure clear_bit() only happens after - * root->reloc_root = NULL. Pairs with have_reloc_root. - */ - smp_wmb(); - clear_bit(BTRFS_ROOT_DEAD_RELOC_TREE, &root->state); + clear_reloc_root(root); if (reloc_root) { /* * btrfs_drop_snapshot drops our ref we hold for @@ -1914,13 +1919,32 @@ void merge_reloc_roots(struct reloc_control *rc) goto out; } ret = merge_reloc_root(rc, root); - btrfs_put_root(root); if (ret) { - if (list_empty(&reloc_root->root_list)) + /* + * Clear the reloc root since below we will call + * free_reloc_roots(), otherwise we leave + * root->reloc_root pointing to a freed reloc + * root and trigger a use-after-free during + * unmount or elsewhere. + */ + clear_reloc_root(root); + btrfs_put_root(root); + /* + * We are adding the reloc_root to the local + * reloc_roots list, so we add a ref for this + * list which will be dropped below by the call + * to free_reloc_roots(). + */ + if (list_empty(&reloc_root->root_list)) { list_add_tail(&reloc_root->root_list, &reloc_roots); + btrfs_grab_root(reloc_root); + } + /* Now drop the ref for root->reloc_root. */ + btrfs_put_root(reloc_root); goto out; } + btrfs_put_root(root); } else { if (!IS_ERR(root)) { if (root->reloc_root == reloc_root) {