btrfs: prevent subvol with swapfile from being deleted

commit 60021bd754 upstream.

A subvolume with an active swapfile must not be deleted otherwise it
would not be possible to deactivate it.

After the subvolume is deleted, we cannot swapoff the swapfile in this
deleted subvolume because the path is unreachable.  The swapfile is
still active and holding references, the filesystem cannot be unmounted.

The test looks like this:

  mkfs.btrfs -f $dev > /dev/null
  mount $dev $mnt

  btrfs sub create $mnt/subvol
  touch $mnt/subvol/swapfile
  chmod 600 $mnt/subvol/swapfile
  chattr +C $mnt/subvol/swapfile
  dd if=/dev/zero of=$mnt/subvol/swapfile bs=1K count=4096
  mkswap $mnt/subvol/swapfile
  swapon $mnt/subvol/swapfile

  btrfs sub delete $mnt/subvol
  swapoff $mnt/subvol/swapfile  # failed: No such file or directory
  swapoff --all

  unmount $mnt                  # target is busy.

To prevent above issue, we simply check that whether the subvolume
contains any active swapfile, and stop the deleting process.  This
behavior is like snapshot ioctl dealing with a swapfile.

CC: stable@vger.kernel.org # 5.4+
Reviewed-by: Robbie Ko <robbieko@synology.com>
Reviewed-by: Qu Wenruo <wqu@suse.com>
Reviewed-by: Filipe Manana <fdmanana@suse.com>
Signed-off-by: Kaiwen Hu <kevinhu@synology.com>
Signed-off-by: David Sterba <dsterba@suse.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
This commit is contained in:
Kaiwen Hu 2022-03-23 15:10:32 +08:00 коммит произвёл Greg Kroah-Hartman
Родитель 4b98799e18
Коммит 887366faf0
1 изменённых файлов: 22 добавлений и 0 удалений

Просмотреть файл

@ -4450,6 +4450,13 @@ int btrfs_delete_subvolume(struct inode *dir, struct dentry *dentry)
dest->root_key.objectid); dest->root_key.objectid);
return -EPERM; return -EPERM;
} }
if (atomic_read(&dest->nr_swapfiles)) {
spin_unlock(&dest->root_item_lock);
btrfs_warn(fs_info,
"attempt to delete subvolume %llu with active swapfile",
root->root_key.objectid);
return -EPERM;
}
root_flags = btrfs_root_flags(&dest->root_item); root_flags = btrfs_root_flags(&dest->root_item);
btrfs_set_root_flags(&dest->root_item, btrfs_set_root_flags(&dest->root_item,
root_flags | BTRFS_ROOT_SUBVOL_DEAD); root_flags | BTRFS_ROOT_SUBVOL_DEAD);
@ -10721,8 +10728,23 @@ static int btrfs_swap_activate(struct swap_info_struct *sis, struct file *file,
* set. We use this counter to prevent snapshots. We must increment it * set. We use this counter to prevent snapshots. We must increment it
* before walking the extents because we don't want a concurrent * before walking the extents because we don't want a concurrent
* snapshot to run after we've already checked the extents. * snapshot to run after we've already checked the extents.
*
* It is possible that subvolume is marked for deletion but still not
* removed yet. To prevent this race, we check the root status before
* activating the swapfile.
*/ */
spin_lock(&root->root_item_lock);
if (btrfs_root_dead(root)) {
spin_unlock(&root->root_item_lock);
btrfs_exclop_finish(fs_info);
btrfs_warn(fs_info,
"cannot activate swapfile because subvolume %llu is being deleted",
root->root_key.objectid);
return -EPERM;
}
atomic_inc(&root->nr_swapfiles); atomic_inc(&root->nr_swapfiles);
spin_unlock(&root->root_item_lock);
isize = ALIGN_DOWN(inode->i_size, fs_info->sectorsize); isize = ALIGN_DOWN(inode->i_size, fs_info->sectorsize);