| From 60021bd754c6ca0addc6817994f20290a321d8d6 Mon Sep 17 00:00:00 2001 |
| From: Kaiwen Hu <kevinhu@synology.com> |
| Date: Wed, 23 Mar 2022 15:10:32 +0800 |
| Subject: btrfs: prevent subvol with swapfile from being deleted |
| |
| From: Kaiwen Hu <kevinhu@synology.com> |
| |
| commit 60021bd754c6ca0addc6817994f20290a321d8d6 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> |
| --- |
| fs/btrfs/inode.c | 24 +++++++++++++++++++++++- |
| 1 file changed, 23 insertions(+), 1 deletion(-) |
| |
| --- a/fs/btrfs/inode.c |
| +++ b/fs/btrfs/inode.c |
| @@ -4462,6 +4462,13 @@ int btrfs_delete_subvolume(struct inode |
| dest->root_key.objectid); |
| 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); |
| btrfs_set_root_flags(&dest->root_item, |
| root_flags | BTRFS_ROOT_SUBVOL_DEAD); |
| @@ -10764,8 +10771,23 @@ static int btrfs_swap_activate(struct sw |
| * set. We use this counter to prevent snapshots. We must increment it |
| * before walking the extents because we don't want a concurrent |
| * 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); |
| + spin_unlock(&root->root_item_lock); |
| |
| isize = ALIGN_DOWN(inode->i_size, fs_info->sectorsize); |
| |