Commit 80be2ec5 authored by Omar Sandoval's avatar Omar Sandoval Committed by Ye Bin
Browse files

btrfs: fix anon_dev leak in create_subvol()

mainline inclusion
from mainline-v5.19-rc1
commit 2256e901
category: bugfix
bugzilla: https://gitee.com/src-openeuler/kernel/issues/I9QR71
CVE: CVE-2024-35956

Reference: https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/commit/?id=2256e901f5bddc56e24089c96f27b77da932dfcc



--------------------------------

When btrfs_qgroup_inherit(), btrfs_alloc_tree_block, or
btrfs_insert_root() fail in create_subvol(), we return without freeing
anon_dev. Reorganize the error handling in create_subvol() to fix this.

Reviewed-by: default avatarSweet Tea Dorminy <sweettea-kernel@dorminy.me>
Signed-off-by: default avatarOmar Sandoval <osandov@fb.com>
Reviewed-by: default avatarDavid Sterba <dsterba@suse.com>
Signed-off-by: default avatarDavid Sterba <dsterba@suse.com>
Conflicts:
	fs/btrfs/ioctl.c
[fix context diff]
Signed-off-by: default avatarYe Bin <yebin10@huawei.com>
parent 43993247
Loading
Loading
Loading
Loading
+22 −25
Original line number Diff line number Diff line
@@ -592,7 +592,7 @@ static noinline int create_subvol(struct inode *dir,
	struct inode *inode;
	int ret;
	int err;
	dev_t anon_dev = 0;
	dev_t anon_dev;
	u64 objectid;
	u64 new_dirid = BTRFS_FIRST_FREE_OBJECTID;
	u64 index = 0;
@@ -603,11 +603,7 @@ static noinline int create_subvol(struct inode *dir,

	ret = btrfs_find_free_objectid(fs_info->tree_root, &objectid);
	if (ret)
		goto fail_free;

	ret = get_anon_bdev(&anon_dev);
	if (ret < 0)
		goto fail_free;
		goto out_root_item;

	/*
	 * Don't create subvolume whose level is not zero. Or qgroup will be
@@ -615,9 +611,13 @@ static noinline int create_subvol(struct inode *dir,
	 */
	if (btrfs_qgroup_level(objectid)) {
		ret = -ENOSPC;
		goto fail_free;
		goto out_root_item;
	}

	ret = get_anon_bdev(&anon_dev);
	if (ret < 0)
		goto out_root_item;

	btrfs_init_block_rsv(&block_rsv, BTRFS_BLOCK_RSV_TEMP);
	/*
	 * The same as the snapshot creation, please see the comment
@@ -625,26 +625,26 @@ static noinline int create_subvol(struct inode *dir,
	 */
	ret = btrfs_subvolume_reserve_metadata(root, &block_rsv, 8, false);
	if (ret)
		goto fail_free;
		goto out_anon_dev;

	trans = btrfs_start_transaction(root, 0);
	if (IS_ERR(trans)) {
		ret = PTR_ERR(trans);
		btrfs_subvolume_release_metadata(root, &block_rsv);
		goto fail_free;
		goto out_anon_dev;
	}
	trans->block_rsv = &block_rsv;
	trans->bytes_reserved = block_rsv.size;

	ret = btrfs_qgroup_inherit(trans, 0, objectid, inherit);
	if (ret)
		goto fail;
		goto out;

	leaf = btrfs_alloc_tree_block(trans, root, 0, objectid, NULL, 0, 0, 0,
				      BTRFS_NESTING_NORMAL);
	if (IS_ERR(leaf)) {
		ret = PTR_ERR(leaf);
		goto fail;
		goto out;
	}

	btrfs_mark_buffer_dirty(leaf);
@@ -697,7 +697,7 @@ static noinline int create_subvol(struct inode *dir,
		 */
		btrfs_free_tree_block(trans, root, leaf, 0, 1);
		free_extent_buffer(leaf);
		goto fail;
		goto out;
	}

	free_extent_buffer(leaf);
@@ -706,12 +706,11 @@ static noinline int create_subvol(struct inode *dir,
	key.offset = (u64)-1;
	new_root = btrfs_get_new_fs_root(fs_info, objectid, &anon_dev);
	if (IS_ERR(new_root)) {
		free_anon_bdev(anon_dev);
		ret = PTR_ERR(new_root);
		btrfs_abort_transaction(trans, ret);
		goto fail;
		goto out;
	}
	/* Freeing will be done in btrfs_put_root() of new_root */
	/* anon_dev is owned by new_root now. */
	anon_dev = 0;

	btrfs_record_root_in_trans(trans, new_root);
@@ -721,7 +720,7 @@ static noinline int create_subvol(struct inode *dir,
	if (ret) {
		/* We potentially lose an unused inode item here */
		btrfs_abort_transaction(trans, ret);
		goto fail;
		goto out;
	}

	mutex_lock(&new_root->objectid_mutex);
@@ -734,28 +733,28 @@ static noinline int create_subvol(struct inode *dir,
	ret = btrfs_set_inode_index(BTRFS_I(dir), &index);
	if (ret) {
		btrfs_abort_transaction(trans, ret);
		goto fail;
		goto out;
	}

	ret = btrfs_insert_dir_item(trans, name, namelen, BTRFS_I(dir), &key,
				    BTRFS_FT_DIR, index);
	if (ret) {
		btrfs_abort_transaction(trans, ret);
		goto fail;
		goto out;
	}

	btrfs_i_size_write(BTRFS_I(dir), dir->i_size + namelen * 2);
	ret = btrfs_update_inode(trans, root, dir);
	if (ret) {
		btrfs_abort_transaction(trans, ret);
		goto fail;
		goto out;
	}

	ret = btrfs_add_root_ref(trans, objectid, root->root_key.objectid,
				 btrfs_ino(BTRFS_I(dir)), index, name, namelen);
	if (ret) {
		btrfs_abort_transaction(trans, ret);
		goto fail;
		goto out;
	}

	ret = btrfs_uuid_tree_add(trans, root_item->uuid,
@@ -763,8 +762,7 @@ static noinline int create_subvol(struct inode *dir,
	if (ret)
		btrfs_abort_transaction(trans, ret);

fail:
	kfree(root_item);
out:
	trans->block_rsv = NULL;
	trans->bytes_reserved = 0;
	btrfs_subvolume_release_metadata(root, &block_rsv);
@@ -779,11 +777,10 @@ static noinline int create_subvol(struct inode *dir,
			return PTR_ERR(inode);
		d_instantiate(dentry, inode);
	}
	return ret;

fail_free:
out_anon_dev:
	if (anon_dev)
		free_anon_bdev(anon_dev);
out_root_item:
	kfree(root_item);
	return ret;
}