mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-09-29 13:53:33 +00:00
btrfs: avoid copying BTRFS_ROOT_SUBVOL_DEAD flag to snapshot of subvolume being deleted
Sweet Tea spotted a race between subvolume deletion and snapshotting that can result in the root item for the snapshot having the BTRFS_ROOT_SUBVOL_DEAD flag set. The race is: Thread 1 | Thread 2 ----------------------------------------------|---------- btrfs_delete_subvolume | btrfs_set_root_flags(BTRFS_ROOT_SUBVOL_DEAD)| |btrfs_mksubvol | down_read(subvol_sem) | create_snapshot | ... | create_pending_snapshot | copy root item from source down_write(subvol_sem) | This flag is only checked in send and swap activate, which this would cause to fail mysteriously. create_snapshot() now checks the root refs to reject a deleted subvolume, so we can fix this by locking subvol_sem earlier so that the BTRFS_ROOT_SUBVOL_DEAD flag and the root refs are updated atomically. CC: stable@vger.kernel.org # 4.14+ Reported-by: Sweet Tea Dorminy <sweettea-kernel@dorminy.me> Reviewed-by: Sweet Tea Dorminy <sweettea-kernel@dorminy.me> Reviewed-by: Anand Jain <anand.jain@oracle.com> Signed-off-by: Omar Sandoval <osandov@fb.com> Reviewed-by: David Sterba <dsterba@suse.com> Signed-off-by: David Sterba <dsterba@suse.com>
This commit is contained in:
parent
7081929ab2
commit
3324d05478
1 changed files with 13 additions and 9 deletions
|
@ -4458,6 +4458,8 @@ int btrfs_delete_subvolume(struct btrfs_inode *dir, struct dentry *dentry)
|
||||||
u64 root_flags;
|
u64 root_flags;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
|
down_write(&fs_info->subvol_sem);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Don't allow to delete a subvolume with send in progress. This is
|
* Don't allow to delete a subvolume with send in progress. This is
|
||||||
* inside the inode lock so the error handling that has to drop the bit
|
* inside the inode lock so the error handling that has to drop the bit
|
||||||
|
@ -4469,25 +4471,25 @@ int btrfs_delete_subvolume(struct btrfs_inode *dir, struct dentry *dentry)
|
||||||
btrfs_warn(fs_info,
|
btrfs_warn(fs_info,
|
||||||
"attempt to delete subvolume %llu during send",
|
"attempt to delete subvolume %llu during send",
|
||||||
dest->root_key.objectid);
|
dest->root_key.objectid);
|
||||||
return -EPERM;
|
ret = -EPERM;
|
||||||
|
goto out_up_write;
|
||||||
}
|
}
|
||||||
if (atomic_read(&dest->nr_swapfiles)) {
|
if (atomic_read(&dest->nr_swapfiles)) {
|
||||||
spin_unlock(&dest->root_item_lock);
|
spin_unlock(&dest->root_item_lock);
|
||||||
btrfs_warn(fs_info,
|
btrfs_warn(fs_info,
|
||||||
"attempt to delete subvolume %llu with active swapfile",
|
"attempt to delete subvolume %llu with active swapfile",
|
||||||
root->root_key.objectid);
|
root->root_key.objectid);
|
||||||
return -EPERM;
|
ret = -EPERM;
|
||||||
|
goto out_up_write;
|
||||||
}
|
}
|
||||||
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);
|
||||||
spin_unlock(&dest->root_item_lock);
|
spin_unlock(&dest->root_item_lock);
|
||||||
|
|
||||||
down_write(&fs_info->subvol_sem);
|
|
||||||
|
|
||||||
ret = may_destroy_subvol(dest);
|
ret = may_destroy_subvol(dest);
|
||||||
if (ret)
|
if (ret)
|
||||||
goto out_up_write;
|
goto out_undead;
|
||||||
|
|
||||||
btrfs_init_block_rsv(&block_rsv, BTRFS_BLOCK_RSV_TEMP);
|
btrfs_init_block_rsv(&block_rsv, BTRFS_BLOCK_RSV_TEMP);
|
||||||
/*
|
/*
|
||||||
|
@ -4497,7 +4499,7 @@ int btrfs_delete_subvolume(struct btrfs_inode *dir, struct dentry *dentry)
|
||||||
*/
|
*/
|
||||||
ret = btrfs_subvolume_reserve_metadata(root, &block_rsv, 5, true);
|
ret = btrfs_subvolume_reserve_metadata(root, &block_rsv, 5, true);
|
||||||
if (ret)
|
if (ret)
|
||||||
goto out_up_write;
|
goto out_undead;
|
||||||
|
|
||||||
trans = btrfs_start_transaction(root, 0);
|
trans = btrfs_start_transaction(root, 0);
|
||||||
if (IS_ERR(trans)) {
|
if (IS_ERR(trans)) {
|
||||||
|
@ -4563,15 +4565,17 @@ int btrfs_delete_subvolume(struct btrfs_inode *dir, struct dentry *dentry)
|
||||||
inode->i_flags |= S_DEAD;
|
inode->i_flags |= S_DEAD;
|
||||||
out_release:
|
out_release:
|
||||||
btrfs_subvolume_release_metadata(root, &block_rsv);
|
btrfs_subvolume_release_metadata(root, &block_rsv);
|
||||||
out_up_write:
|
out_undead:
|
||||||
up_write(&fs_info->subvol_sem);
|
|
||||||
if (ret) {
|
if (ret) {
|
||||||
spin_lock(&dest->root_item_lock);
|
spin_lock(&dest->root_item_lock);
|
||||||
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);
|
||||||
spin_unlock(&dest->root_item_lock);
|
spin_unlock(&dest->root_item_lock);
|
||||||
} else {
|
}
|
||||||
|
out_up_write:
|
||||||
|
up_write(&fs_info->subvol_sem);
|
||||||
|
if (!ret) {
|
||||||
d_invalidate(dentry);
|
d_invalidate(dentry);
|
||||||
btrfs_prune_dentries(dest);
|
btrfs_prune_dentries(dest);
|
||||||
ASSERT(dest->send_in_progress == 0);
|
ASSERT(dest->send_in_progress == 0);
|
||||||
|
|
Loading…
Reference in a new issue