mirror of
git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-03-22 07:27:12 +08:00
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: dd5f9615fc ("Btrfs: maintain subvolume items in the UUID tree")
CC: stable@vger.kernel.org # 3.12+
Reviewed-by: Anand Jain <asj@kernel.org>
Signed-off-by: Filipe Manana <fdmanana@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
This commit is contained in:
committed by
David Sterba
parent
e1b18b9590
commit
87f2c46003
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user