]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
btrfs: deny freezing a device while it is being removed
authorChristian Brauner <brauner@kernel.org>
Tue, 16 Jun 2026 11:58:16 +0000 (13:58 +0200)
committerChristian Brauner <brauner@kernel.org>
Mon, 29 Jun 2026 08:31:49 +0000 (10:31 +0200)
btrfs_rm_device() runs under mnt_want_write_file(), but the claim on the
removed device is released by the ioctl after mnt_drop_write_file(), so a
bdev_freeze() racing that window could freeze the filesystem through the
device just as its claim is torn down, leaving nothing for bdev_thaw() to
rebalance.

The window cannot be closed by reordering the teardown.  btrfs_rm_device()
hands the final bdev_fput() back to the ioctl, run only after
mnt_drop_write_file(), because bdev_release() takes the disk ->open_mutex and
its dependency chain, which must not nest under the superblock's freeze/write
protection -- freeze_super() drops s_umount before draining writers precisely
to keep sb_start_write ordered above s_umount.  Holding mnt_want_write across
bdev_fput() would reintroduce that inversion, so the holder teardown is forced
outside the write-protected section.  A freeze landing in the resulting gap
resolves the still-live holder, rides in, and strands when the claim is
released; no ordering of the close against the drop removes the gap.  The
device itself therefore has to refuse freezing for the whole removal.

Deny freezing the device for the duration of the removal: bdev_deny_freeze()
at the start of btrfs_rm_device() (it cannot be frozen yet, the ioctl holds
the write count), and release it through btrfs_release_device_allow_freeze()
in the ioctls on success, or bdev_allow_freeze() on the error paths that keep
the device a member.  A device frozen before the removal begins is refused
with -EBUSY.

btrfs_release_device_allow_freeze() yields the holder, re-allows freezing,
then closes the device, so the re-allow neither strands the filesystem on a
racing freeze nor touches the block device after the final fput.

Link: https://patch.msgid.link/20260616-work-super-freeze_deny_upstream-v2-3-b3567c7f994b@kernel.org
Reviewed-by: Johannes Thumshirn <johannes.thumshirn@wdc.com>
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
fs/btrfs/ioctl.c
fs/btrfs/volumes.c
fs/btrfs/volumes.h

index 9d47d16394fc569fa62a4769eeff0aaab16fe252..26ebfac37952916eef21a3e9b488ba6f1a3f2ddb 100644 (file)
@@ -2622,7 +2622,7 @@ static long btrfs_ioctl_rm_dev_v2(struct file *file, void __user *arg)
 err_drop:
        mnt_drop_write_file(file);
        if (bdev_file)
-               bdev_fput(bdev_file);
+               btrfs_release_device_allow_freeze(bdev_file);
 out:
        btrfs_put_dev_args_from_path(&args);
        return ret;
@@ -2672,7 +2672,7 @@ static long btrfs_ioctl_rm_dev(struct file *file, void __user *arg)
 
        mnt_drop_write_file(file);
        if (bdev_file)
-               bdev_fput(bdev_file);
+               btrfs_release_device_allow_freeze(bdev_file);
 out:
        btrfs_put_dev_args_from_path(&args);
        return ret;
index 6eab4cc73ce44c8d935a57515ebefb6a73ac1dd8..6a0d22b6ce056bcc944529be62684e7da74f08a6 100644 (file)
@@ -1124,6 +1124,15 @@ void btrfs_free_extra_devids(struct btrfs_fs_devices *fs_devices)
        mutex_unlock(&uuid_mutex);
 }
 
+/* Release a device that was made unfreezable for a membership change. */
+void btrfs_release_device_allow_freeze(struct file *bdev_file)
+{
+       /* Yield before allow (strand-safe); file still open for the allow (UAF-safe). */
+       bdev_yield_claim(bdev_file);
+       bdev_allow_freeze(file_bdev(bdev_file));
+       bdev_fput(bdev_file);
+}
+
 static void btrfs_close_bdev(struct btrfs_device *device)
 {
        if (!device->bdev)
@@ -2373,6 +2382,13 @@ int btrfs_rm_device(struct btrfs_fs_info *fs_info,
            fs_info->fs_devices->rw_devices == 1)
                return BTRFS_ERROR_DEV_ONLY_WRITABLE;
 
+       /* Removal and freezing are mutually exclusive; refuse if frozen now. */
+       if (device->bdev) {
+               ret = bdev_deny_freeze(device->bdev);
+               if (ret)
+                       return ret;
+       }
+
        if (test_bit(BTRFS_DEV_STATE_WRITEABLE, &device->dev_state)) {
                mutex_lock(&fs_info->chunk_mutex);
                list_del_init(&device->dev_alloc_list);
@@ -2399,6 +2415,8 @@ int btrfs_rm_device(struct btrfs_fs_info *fs_info,
                           device->devid, ret);
                btrfs_abort_transaction(trans, ret);
                btrfs_end_transaction(trans);
+               if (device->bdev)
+                       bdev_allow_freeze(device->bdev);
                return ret;
        }
 
@@ -2490,6 +2508,8 @@ int btrfs_rm_device(struct btrfs_fs_info *fs_info,
        return btrfs_commit_transaction(trans);
 
 error_undo:
+       if (device->bdev)
+               bdev_allow_freeze(device->bdev);
        if (test_bit(BTRFS_DEV_STATE_WRITEABLE, &device->dev_state)) {
                mutex_lock(&fs_info->chunk_mutex);
                list_add(&device->dev_alloc_list,
index 63be45c3298ca4bde476c4b264be43c3b264f362..f8d3fd4dab555b4e815e8433b8d74ebafa0d1895 100644 (file)
@@ -744,6 +744,7 @@ int btrfs_open_devices(struct btrfs_fs_devices *fs_devices,
 struct btrfs_device *btrfs_scan_one_device(const char *path, bool mount_arg_dev);
 int btrfs_forget_devices(dev_t devt);
 void btrfs_close_devices(struct btrfs_fs_devices *fs_devices);
+void btrfs_release_device_allow_freeze(struct file *bdev_file);
 void btrfs_free_extra_devids(struct btrfs_fs_devices *fs_devices);
 void btrfs_assign_next_active_device(struct btrfs_device *device,
                                     struct btrfs_device *this_dev);