btrfs: avoid copying BTRFS_ROOT_SUBVOL_DEAD flag to snapshot of subvolume being deleted
commit 3324d0547861b16cf436d54abba7052e0c8aa9de upstream. 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> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
This commit is contained in:
parent
d9c54763e5
commit
9ebd514fbd
@ -4689,6 +4689,8 @@ int btrfs_delete_subvolume(struct 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
|
||||||
@ -4700,25 +4702,25 @@ int btrfs_delete_subvolume(struct 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);
|
||||||
/*
|
/*
|
||||||
@ -4728,7 +4730,7 @@ int btrfs_delete_subvolume(struct 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)) {
|
||||||
@ -4794,15 +4796,17 @@ int btrfs_delete_subvolume(struct 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 New Issue
Block a user