Skip to content

Commit 8e0d825

Browse files
fdmananaopsiff
authored andcommitted
btrfs: fix transaction abort on set received ioctl due to item overflow
[ Upstream commit 87f2c46003fce4d739138aab4af1942b1afdadac ] 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: 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> [ adapted BTRFS_PATH_AUTO_FREE macro to manual btrfs_free_path calls ] Signed-off-by: Sasha Levin <sashal@kernel.org> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> (cherry picked from commit b19c0465e4daad5aa8f60552ea0578cf31a11b1e) Signed-off-by: Wentao Guan <guanwentao@uniontech.com>
1 parent 32b3bdc commit 8e0d825

3 files changed

Lines changed: 64 additions & 2 deletions

File tree

fs/btrfs/ioctl.c

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3999,6 +3999,25 @@ static long _btrfs_ioctl_set_received_subvol(struct file *file,
39993999
goto out;
40004000
}
40014001

4002+
received_uuid_changed = memcmp(root_item->received_uuid, sa->uuid,
4003+
BTRFS_UUID_SIZE);
4004+
4005+
/*
4006+
* Before we attempt to add the new received uuid, check if we have room
4007+
* for it in case there's already an item. If the size of the existing
4008+
* item plus this root's ID (u64) exceeds the maximum item size, we can
4009+
* return here without the need to abort a transaction. If we don't do
4010+
* this check, the btrfs_uuid_tree_add() call below would fail with
4011+
* -EOVERFLOW and result in a transaction abort. Malicious users could
4012+
* exploit this to turn the fs into RO mode.
4013+
*/
4014+
if (received_uuid_changed && !btrfs_is_empty_uuid(sa->uuid)) {
4015+
ret = btrfs_uuid_tree_check_overflow(fs_info, sa->uuid,
4016+
BTRFS_UUID_KEY_RECEIVED_SUBVOL);
4017+
if (ret < 0)
4018+
goto out;
4019+
}
4020+
40024021
/*
40034022
* 1 - root item
40044023
* 2 - uuid items (received uuid + subvol uuid)
@@ -4014,8 +4033,6 @@ static long _btrfs_ioctl_set_received_subvol(struct file *file,
40144033
sa->rtime.sec = ct.tv_sec;
40154034
sa->rtime.nsec = ct.tv_nsec;
40164035

4017-
received_uuid_changed = memcmp(root_item->received_uuid, sa->uuid,
4018-
BTRFS_UUID_SIZE);
40194036
if (received_uuid_changed &&
40204037
!btrfs_is_empty_uuid(root_item->received_uuid)) {
40214038
ret = btrfs_uuid_tree_remove(trans, root_item->received_uuid,

fs/btrfs/uuid-tree.c

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,49 @@ int btrfs_uuid_tree_remove(struct btrfs_trans_handle *trans, u8 *uuid, u8 type,
228228
return ret;
229229
}
230230

231+
/*
232+
* Check if we can add one root ID to a UUID key.
233+
* If the key does not yet exists, we can, otherwise only if extended item does
234+
* not exceeds the maximum item size permitted by the leaf size.
235+
*
236+
* Returns 0 on success, negative value on error.
237+
*/
238+
int btrfs_uuid_tree_check_overflow(struct btrfs_fs_info *fs_info,
239+
u8 *uuid, u8 type)
240+
{
241+
struct btrfs_path *path;
242+
int ret;
243+
u32 item_size;
244+
struct btrfs_key key;
245+
246+
if (WARN_ON_ONCE(!fs_info->uuid_root))
247+
return -EINVAL;
248+
249+
path = btrfs_alloc_path();
250+
if (!path)
251+
return -ENOMEM;
252+
253+
btrfs_uuid_to_key(uuid, type, &key);
254+
ret = btrfs_search_slot(NULL, fs_info->uuid_root, &key, path, 0, 0);
255+
if (ret < 0) {
256+
btrfs_free_path(path);
257+
return ret;
258+
}
259+
if (ret > 0) {
260+
btrfs_free_path(path);
261+
return 0;
262+
}
263+
264+
item_size = btrfs_item_size(path->nodes[0], path->slots[0]);
265+
btrfs_free_path(path);
266+
267+
if (sizeof(struct btrfs_item) + item_size + sizeof(u64) >
268+
BTRFS_LEAF_DATA_SIZE(fs_info))
269+
return -EOVERFLOW;
270+
271+
return 0;
272+
}
273+
231274
static int btrfs_uuid_iter_rem(struct btrfs_root *uuid_root, u8 *uuid, u8 type,
232275
u64 subid)
233276
{

fs/btrfs/uuid-tree.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ int btrfs_uuid_tree_add(struct btrfs_trans_handle *trans, u8 *uuid, u8 type,
77
u64 subid);
88
int btrfs_uuid_tree_remove(struct btrfs_trans_handle *trans, u8 *uuid, u8 type,
99
u64 subid);
10+
int btrfs_uuid_tree_check_overflow(struct btrfs_fs_info *fs_info,
11+
u8 *uuid, u8 type);
1012
int btrfs_uuid_tree_iterate(struct btrfs_fs_info *fs_info);
1113

1214
#endif

0 commit comments

Comments
 (0)