Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 30b0463a authored by Jan Schmidt's avatar Jan Schmidt Committed by Josef Bacik
Browse files

Btrfs: fix accessing the root pointer in tree mod log functions



The tree mod log functions were accessing root->node->... directly, without
use of btrfs_root_node() or explicit rcu locking. This could lead to an
extent buffer reference being leaked and another reference being freed too
early when preemtion was enabled.

Signed-off-by: default avatarJan Schmidt <list.btrfs@jan-o-sch.net>
Signed-off-by: default avatarJosef Bacik <jbacik@fusionio.com>
parent 90f8d62e
Loading
Loading
Loading
Loading
+20 −20
Original line number Diff line number Diff line
@@ -1069,11 +1069,11 @@ static noinline int __btrfs_cow_block(struct btrfs_trans_handle *trans,
 */
static struct tree_mod_elem *
__tree_mod_log_oldest_root(struct btrfs_fs_info *fs_info,
			   struct btrfs_root *root, u64 time_seq)
			   struct extent_buffer *eb_root, u64 time_seq)
{
	struct tree_mod_elem *tm;
	struct tree_mod_elem *found = NULL;
	u64 root_logical = root->node->start;
	u64 root_logical = eb_root->start;
	int looped = 0;

	if (!time_seq)
@@ -1107,7 +1107,6 @@ __tree_mod_log_oldest_root(struct btrfs_fs_info *fs_info,

		found = tm;
		root_logical = tm->old_root.logical;
		BUG_ON(root_logical == root->node->start);
		looped = 1;
	}

@@ -1245,30 +1244,31 @@ static inline struct extent_buffer *
get_old_root(struct btrfs_root *root, u64 time_seq)
{
	struct tree_mod_elem *tm;
	struct extent_buffer *eb;
	struct extent_buffer *eb = NULL;
	struct extent_buffer *eb_root;
	struct extent_buffer *old;
	struct tree_mod_root *old_root = NULL;
	u64 old_generation = 0;
	u64 logical;
	u32 blocksize;

	eb = btrfs_read_lock_root_node(root);
	tm = __tree_mod_log_oldest_root(root->fs_info, root, time_seq);
	eb_root = btrfs_read_lock_root_node(root);
	tm = __tree_mod_log_oldest_root(root->fs_info, eb_root, time_seq);
	if (!tm)
		return root->node;
		return eb_root;

	if (tm->op == MOD_LOG_ROOT_REPLACE) {
		old_root = &tm->old_root;
		old_generation = tm->generation;
		logical = old_root->logical;
	} else {
		logical = root->node->start;
		logical = eb_root->start;
	}

	tm = tree_mod_log_search(root->fs_info, logical, time_seq);
	if (old_root && tm && tm->op != MOD_LOG_KEY_REMOVE_WHILE_FREEING) {
		btrfs_tree_read_unlock(root->node);
		free_extent_buffer(root->node);
		btrfs_tree_read_unlock(eb_root);
		free_extent_buffer(eb_root);
		blocksize = btrfs_level_size(root, old_root->level);
		old = read_tree_block(root, logical, blocksize, 0);
		if (!old) {
@@ -1280,13 +1280,13 @@ get_old_root(struct btrfs_root *root, u64 time_seq)
			free_extent_buffer(old);
		}
	} else if (old_root) {
		btrfs_tree_read_unlock(root->node);
		free_extent_buffer(root->node);
		btrfs_tree_read_unlock(eb_root);
		free_extent_buffer(eb_root);
		eb = alloc_dummy_extent_buffer(logical, root->nodesize);
	} else {
		eb = btrfs_clone_extent_buffer(root->node);
		btrfs_tree_read_unlock(root->node);
		free_extent_buffer(root->node);
		eb = btrfs_clone_extent_buffer(eb_root);
		btrfs_tree_read_unlock(eb_root);
		free_extent_buffer(eb_root);
	}

	if (!eb)
@@ -1296,7 +1296,7 @@ get_old_root(struct btrfs_root *root, u64 time_seq)
	if (old_root) {
		btrfs_set_header_bytenr(eb, eb->start);
		btrfs_set_header_backref_rev(eb, BTRFS_MIXED_BACKREF_REV);
		btrfs_set_header_owner(eb, root->root_key.objectid);
		btrfs_set_header_owner(eb, btrfs_header_owner(eb_root));
		btrfs_set_header_level(eb, old_root->level);
		btrfs_set_header_generation(eb, old_generation);
	}
@@ -1313,15 +1313,15 @@ int btrfs_old_root_level(struct btrfs_root *root, u64 time_seq)
{
	struct tree_mod_elem *tm;
	int level;
	struct extent_buffer *eb_root = btrfs_root_node(root);

	tm = __tree_mod_log_oldest_root(root->fs_info, root, time_seq);
	tm = __tree_mod_log_oldest_root(root->fs_info, eb_root, time_seq);
	if (tm && tm->op == MOD_LOG_ROOT_REPLACE) {
		level = tm->old_root.level;
	} else {
		rcu_read_lock();
		level = btrfs_header_level(root->node);
		rcu_read_unlock();
		level = btrfs_header_level(eb_root);
	}
	free_extent_buffer(eb_root);

	return level;
}