diff options
Diffstat (limited to 'fs/btrfs/relocation.c')
| -rw-r--r-- | fs/btrfs/relocation.c | 55 |
1 files changed, 43 insertions, 12 deletions
diff --git a/fs/btrfs/relocation.c b/fs/btrfs/relocation.c index 0d63d117db59..fc5c14b5adad 100644 --- a/fs/btrfs/relocation.c +++ b/fs/btrfs/relocation.c @@ -588,6 +588,7 @@ static int __add_reloc_root(struct btrfs_root *root, struct reloc_control *rc) btrfs_err(fs_info, "Duplicate root found for start=%llu while inserting into relocation tree", node->bytenr); + kfree(node); return -EEXIST; } @@ -890,6 +891,13 @@ static int get_new_location(struct inode *reloc_inode, u64 *new_bytenr, leaf = path->nodes[0]; fi = btrfs_item_ptr(leaf, path->slots[0], struct btrfs_file_extent_item); + if (unlikely(btrfs_file_extent_type(leaf, fi) == BTRFS_FILE_EXTENT_INLINE)) { + btrfs_print_leaf(leaf); + btrfs_err(fs_info, + "unexpected inline file extent item for data reloc inode %llu key offset %llu", + btrfs_ino(BTRFS_I(reloc_inode)), bytenr); + return -EUCLEAN; + } /* * The cluster-boundary key searched above is always written by @@ -1518,6 +1526,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 +1551,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,21 +1927,39 @@ again: 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) { - root->reloc_root = NULL; + clear_reloc_root(root); + /* Drop the ref for root->reloc_root. */ btrfs_put_root(reloc_root); } - clear_bit(BTRFS_ROOT_DEAD_RELOC_TREE, - &root->state); btrfs_put_root(root); } |