Commit 942048d4 authored by Linus Torvalds's avatar Linus Torvalds
Browse files
Pull btrfs fixes from David Sterba:

 - in send, fix duplicated rmdir operations when using extrefs
   (hardlinks), receive can fail with ENOENT

 - fixup of error check when reading extent root in ref-verify and
   damaged roots are allowed by mount option (found by smatch)

 - fix freeing partially initialized fs info (found by syzkaller)

 - fix use-after-free when printing ref_tracking status of delayed
   inodes

* tag 'for-6.18-rc2-tag' of git://git.kernel.org/pub/scm/linux/kernel/git/kdave/linux:
  btrfs: ref-verify: fix IS_ERR() vs NULL check in btrfs_build_ref_tree()
  btrfs: fix delayed_node ref_tracker use after free
  btrfs: send: fix duplicated rmdir operations when using extrefs
  btrfs: directly free partially initialized fs_info in btrfs_check_leaked_roots()
parents 43e9ad0c ada7d45b
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -2110,9 +2110,9 @@ void btrfs_kill_all_delayed_nodes(struct btrfs_root *root)

		for (int i = 0; i < count; i++) {
			__btrfs_kill_delayed_node(delayed_nodes[i]);
			btrfs_delayed_node_ref_tracker_dir_print(delayed_nodes[i]);
			btrfs_release_delayed_node(delayed_nodes[i],
						   &delayed_node_trackers[i]);
			btrfs_delayed_node_ref_tracker_dir_print(delayed_nodes[i]);
		}
	}
}
+7 −0
Original line number Diff line number Diff line
@@ -219,6 +219,13 @@ static inline void btrfs_delayed_node_ref_tracker_dir_print(struct btrfs_delayed
	if (!btrfs_test_opt(node->root->fs_info, REF_TRACKER))
		return;

	/*
	 * Only print if there are leaked references. The caller is
	 * holding one reference, so if refs == 1 there is no leak.
	 */
	if (refcount_read(&node->refs) == 1)
		return;

	ref_tracker_dir_print(&node->ref_dir.dir,
			      BTRFS_DELAYED_NODE_REF_TRACKER_DISPLAY_LIMIT);
}
+1 −1
Original line number Diff line number Diff line
@@ -982,7 +982,7 @@ int btrfs_build_ref_tree(struct btrfs_fs_info *fs_info)

	extent_root = btrfs_extent_root(fs_info, 0);
	/* If the extent tree is damaged we cannot ignore it (IGNOREBADROOTS). */
	if (IS_ERR(extent_root)) {
	if (!extent_root) {
		btrfs_warn(fs_info, "ref-verify: extent tree not available, disabling");
		btrfs_clear_opt(fs_info->mount_opt, REF_VERIFY);
		return 0;
+48 −8
Original line number Diff line number Diff line
@@ -4102,6 +4102,48 @@ static int refresh_ref_path(struct send_ctx *sctx, struct recorded_ref *ref)
	return ret;
}

static int rbtree_check_dir_ref_comp(const void *k, const struct rb_node *node)
{
	const struct recorded_ref *data = k;
	const struct recorded_ref *ref = rb_entry(node, struct recorded_ref, node);

	if (data->dir > ref->dir)
		return 1;
	if (data->dir < ref->dir)
		return -1;
	if (data->dir_gen > ref->dir_gen)
		return 1;
	if (data->dir_gen < ref->dir_gen)
		return -1;
	return 0;
}

static bool rbtree_check_dir_ref_less(struct rb_node *node, const struct rb_node *parent)
{
	const struct recorded_ref *entry = rb_entry(node, struct recorded_ref, node);

	return rbtree_check_dir_ref_comp(entry, parent) < 0;
}

static int record_check_dir_ref_in_tree(struct rb_root *root,
			struct recorded_ref *ref, struct list_head *list)
{
	struct recorded_ref *tmp_ref;
	int ret;

	if (rb_find(ref, root, rbtree_check_dir_ref_comp))
		return 0;

	ret = dup_ref(ref, list);
	if (ret < 0)
		return ret;

	tmp_ref = list_last_entry(list, struct recorded_ref, list);
	rb_add(&tmp_ref->node, root, rbtree_check_dir_ref_less);
	tmp_ref->root = root;
	return 0;
}

static int rename_current_inode(struct send_ctx *sctx,
				struct fs_path *current_path,
				struct fs_path *new_path)
@@ -4129,11 +4171,11 @@ static int process_recorded_refs(struct send_ctx *sctx, int *pending_move)
	struct recorded_ref *cur;
	struct recorded_ref *cur2;
	LIST_HEAD(check_dirs);
	struct rb_root rbtree_check_dirs = RB_ROOT;
	struct fs_path *valid_path = NULL;
	u64 ow_inode = 0;
	u64 ow_gen;
	u64 ow_mode;
	u64 last_dir_ino_rm = 0;
	bool did_overwrite = false;
	bool is_orphan = false;
	bool can_rename = true;
@@ -4437,7 +4479,7 @@ static int process_recorded_refs(struct send_ctx *sctx, int *pending_move)
					goto out;
			}
		}
		ret = dup_ref(cur, &check_dirs);
		ret = record_check_dir_ref_in_tree(&rbtree_check_dirs, cur, &check_dirs);
		if (ret < 0)
			goto out;
	}
