]> git.ipfire.org Git - thirdparty/kernel/stable.git/commitdiff
btrfs: fix transaction abort on set received ioctl due to item overflow
authorFilipe Manana <fdmanana@suse.com>
Thu, 26 Feb 2026 23:41:07 +0000 (23:41 +0000)
committerDavid Sterba <dsterba@suse.com>
Tue, 3 Mar 2026 16:03:59 +0000 (17:03 +0100)
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: dd5f9615fc5c ("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>
fs/btrfs/ioctl.c
fs/btrfs/uuid-tree.c
fs/btrfs/uuid-tree.h

index 1d22c5c05b50e0604252e6724d9bdb4ee4082039..56d17eedaf903b7a3ecde04083dcc0ec1bb14454 100644 (file)
@@ -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,
index f24c14b9bb2fd7420b06263a5a0c4b889a859bc6..43c17a1d34513c00410b041bf9f005b5b414ff4f 100644 (file)
@@ -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)
 {
index c60ad20325cce03450b8abc029468f6f79670aa4..02b235a3653f069c51456fbd5e7327ed8b6f39c6 100644 (file)
@@ -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);