diff options
author | Filipe Manana <fdmanana@suse.com> | 2018-11-19 16:20:34 +0000 |
---|---|---|
committer | Ben Hutchings <ben@decadent.org.uk> | 2019-02-11 17:54:09 +0000 |
commit | 87815e8b77904bd355e0ef2f81e1ee42bb68e6bb (patch) | |
tree | 98ae4c97504d44b7310525d47a1d9787c726cec3 /fs | |
parent | 974525d70ca66e0ff803741626aef6c171d4653c (diff) |
Btrfs: fix race between enabling quotas and subvolume creation
commit 552f0329c75b3e1d7f9bb8c9e421d37403f192cd upstream.
We have a race between enabling quotas end subvolume creation that cause
subvolume creation to fail with -EINVAL, and the following diagram shows
how it happens:
CPU 0 CPU 1
btrfs_ioctl()
btrfs_ioctl_quota_ctl()
btrfs_quota_enable()
mutex_lock(fs_info->qgroup_ioctl_lock)
btrfs_ioctl()
create_subvol()
btrfs_qgroup_inherit()
-> save fs_info->quota_root
into quota_root
-> stores a NULL value
-> tries to lock the mutex
qgroup_ioctl_lock
-> blocks waiting for
the task at CPU0
-> sets BTRFS_FS_QUOTA_ENABLED in fs_info
-> sets quota_root in fs_info->quota_root
(non-NULL value)
mutex_unlock(fs_info->qgroup_ioctl_lock)
-> checks quota enabled
flag is set
-> returns -EINVAL because
fs_info->quota_root was
NULL before it acquired
the mutex
qgroup_ioctl_lock
-> ioctl returns -EINVAL
Returning -EINVAL to user space will be confusing if all the arguments
passed to the subvolume creation ioctl were valid.
Fix it by grabbing the value from fs_info->quota_root after acquiring
the mutex.
Reviewed-by: Qu Wenruo <wqu@suse.com>
Signed-off-by: Filipe Manana <fdmanana@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
[bwh: Backported to 3.16: adjust context]
Signed-off-by: Ben Hutchings <ben@decadent.org.uk>
Diffstat (limited to 'fs')
-rw-r--r-- | fs/btrfs/qgroup.c | 3 |
1 files changed, 2 insertions, 1 deletions
diff --git a/fs/btrfs/qgroup.c b/fs/btrfs/qgroup.c index 0e32f21e0868..1ad1be17e9c2 100644 --- a/fs/btrfs/qgroup.c +++ b/fs/btrfs/qgroup.c @@ -2024,7 +2024,7 @@ int btrfs_qgroup_inherit(struct btrfs_trans_handle *trans, int ret = 0; int i; u64 *i_qgroups; - struct btrfs_root *quota_root = fs_info->quota_root; + struct btrfs_root *quota_root; struct btrfs_qgroup *srcgroup; struct btrfs_qgroup *dstgroup; u32 level_size = 0; @@ -2034,6 +2034,7 @@ int btrfs_qgroup_inherit(struct btrfs_trans_handle *trans, if (!fs_info->quota_enabled) goto out; + quota_root = fs_info->quota_root; if (!quota_root) { ret = -EINVAL; goto out; |