@@ -4465,7 +4507,7 @@ static int process_recorded_refs(struct send_ctx *sctx, int *pending_move)
		}

		list_for_each_entry(cur, &sctx->deleted_refs, list) {
			ret = dup_ref(cur, &check_dirs);
			ret = record_check_dir_ref_in_tree(&rbtree_check_dirs, cur, &check_dirs);
			if (ret < 0)
				goto out;
		}
@@ -4475,7 +4517,7 @@ static int process_recorded_refs(struct send_ctx *sctx, int *pending_move)
		 * We have a moved dir. Add the old parent to check_dirs
		 */
		cur = list_first_entry(&sctx->deleted_refs, struct recorded_ref, list);
		ret = dup_ref(cur, &check_dirs);
		ret = record_check_dir_ref_in_tree(&rbtree_check_dirs, cur, &check_dirs);
		if (ret < 0)
			goto out;
	} else if (!S_ISDIR(sctx->cur_inode_mode)) {
@@ -4509,7 +4551,7 @@ static int process_recorded_refs(struct send_ctx *sctx, int *pending_move)
				if (is_current_inode_path(sctx, cur->full_path))
					fs_path_reset(&sctx->cur_inode_path);
			}
			ret = dup_ref(cur, &check_dirs);
			ret = record_check_dir_ref_in_tree(&rbtree_check_dirs, cur, &check_dirs);
			if (ret < 0)
				goto out;
		}
@@ -4552,8 +4594,7 @@ static int process_recorded_refs(struct send_ctx *sctx, int *pending_move)
			ret = cache_dir_utimes(sctx, cur->dir, cur->dir_gen);
			if (ret < 0)
				goto out;
		} else if (ret == inode_state_did_delete &&
			   cur->dir != last_dir_ino_rm) {
		} else if (ret == inode_state_did_delete) {
			ret = can_rmdir(sctx, cur->dir, cur->dir_gen);
			if (ret < 0)
				goto out;
@@ -4565,7 +4606,6 @@ static int process_recorded_refs(struct send_ctx *sctx, int *pending_move)
				ret = send_rmdir(sctx, valid_path);
				if (ret < 0)
					goto out;
				last_dir_ino_rm = cur->dir;
			}
		}
	}
+7 −1
Original line number Diff line number Diff line
@@ -2068,7 +2068,13 @@ static int btrfs_get_tree_subvol(struct fs_context *fc)
	fs_info->super_copy = kzalloc(BTRFS_SUPER_INFO_SIZE, GFP_KERNEL);
	fs_info->super_for_commit = kzalloc(BTRFS_SUPER_INFO_SIZE, GFP_KERNEL);
	if (!fs_info->super_copy || !fs_info->super_for_commit) {
		btrfs_free_fs_info(fs_info);
		/*
		 * Dont call btrfs_free_fs_info() to free it as it's still
		 * initialized partially.
		 */
		kfree(fs_info->super_copy);
		kfree(fs_info->super_for_commit);
		kvfree(fs_info);
		return -ENOMEM;
	}
	btrfs_init_fs_info(fs_info);