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
1 changed files with 13 additions and 9 deletions
|
|
@ -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 @@ out_end_trans:
|
||||||
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…
Add table
Add a link
Reference in a new issue