Commit 87f2c460 authored by Filipe Manana's avatar Filipe Manana Committed by David Sterba
Browse files

btrfs: fix transaction abort on set received ioctl due to item overflow



If the set received ioctl fails due to an item overflow when attempting to
add the BTRFS_UUID_KEY_RECEIVED_SUBVOL we have to abort the transaction
since we did some metadata updates before.

This means that if a user calls this ioctl with the same received UUID
field for a lot of subvolumes, we will hit the overflow, trigger the
transaction abort and turn the filesystem into RO mode. A malicious user
could exploit this, and this ioctl does not even requires that a user
has admin privileges (CAP_SYS_ADMIN), only that he/she owns the subvolume.

Fix this by doing an early check for item overflow before starting a
transaction. This is also race safe because we are holding the subvol_sem
semaphore in exclusive (write) mode.

A test case for fstests will follow soon.

Fixes: dd5f9615 ("Btrfs: maintain subvolume items in the UUID tree")
CC: stable@vger.kernel.org # 3.12+
Reviewed-by: default avatarAnand Jain <asj@kernel.org>
Signed-off-by: default avatarFilipe Manana <fdmanana@suse.com>
Reviewed-by: default avatarDavid Sterba <dsterba@suse.com>
Signed-off-by: default avatarDavid Sterba <dsterba@suse.com>
parent e1b18b95
Loading
Loading
Loading
Loading
+19 −2
Original line number Diff line number Diff line
@@ -3929,6 +3929,25 @@ static long _btrfs_ioctl_set_received_subvol(struct file *file,
		goto out;
	}

	received_uuid_changed = memcmp(root_item->received_uuid, sa->uuid,
				       BTRFS_UUID_SIZE);

	/*
	 * Before we attempt to add the new received uuid, check if we have room
	 * for it in case there's already an item. If the size of the existing
	 * item plus this root's ID (u64) exceeds the maximum item size, we can
	 * return here without the need to abort a transaction. If we don't do
	 * this check, the btrfs_uuid_tree_add() call below would fail with
	 * -EOVERFLOW and result in a transaction abort. Malicious users could
	 * exploit this to turn the fs into RO mode.
	 */
	if (received_uuid_changed && !btrfs_is_empty_uuid(sa->uuid)) {
		ret = btrfs_uuid_tree_check_overflow(fs_info, sa->uuid,
						     BTRFS_UUID_KEY_RECEIVED_SUBVOL);
		if (ret < 0)
			goto out;
	}

	/*
	 * 1 - root item
	 * 2 - uuid items (received uuid + subvol uuid)
@@ -3944,8 +3963,6 @@ static long _btrfs_ioctl_set_received_subvol(struct file *file,
	sa->rtime.sec = ct.tv_sec;
	sa->rtime.nsec = ct.tv_nsec;

	received_uuid_changed = memcmp(root_item->received_uuid, sa->uuid,
				       BTRFS_UUID_SIZE);
	if (received_uuid_changed &&
	    !btrfs_is_empty_uuid(root_item->received_uuid)) {
		ret = btrfs_uuid_tree_remove(trans, root_item->received_uuid,
+38 −0
Original line number Diff line number Diff line
@@ -199,6 +199,44 @@ int btrfs_uuid_tree_remove(struct btrfs_trans_handle *trans, const u8 *uuid, u8
	return 0;
}

/*
 * Check if we can add one root ID to a UUID key.
 * If the key does not yet exists, we can, otherwise only if extended item does
 * not exceeds the maximum item size permitted by the leaf size.
 *
 * Returns 0 on success, negative value on error.
 */
int btrfs_uuid_tree_check_overflow(struct btrfs_fs_info *fs_info,
				   const u8 *uuid, u8 type)
{
	BTRFS_PATH_AUTO_FREE(path);
	int ret;
	u32 item_size;
	struct btrfs_key key;

	if (WARN_ON_ONCE(!fs_info->uuid_root))
		return -EINVAL;

	path = btrfs_alloc_path();
	if (!path)
		return -ENOMEM;

	btrfs_uuid_to_key(uuid, type, &key);
	ret = btrfs_search_slot(NULL, fs_info->uuid_root, &key, path, 0, 0);
	if (ret < 0)
		return ret;
	if (ret > 0)
		return 0;

	item_size = btrfs_item_size(path->nodes[0], path->slots[0]);

	if (sizeof(struct btrfs_item) + item_size + sizeof(u64) >
	    BTRFS_LEAF_DATA_SIZE(fs_info))
		return -EOVERFLOW;

	return 0;
}

static int btrfs_uuid_iter_rem(struct btrfs_root *uuid_root, u8 *uuid, u8 type,
			       u64 subid)
{
+2 −0
Original line number Diff line number Diff line
@@ -12,6 +12,8 @@ int btrfs_uuid_tree_add(struct btrfs_trans_handle *trans, const u8 *uuid, u8 typ
			u64 subid);
int btrfs_uuid_tree_remove(struct btrfs_trans_handle *trans, const u8 *uuid, u8 type,
			u64 subid);
int btrfs_uuid_tree_check_overflow(struct btrfs_fs_info *fs_info,
				   const u8 *uuid, u8 type);
int btrfs_uuid_tree_iterate(struct btrfs_fs_info *fs_info);
int btrfs_create_uuid_tree(struct btrfs_fs_info *fs_info);
int btrfs_uuid_scan_kthread(void *data);