mirror of
https://github.com/torvalds/linux.git
synced 2025-12-07 20:06:24 +00:00
Merge tag 'for-6.18-rc2-tag' of git://git.kernel.org/pub/scm/linux/kernel/git/kdave/linux
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()
This commit is contained in:
@@ -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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -4102,6 +4102,48 @@ out:
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user