Skip to content

Commit 87f2c46

Browse files
fdmananakdave
authored andcommitted
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: dd5f961 ("Btrfs: maintain subvolume items in the UUID tree") CC: [email protected] # 3.12+ Reviewed-by: Anand Jain <[email protected]> Signed-off-by: Filipe Manana <[email protected]> Reviewed-by: David Sterba <[email protected]> Signed-off-by: David Sterba <[email protected]>
1 parent e1b18b9 commit 87f2c46

3 files changed

Lines changed: 59 additions & 2 deletions

File tree

fs/btrfs/ioctl.c

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3929,6 +3929,25 @@ static long _btrfs_ioctl_set_received_subvol(struct file *file,
39293929
goto out;
39303930
}
39313931

3932+
received_uuid_changed = memcmp(root_item->received_uuid, sa->uuid,
3933+
BTRFS_UUID_SIZE);
3934+
3935+
/*
3936+
* Before we attempt to add the new received uuid, check if we have room
3937+
* for it in case there's already an item. If the size of the existing
3938+
* item plus this root's ID (u64) exceeds the maximum item size, we can
3939+
* return here without the need to abort a transaction. If we don't do
3940+
* this check, the btrfs_uuid_tree_add() call below would fail with
3941+
* -EOVERFLOW and result in a transaction abort. Malicious users could
3942+
* exploit this to turn the fs into RO mode.
3943+
*/
3944+
if (received_uuid_changed && !btrfs_is_empty_uuid(sa->uuid)) {
3945+
ret = btrfs_uuid_tree_check_overflow(fs_info, sa->uuid,
3946+
BTRFS_UUID_KEY_RECEIVED_SUBVOL);
3947+
if (ret < 0)
3948+
goto out;
3949+
}
3950+
39323951
/*
39333952
* 1 - root item
39343953
* 2 - uuid items (received uuid + subvol uuid)
@@ -3944,8 +3963,6 @@ static long _btrfs_ioctl_set_received_subvol(struct file *file,
39443963
sa->rtime.sec = ct.tv_sec;
39453964
sa->rtime.nsec = ct.tv_nsec;
39463965

3947-
received_uuid_changed = memcmp(root_item->received_uuid, sa->uuid,
3948-
BTRFS_UUID_SIZE);
39493966
if (received_uuid_changed &&
39503967
!btrfs_is_empty_uuid(root_item->received_uuid)) {
39513968
ret = btrfs_uuid_tree_remove(trans, root_item->received_uuid,

fs/btrfs/uuid-tree.c

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,44 @@ int btrfs_uuid_tree_remove(struct btrfs_trans_handle *trans, const u8 *uuid, u8
199199
return 0;
200200
}
201201

202+
/*
203+
* Check if we can add one root ID to a UUID key.
204+
* If the key does not yet exists, we can, otherwise only if extended item does
205+
* not exceeds the maximum item size permitted by the leaf size.
206+
*
207+
* Returns 0 on success, negative value on error.
208+
*/
209+
int btrfs_uuid_tree_check_overflow(struct btrfs_fs_info *fs_info,
210+
const u8 *uuid, u8 type)
211+
{
212+
BTRFS_PATH_AUTO_FREE(path);
213+
int ret;
214+
u32 item_size;
215+
struct btrfs_key key;
216+
217+
if (WARN_ON_ONCE(!fs_info->uuid_root))
218+
return -EINVAL;
219+
220+
path = btrfs_alloc_path();
221+
if (!path)
222+
return -ENOMEM;
223+
224+
btrfs_uuid_to_key(uuid, type, &key);
225+
ret = btrfs_search_slot(NULL, fs_info->uuid_root, &key, path, 0, 0);
226+
if (ret < 0)
227+
return ret;
228+
if (ret > 0)
229+
return 0;
230+
231+
item_size = btrfs_item_size(path->nodes[0], path->slots[0]);
232+
233+
if (sizeof(struct btrfs_item) + item_size + sizeof(u64) >
234+
BTRFS_LEAF_DATA_SIZE(fs_info))
235+
return -EOVERFLOW;
236+
237+
return 0;
238+
}
239+
202240
static int btrfs_uuid_iter_rem(struct btrfs_root *uuid_root, u8 *uuid, u8 type,
203241
u64 subid)
204242
{

fs/btrfs/uuid-tree.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ int btrfs_uuid_tree_add(struct btrfs_trans_handle *trans, const u8 *uuid, u8 typ
1212
u64 subid);
1313
int btrfs_uuid_tree_remove(struct btrfs_trans_handle *trans, const u8 *uuid, u8 type,
1414
u64 subid);
15+
int btrfs_uuid_tree_check_overflow(struct btrfs_fs_info *fs_info,
16+
const u8 *uuid, u8 type);
1517
int btrfs_uuid_tree_iterate(struct btrfs_fs_info *fs_info);
1618
int btrfs_create_uuid_tree(struct btrfs_fs_info *fs_info);
1719
int btrfs_uuid_scan_kthread(void *data);

0 commit comments

Comments
 (0)