Fix locking when scanning devices
David Sterba
maintainercover Fix locking when scanning devices
This patchset fixes the bugs recently reported by syzbot. I've tried to use patches from Anand [1] to fix that but in the end there were fixes not suitable for merging to 4.18 and my final fix took a different approach.
In short, fs_devices::opened is protected by uuid_mutex and this mutex can be used to exclude mount and scanning to interfere.
The fstests pass and 2 syzbot reproducers reported no problems. I'd like to push the patchset to 4.18 but not rc2 as it's too close. I'll add the patchset to for-next soon if there are no major problems found, but otherwise I'm open to comments.
[1]
https://patchwork.kernel.org/patch/10446779/
https://patchwork.kernel.org/patch/10437707/ 1-6
David Sterba (7):
btrfs: restore uuid_mutex in btrfs_open_devices
btrfs: extend critical section when scanning a new device
btrfs: lift uuid_mutex to callers of btrfs_scan_one_device
btrfs: lift uuid_mutex to callers of btrfs_open_devices
btrfs: lift uuid_mutex to callers of btrfs_parse_early_options
btrfs: reorder initialization before the mount locks uuid_mutex
btrfs: fix mount and ioctl device scan ioctl race
fs/btrfs/super.c | 38 +++++++++++++++++++++++++------------- fs/btrfs/volumes.c | 11 ++++++----- 2 files changed, 31 insertions(+), 18 deletions(-)
Signature · 1 line
2.17.0
David Sterba
maintainerPATCH 1/7 btrfs: restore uuid_mutex in btrfs_open_devices
Commit 542c5908abfe84f7b4c1 ("btrfs: replace uuid_mutex by device_list_mutex in btrfs_open_devices") switched to device_list_mutex as we need that for the device list traversal, but we also need uuid_mutex to protect access to fs_devices::opened to be consistent with other users of that item.
--- fs/btrfs/volumes.c | 2 ++ 1 file changed, 2 insertions(+)
fs/btrfs/volumes.c +2 -0
@@ -1146,6 +1146,7 @@ int btrfs_open_devices(struct btrfs_fs_devices *fs_devices, {int ret; +mutex_lock (&uuid_mutex);mutex_lock (&fs_devices->device_list_mutex);if (fs_devices->opened) { fs_devices->opened++;
@@ -1155,6 +1156,7 @@ int btrfs_open_devices(struct btrfs_fs_devices *fs_devices, ret =open_fs_devices (fs_devices, flags, holder); }mutex_unlock (&fs_devices->device_list_mutex); +mutex_unlock (&uuid_mutex);return ret; }
Signature · 1 line
2.17.0
Re: [PATCH 1/7] btrfs: restore uuid_mutex in btrfs_open_devices
Quoted 8 lines · David Sterba
Commit
542c5908abfe84f7b4c1("btrfs: replaceuuid_mutexbydevice_list_mutexinbtrfs_open_devices") switched todevice_list_mutexas we need that for the device list traversal, but we also needuuid_mutexto protect access tofs_devices::openedto be consistent with other users of that item.CC Anand Jain <[email protected]>Signed-off-by David Sterba <[email protected]>
I am optimize the uuid_mutex for fsids concurrency on top these set of
patches, so.
Thanks, Anand
Quoted 25 lines
--- fs/btrfs/volumes.c | 2 ++ 1 file changed, 2 insertions(+)fs/btrfs/volumes.c +2
-0@@ -1146,6 +1146,7 @@ int btrfs_open_devices(struct btrfs_fs_devices *fs_devices, {int ret; +mutex_lock (&uuid_mutex);mutex_lock (&fs_devices->device_list_mutex);if (fs_devices->opened) { fs_devices->opened++;@@ -1155,6 +1156,7 @@ int btrfs_open_devices(struct btrfs_fs_devices *fs_devices, ret =open_fs_devices (fs_devices, flags, holder); }mutex_unlock (&fs_devices->device_list_mutex); +mutex_unlock (&uuid_mutex);return ret; }
Re: [PATCH 1/7] btrfs: restore uuid_mutex in btrfs_open_devices
Quoted 7 lines · Anand Jain
Quoted 6 lines · David Sterba
Commit
542c5908abfe84f7b4c1("btrfs: replaceuuid_mutexbydevice_list_mutexinbtrfs_open_devices") switched todevice_list_mutexas we need that for the device list traversal, but we also needuuid_mutexto protect access tofs_devices::openedto be consistent with other users of that item.
Quoted 7 lines
Quoted 2 lines
CC Anand Jain <[email protected]>Signed-off-by David Sterba <[email protected]>I am optimize the
uuid_mutexfor fsids concurrency on top these set of
patches, so.Reviewed-by Anand Jain <[email protected]>
So this should go to 4.18, instead of reverting 542c5908abfe8.
David Sterba
maintainerPATCH 2/7 btrfs: extend critical section when scanning a new device
In reply to David SterbaThe stale device list removal needs to be protected by device_list_mutex too as this could delete from the list and could race with another list modification and cause crash.
The device needs to be fully initialized before it's added to the list so the fs_devices also need to be set under the mutex.
--- fs/btrfs/volumes.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-)
fs/btrfs/volumes.c +2 -3
@@ -791,12 +791,11 @@ static noinline struct btrfs_device *device_list_add(const char *path,rcu_assign_pointer (device->name, name);mutex_lock (&fs_devices->device_list_mutex); + device->fs_devices = fs_devices;list_add_rcu (&device->dev_list, &fs_devices->devices); fs_devices->num_devices++; -mutex_unlock (&fs_devices->device_list_mutex); - - device->fs_devices = fs_devices;btrfs_free_stale_devices (path, device); +mutex_unlock (&fs_devices->device_list_mutex);if (disk_super->label[0 ]) pr_info("BTRFS: device label %s devid %llu transid %llu %s\n",
Signature · 1 line
2.17.0
Re: [PATCH 2/7] btrfs: extend critical section when scanning a new device
Quoted 3 lines · David Sterba
The stale device list removal needs to be protected by
device_list_mutextoo as this could delete from the list and could race with another list modification and cause crash.
Its is very less likely or almost practically impossible (unless instrumented) to have same device with same fsid but with different devid, which then it would delete the device which is under the same fs_devices which device_list_add just operated. Otherwise in most of the common cases it shall delete the device which does not belong to the fs_devices which the device_list_add() just added a device. So fs_devices::device_list_mutex lock won't help.
I have few patches to fix this. I shall send it. But then it was all based on the atomic volume exclusive flag which in stages I had plans to replace fs_info::BTRFS_FS_EXCL_OP with volume exclusive flag as well.
Also we need locks to manage with in btrfs_free_stale_devices() so that we can reuse this code to support user land forget device feature, (38cf665d338fca33af4b16f9ec7cad6637fc0fec btrfs: make btrfs_free_stale_device() to iterate all stales).
Thanks, Anand
Quoted 28 lines
The device needs to be fully initialized before it's added to the list so the
fs_devicesalso need to be set under the mutex.Signed-off-by David Sterba <[email protected]>--- fs/btrfs/volumes.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-)fs/btrfs/volumes.c +2
-3@@ -791,12 +791,11 @@ static noinline struct btrfs_device *device_list_add(const char *path,rcu_assign_pointer (device->name, name);mutex_lock (&fs_devices->device_list_mutex); + device->fs_devices = fs_devices;list_add_rcu (&device->dev_list, &fs_devices->devices); fs_devices->num_devices++; -mutex_unlock (&fs_devices->device_list_mutex); - - device->fs_devices = fs_devices;btrfs_free_stale_devices (path, device); +mutex_unlock (&fs_devices->device_list_mutex);if (disk_super->label[0 ]) pr_info("BTRFS: device label %s devid %llu transid %llu %s\n",
Re: [PATCH 2/7] btrfs: extend critical section when scanning a new device
In reply to David SterbaQuoted 17 lines · David Sterba
The stale device list removal needs to be protected by
device_list_mutextoo as this could delete from the list and could race with another list modification and cause crash.The device needs to be fully initialized before it's added to the list so the
fs_devicesalso need to be set under the mutex.Signed-off-by David Sterba <[email protected]>--- fs/btrfs/volumes.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-)fs/btrfs/volumes.c +0
-0
fs/btrfs/volumes.c static noinline struct btrfs_device *device_list_add(const char *path, · line 797
Quoted 10 lines
fs/btrfs/volumes.c +1
-3@@ -791,12 +791,11 @@ static noinline struct btrfs_device *device_list_add(const char *path,rcu_assign_pointer (device->name, name);mutex_lock (&fs_devices->device_list_mutex); + device->fs_devices = fs_devices;list_add_rcu (&device->dev_list, &fs_devices->devices); fs_devices->num_devices++; -mutex_unlock (&fs_devices->device_list_mutex); - - device->fs_devices = fs_devices;btrfs_free_stale_devices (path, device);
This is not correct.
btrfs_free_stale_devices need the per fs_devices local device_list_mutex lock as it traverses through the fs_uuids list. Holding just the lock of the %fs_devices which is being scanned or mounted is not correct.
In my workspace I have replaced this patch with ..
4130724ec926 btrfs: fix btrfs_free_stale_devices() with needed locks 90fedec0c200 btrfs: btrfs_free_stale_devices() rename local variables 439eec4e943c btrfs: fix device_list_add() missing device_list_mutex() 417ecc91e223 btrfs: do btrfs_free_stale_devices() outside ofdevice_list_add()
which are in the ML as well.
Thanks, Anand
Quoted 5 lines
fs/btrfs/volumes.c +1
-0+mutex_unlock (&fs_devices->device_list_mutex);if (disk_super->label[0 ]) pr_info("BTRFS: device label %s devid %llu transid %llu %s\n",
Re: [PATCH 2/7] btrfs: extend critical section when scanning a new device
Quoted 34 lines · Anand Jain
Quoted 17 lines · David Sterba
The stale device list removal needs to be protected by
device_list_mutextoo as this could delete from the list and could race with another list modification and cause crash.The device needs to be fully initialized before it's added to the list so the
fs_devicesalso need to be set under the mutex.Signed-off-by David Sterba <[email protected]>--- fs/btrfs/volumes.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-)fs/btrfs/volumes.c +0
-0fs/btrfs/volumes.c static noinline struct btrfs_device *device_list_add(const char *path, · line 797
Quoted 10 lines
fs/btrfs/volumes.c +1
-3@@ -791,12 +791,11 @@ static noinline struct btrfs_device *device_list_add(const char *path,rcu_assign_pointer (device->name, name);mutex_lock (&fs_devices->device_list_mutex); + device->fs_devices = fs_devices;list_add_rcu (&device->dev_list, &fs_devices->devices); fs_devices->num_devices++; -mutex_unlock (&fs_devices->device_list_mutex); - - device->fs_devices = fs_devices;btrfs_free_stale_devices (path, device);This is not correct.
Quoted 3 lines
btrfs_free_stale_devicesneed the perfs_deviceslocaldevice_list_mutexlock as it traverses through thefs_uuidslist. Holding just the lock of the %fs_devices which is being scanned or mounted is not correct.
I see, ok. The use of fs_devs is not very visible in btrfs_free_stale_devices but at least the function comment hints that all devices are being traversed.
David Sterba
maintainerPATCH 3/7 btrfs: lift uuid_mutex to callers of btrfs_scan_one_device
In reply to David SterbaPrepartory work to fix race between mount and device scan.
The callers will have to manage the critical section, eg. mount wants to scan and then call btrfs_open_devices without the ioctl scan walking in and modifying the fs devices in the meantime.
--- fs/btrfs/super.c | 12 +++++++++++- fs/btrfs/volumes.c | 4 ++-- 2 files changed, 13 insertions(+), 3 deletions(-)
fs/btrfs/super.c +11 -1
@@ -917,8 +917,10 @@ static int btrfs_parse_early_options(const char *options, fmode_t flags, error = -ENOMEM ; goto out; } +mutex_lock (&uuid_mutex); error =btrfs_scan_one_device (device_name, flags, holder, fs_devices); +mutex_unlock (&uuid_mutex);kfree (device_name);if (error) goto out;
@@ -1539,7 +1541,9 @@ static struct dentry *btrfs_mount_root(struct file_system_type *fs_type,return ERR_PTR (error); } +mutex_lock (&uuid_mutex); error =btrfs_scan_one_device (device_name, mode, fs_type, &fs_devices); +mutex_unlock (&uuid_mutex);if (error) goto error_sec_opts;
@@ -2234,15 +2238,21 @@ static long btrfs_control_ioctl(struct file *file, unsigned int cmd,switch (cmd) {case BTRFS_IOC_SCAN_DEV : +mutex_lock (&uuid_mutex); ret =btrfs_scan_one_device (vol->name,FMODE_READ , &btrfs_root_fs_type, &fs_devices); +mutex_unlock (&uuid_mutex);break ;case BTRFS_IOC_DEVICES_READY : +mutex_lock (&uuid_mutex); ret =btrfs_scan_one_device (vol->name,FMODE_READ , &btrfs_root_fs_type, &fs_devices); -if (ret) +if (ret) { +mutex_unlock (&uuid_mutex);break ; + } ret = !(fs_devices->num_devices == fs_devices->total_devices); +mutex_unlock (&uuid_mutex);break ;case BTRFS_IOC_GET_SUPPORTED_FEATURES : ret =btrfs_ioctl_get_supported_features ((void __user *)arg);
fs/btrfs/volumes.c +2 -2
@@ -1226,6 +1226,8 @@ int btrfs_scan_one_device(const char *path, fmode_t flags, void *holder,int ret =0 ;u64 bytenr; +lockdep_assert_held (&uuid_mutex); +/* * we would like to check all the supers, but that would make * a btrfs mount succeed after a mkfs from a different FS.
@@ -1244,13 +1246,11 @@ int btrfs_scan_one_device(const char *path, fmode_t flags, void *holder, goto error_bdev_put; } -mutex_lock (&uuid_mutex); device =device_list_add (path, disk_super);if (IS_ERR (device)) ret =PTR_ERR (device);else *fs_devices_ret = device->fs_devices; -mutex_unlock (&uuid_mutex);btrfs_release_disk_super (page);
Signature · 1 line
2.17.0
Re: [PATCH 3/7] btrfs: lift uuid_mutex to callers of btrfs_scan_one_device
Quoted 7 lines · David Sterba
Prepartory work to fix race between mount and device scan.
The callers will have to manage the critical section, eg. mount wants to scan and then call
btrfs_open_deviceswithout the ioctl scan walking in and modifying the fs devices in the meantime.Signed-off-by David Sterba <[email protected]>
Thanks, Anand
Quoted 81 lines
--- fs/btrfs/super.c | 12 +++++++++++- fs/btrfs/volumes.c | 4 ++-- 2 files changed, 13 insertions(+), 3 deletions(-)fs/btrfs/super.c +11
-1@@ -917,8 +917,10 @@ static int btrfs_parse_early_options(const char *options, fmode_t flags, error = -ENOMEM ; goto out; } +mutex_lock (&uuid_mutex); error =btrfs_scan_one_device (device_name, flags, holder, fs_devices); +mutex_unlock (&uuid_mutex);kfree (device_name);if (error) goto out;@@ -1539,7 +1541,9 @@ static struct dentry *btrfs_mount_root(struct file_system_type *fs_type,return ERR_PTR (error); } +mutex_lock (&uuid_mutex); error =btrfs_scan_one_device (device_name, mode, fs_type, &fs_devices); +mutex_unlock (&uuid_mutex);if (error) goto error_sec_opts;@@ -2234,15 +2238,21 @@ static long btrfs_control_ioctl(struct file *file, unsigned int cmd,switch (cmd) {case BTRFS_IOC_SCAN_DEV : +mutex_lock (&uuid_mutex); ret =btrfs_scan_one_device (vol->name,FMODE_READ , &btrfs_root_fs_type, &fs_devices); +mutex_unlock (&uuid_mutex);break ;case BTRFS_IOC_DEVICES_READY : +mutex_lock (&uuid_mutex); ret =btrfs_scan_one_device (vol->name,FMODE_READ , &btrfs_root_fs_type, &fs_devices); -if (ret) +if (ret) { +mutex_unlock (&uuid_mutex);break ; + } ret = !(fs_devices->num_devices == fs_devices->total_devices); +mutex_unlock (&uuid_mutex);break ;case BTRFS_IOC_GET_SUPPORTED_FEATURES : ret =btrfs_ioctl_get_supported_features ((void __user *)arg);fs/btrfs/volumes.c +2
-2@@ -1226,6 +1226,8 @@ int btrfs_scan_one_device(const char *path, fmode_t flags, void *holder,int ret =0 ;u64 bytenr; +lockdep_assert_held (&uuid_mutex); +/* * we would like to check all the supers, but that would make * a btrfs mount succeed after a mkfs from a different FS. @@ -1244,13 +1246,11 @@ int btrfs_scan_one_device(const char *path, fmode_t flags, void *holder, goto error_bdev_put; } -mutex_lock (&uuid_mutex); device =device_list_add (path, disk_super);if (IS_ERR (device)) ret =PTR_ERR (device);else *fs_devices_ret = device->fs_devices; -mutex_unlock (&uuid_mutex);btrfs_release_disk_super (page);
David Sterba
maintainerPATCH 4/7 btrfs: lift uuid_mutex to callers of btrfs_open_devices
In reply to David SterbaPrepartory work to fix race between mount and device scan.
The callers will have to manage the critical section, eg. mount wants to scan and then call btrfs_open_devices without the ioctl scan walking in and modifying the fs devices in the meantime.
--- fs/btrfs/super.c | 2 ++ fs/btrfs/volumes.c | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-)
fs/btrfs/super.c +2 -0
@@ -1569,7 +1569,9 @@ static struct dentry *btrfs_mount_root(struct file_system_type *fs_type, goto error_fs_info; } +mutex_lock (&uuid_mutex); error =btrfs_open_devices (fs_devices, mode, fs_type); +mutex_unlock (&uuid_mutex);if (error) goto error_fs_info;
fs/btrfs/volumes.c +2 -2
@@ -1145,7 +1145,8 @@ int btrfs_open_devices(struct btrfs_fs_devices *fs_devices, {int ret; -mutex_lock (&uuid_mutex); +lockdep_assert_held (&uuid_mutex); +mutex_lock (&fs_devices->device_list_mutex);if (fs_devices->opened) { fs_devices->opened++;
@@ -1155,7 +1156,6 @@ int btrfs_open_devices(struct btrfs_fs_devices *fs_devices, ret =open_fs_devices (fs_devices, flags, holder); }mutex_unlock (&fs_devices->device_list_mutex); -mutex_unlock (&uuid_mutex);return ret; }
Signature · 1 line
2.17.0
Re: [PATCH 4/7] btrfs: lift uuid_mutex to callers of btrfs_open_devices
Quoted 7 lines · David Sterba
Prepartory work to fix race between mount and device scan.
The callers will have to manage the critical section, eg. mount wants to scan and then call
btrfs_open_deviceswithout the ioctl scan walking in and modifying the fs devices in the meantime.Signed-off-by David Sterba <[email protected]>
Thanks, Anand
Quoted 42 lines
--- fs/btrfs/super.c | 2 ++ fs/btrfs/volumes.c | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-)fs/btrfs/super.c +2
-0@@ -1569,7 +1569,9 @@ static struct dentry *btrfs_mount_root(struct file_system_type *fs_type, goto error_fs_info; } +mutex_lock (&uuid_mutex); error =btrfs_open_devices (fs_devices, mode, fs_type); +mutex_unlock (&uuid_mutex);if (error) goto error_fs_info;fs/btrfs/volumes.c +2
-2@@ -1145,7 +1145,8 @@ int btrfs_open_devices(struct btrfs_fs_devices *fs_devices, {int ret; -mutex_lock (&uuid_mutex); +lockdep_assert_held (&uuid_mutex); +mutex_lock (&fs_devices->device_list_mutex);if (fs_devices->opened) { fs_devices->opened++;@@ -1155,7 +1156,6 @@ int btrfs_open_devices(struct btrfs_fs_devices *fs_devices, ret =open_fs_devices (fs_devices, flags, holder); }mutex_unlock (&fs_devices->device_list_mutex); -mutex_unlock (&uuid_mutex);return ret; }
David Sterba
maintainerPATCH 5/7 btrfs: lift uuid_mutex to callers of btrfs_parse_early_options
In reply to David SterbaPrepartory work to fix race between mount and device scan.
btrfs_parse_early_options calls the device scan from mount and we'll need to let mount completely manage the critical section.
--- fs/btrfs/super.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-)
fs/btrfs/super.c +4 -2
@@ -892,6 +892,8 @@ static int btrfs_parse_early_options(const char *options, fmode_t flags,char *device_name, *opts, *orig, *p;int error =0 ; +lockdep_assert_held (&uuid_mutex); +if (!options)return 0 ;
@@ -917,10 +919,8 @@ static int btrfs_parse_early_options(const char *options, fmode_t flags, error = -ENOMEM ; goto out; } -mutex_lock (&uuid_mutex); error =btrfs_scan_one_device (device_name, flags, holder, fs_devices); -mutex_unlock (&uuid_mutex);kfree (device_name);if (error) goto out;
@@ -1528,8 +1528,10 @@ static struct dentry *btrfs_mount_root(struct file_system_type *fs_type,if (!(flags &SB_RDONLY )) mode |=FMODE_WRITE ; +mutex_lock (&uuid_mutex); error =btrfs_parse_early_options (data, mode, fs_type, &fs_devices); +mutex_unlock (&uuid_mutex);if (error) {return ERR_PTR (error); }
Signature · 1 line
2.17.0
Re: [PATCH 5/7] btrfs: lift uuid_mutex to callers of btrfs_parse_early_options
Quoted 6 lines · David Sterba
Prepartory work to fix race between mount and device scan.
btrfs_parse_early_optionscalls the device scan from mount and we'll need to let mount completely manage the critical section.Signed-off-by David Sterba <[email protected]>
Thanks, Anand
Quoted 40 lines
--- fs/btrfs/super.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-)fs/btrfs/super.c +4
-2@@ -892,6 +892,8 @@ static int btrfs_parse_early_options(const char *options, fmode_t flags,char *device_name, *opts, *orig, *p;int error =0 ; +lockdep_assert_held (&uuid_mutex); +if (!options)return 0 ;@@ -917,10 +919,8 @@ static int btrfs_parse_early_options(const char *options, fmode_t flags, error = -ENOMEM ; goto out; } -mutex_lock (&uuid_mutex); error =btrfs_scan_one_device (device_name, flags, holder, fs_devices); -mutex_unlock (&uuid_mutex);kfree (device_name);if (error) goto out;@@ -1528,8 +1528,10 @@ static struct dentry *btrfs_mount_root(struct file_system_type *fs_type,if (!(flags &SB_RDONLY )) mode |=FMODE_WRITE ; +mutex_lock (&uuid_mutex); error =btrfs_parse_early_options (data, mode, fs_type, &fs_devices); +mutex_unlock (&uuid_mutex);if (error) {return ERR_PTR (error); }
David Sterba
maintainerPATCH 6/7 btrfs: reorder initialization before the mount locks uuid_mutex
In reply to David SterbaIn preparation to take a big lock, move resource initialization before the critical section. It's not obvious from the diff, the desired order is:
- initialize mount security options
- allocate temporary fs_info
- allocate superblock buffers
--- fs/btrfs/super.c | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-)
fs/btrfs/super.c +14 -16
@@ -1528,14 +1528,6 @@ static struct dentry *btrfs_mount_root(struct file_system_type *fs_type,if (!(flags &SB_RDONLY )) mode |=FMODE_WRITE ; -mutex_lock (&uuid_mutex); - error =btrfs_parse_early_options (data, mode, fs_type, - &fs_devices); -mutex_unlock (&uuid_mutex); -if (error) { -return ERR_PTR (error); - } -security_init_mnt_opts (&new_sec_opts);if (data) { error =parse_security_options (data, &new_sec_opts);
@@ -1543,12 +1535,6 @@ static struct dentry *btrfs_mount_root(struct file_system_type *fs_type,return ERR_PTR (error); } -mutex_lock (&uuid_mutex); - error =btrfs_scan_one_device (device_name, mode, fs_type, &fs_devices); -mutex_unlock (&uuid_mutex); -if (error) - goto error_sec_opts; -/* * Setup a dummy root and fs_info for test/set super. This is because * we don't actually fill this stuff out until open_ctree, but we need
@@ -1561,8 +1547,6 @@ static struct dentry *btrfs_mount_root(struct file_system_type *fs_type, goto error_sec_opts; } - fs_info->fs_devices = fs_devices; - fs_info->super_copy =kzalloc (BTRFS_SUPER_INFO_SIZE ,GFP_KERNEL ); fs_info->super_for_commit =kzalloc (BTRFS_SUPER_INFO_SIZE ,GFP_KERNEL );security_init_mnt_opts (&fs_info->security_opts);
@@ -1571,6 +1555,20 @@ static struct dentry *btrfs_mount_root(struct file_system_type *fs_type, goto error_fs_info; } +mutex_lock (&uuid_mutex); + error =btrfs_parse_early_options (data, mode, fs_type, &fs_devices); +mutex_unlock (&uuid_mutex); +if (error) + goto error_fs_info; + +mutex_lock (&uuid_mutex); + error =btrfs_scan_one_device (device_name, mode, fs_type, &fs_devices); +mutex_unlock (&uuid_mutex); +if (error) + goto error_fs_info; + + fs_info->fs_devices = fs_devices; +mutex_lock (&uuid_mutex); error =btrfs_open_devices (fs_devices, mode, fs_type);mutex_unlock (&uuid_mutex);
Signature · 1 line
2.17.0
Re: [PATCH 6/7] btrfs: reorder initialization before the mount locks uuid_mutex
Quoted 9 lines · David Sterba
In preparation to take a big lock, move resource initialization before the critical section. It's not obvious from the diff, the desired order is:
- initialize mount security options
- allocate temporaryfs_info
- allocate superblock buffersSigned-off-by David Sterba <[email protected]>
Thanks, Anand
Quoted 67 lines
--- fs/btrfs/super.c | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-)fs/btrfs/super.c +14
-16@@ -1528,14 +1528,6 @@ static struct dentry *btrfs_mount_root(struct file_system_type *fs_type,if (!(flags &SB_RDONLY )) mode |=FMODE_WRITE ; -mutex_lock (&uuid_mutex); - error =btrfs_parse_early_options (data, mode, fs_type, - &fs_devices); -mutex_unlock (&uuid_mutex); -if (error) { -return ERR_PTR (error); - } -security_init_mnt_opts (&new_sec_opts);if (data) { error =parse_security_options (data, &new_sec_opts);@@ -1543,12 +1535,6 @@ static struct dentry *btrfs_mount_root(struct file_system_type *fs_type,return ERR_PTR (error); } -mutex_lock (&uuid_mutex); - error =btrfs_scan_one_device (device_name, mode, fs_type, &fs_devices); -mutex_unlock (&uuid_mutex); -if (error) - goto error_sec_opts; -/* * Setup a dummy root and fs_info for test/set super. This is because * we don't actually fill this stuff out until open_ctree, but we need @@ -1561,8 +1547,6 @@ static struct dentry *btrfs_mount_root(struct file_system_type *fs_type, goto error_sec_opts; } - fs_info->fs_devices = fs_devices; - fs_info->super_copy =kzalloc (BTRFS_SUPER_INFO_SIZE ,GFP_KERNEL ); fs_info->super_for_commit =kzalloc (BTRFS_SUPER_INFO_SIZE ,GFP_KERNEL );security_init_mnt_opts (&fs_info->security_opts);@@ -1571,6 +1555,20 @@ static struct dentry *btrfs_mount_root(struct file_system_type *fs_type, goto error_fs_info; } +mutex_lock (&uuid_mutex); + error =btrfs_parse_early_options (data, mode, fs_type, &fs_devices); +mutex_unlock (&uuid_mutex); +if (error) + goto error_fs_info; + +mutex_lock (&uuid_mutex); + error =btrfs_scan_one_device (device_name, mode, fs_type, &fs_devices); +mutex_unlock (&uuid_mutex); +if (error) + goto error_fs_info; + + fs_info->fs_devices = fs_devices; +mutex_lock (&uuid_mutex); error =btrfs_open_devices (fs_devices, mode, fs_type);mutex_unlock (&uuid_mutex);
David Sterba
maintainerPATCH 7/7 btrfs: fix mount and ioctl device scan ioctl race
In reply to David SterbaTechnically this extends the critical section covered by uuid_mutex to:
- parse early mount options -- here we can call device scan on paths that can be passed as 'device=/dev/...'
- scan the device passed to mount
- open the devices related to the fs_devices -- this increases fs_devices::opened
The race can happen when mount calls one of the scans and there's another one called eg. by mkfs or 'btrfs dev scan':
Mount Scan
----- ----scan_one_device (dev1, fsid1)
scan_one_device (dev2, fsid2)
add the device
free stale devices
fsid1 fs_devices::opened == 0
find fsid1:dev1 free fsid1:dev1 if it's the last one,
free fs_devices of fsid1 too
open_devices (dev1, fsid1)
dev1 not found
When fixed, the uuid mutex will make sure that mount will increase fs_devices::opened and this will not be touched by the racing scan ioctl.
--- fs/btrfs/super.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-)
fs/btrfs/super.c +6 -6
@@ -1557,19 +1557,19 @@ static struct dentry *btrfs_mount_root(struct file_system_type *fs_type,mutex_lock (&uuid_mutex); error =btrfs_parse_early_options (data, mode, fs_type, &fs_devices); -mutex_unlock (&uuid_mutex); -if (error) +if (error) { +mutex_unlock (&uuid_mutex); goto error_fs_info; + } -mutex_lock (&uuid_mutex); error =btrfs_scan_one_device (device_name, mode, fs_type, &fs_devices); -mutex_unlock (&uuid_mutex); -if (error) +if (error) { +mutex_unlock (&uuid_mutex); goto error_fs_info; + } fs_info->fs_devices = fs_devices; -mutex_lock (&uuid_mutex); error =btrfs_open_devices (fs_devices, mode, fs_type);mutex_unlock (&uuid_mutex);if (error)
Signature · 1 line
2.17.0
Re: [PATCH 7/7] btrfs: fix mount and ioctl device scan ioctl race
Quoted 10 lines · David Sterba
Technically this extends the critical section covered by
uuid_mutexto:- parse early mount options -- here we can call device scan on paths that can be passed as 'device=/dev/...'
- scan the device passed to mount
- open the devices related to the
fs_devices-- this increasesfs_devices::opened
Quoted 7 lines
The race can happen when mount calls one of the scans and there's another one called eg. by mkfs or 'btrfs dev scan':
Mount Scan
----- ----scan_one_device(dev1, fsid1)
scan_one_device(dev2, fsid2)
^^^^
dev1typo?
Quoted 9 lines
add the device
free stale devices
fsid1fs_devices::opened== 0
find fsid1:dev1 free fsid1:dev1 if it's the last one,
freefs_devicesof fsid1 too
Quoted 6 lines
open_devices(dev1, fsid1)
dev1 not foundWhen fixed, the uuid mutex will make sure that mount will increase
fs_devices::openedand this will not be touched by the racing scan ioctl.
Using uuid_mutex will unnecessarily serialize mount across different fsids.
Unfortunately we don't have a test case to measure concurrency across btrfs fsids. When we have that, this shall fail.
Expecting different fsids to be able to mount concurrently is a fair expectation. And is certainly important for large servers running btrfs on few luns which shall start to mount at bootup.
These changes is kind of going in an opposite direction as I originally planned to improve concurrency (across fsids) by reducing the unnecessary uuid_mutex footprints.
And fix the other necessaries using the fsid local atomic volume exclusive operations flag. Which in the long term can replace fs_info::BTRFS_FS_EXCL_OP as well.
As both of these approaches fix the issue, its a trade off between the concerns of atomic volume exclusive operations flag (except for the -EBUSY part [1]) VS serialize mount across different fsids, and IMO, its better to make sure different fsids are concurrent in their scan-mount operations as it is critical to the boot-up time.
[1]
Though returning -EBUSY (for one of the racing mount, scan and or ready threads) is theoretically correct but its blunt, and it may wrongly categorize as regression, let me try to fix that part and ask for comments.
Thanks, Anand
Quoted 39 lines
Reported-and-tested-by [email protected]Reported-and-tested-by [email protected]Signed-off-by David Sterba <[email protected]>--- fs/btrfs/super.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-)fs/btrfs/super.c +6
-6@@ -1557,19 +1557,19 @@ static struct dentry *btrfs_mount_root(struct file_system_type *fs_type,mutex_lock (&uuid_mutex); error =btrfs_parse_early_options (data, mode, fs_type, &fs_devices); -mutex_unlock (&uuid_mutex); -if (error) +if (error) { +mutex_unlock (&uuid_mutex); goto error_fs_info; + } -mutex_lock (&uuid_mutex); error =btrfs_scan_one_device (device_name, mode, fs_type, &fs_devices); -mutex_unlock (&uuid_mutex); -if (error) +if (error) { +mutex_unlock (&uuid_mutex); goto error_fs_info; + } fs_info->fs_devices = fs_devices; -mutex_lock (&uuid_mutex); error =btrfs_open_devices (fs_devices, mode, fs_type);mutex_unlock (&uuid_mutex);if (error)
Re: [PATCH 7/7] btrfs: fix mount and ioctl device scan ioctl race
In reply to David SterbaQuoted 29 lines · David Sterba
Technically this extends the critical section covered by
uuid_mutexto:- parse early mount options -- here we can call device scan on paths that can be passed as 'device=/dev/...'
- scan the device passed to mount
- open the devices related to the
fs_devices-- this increasesfs_devices::openedThe race can happen when mount calls one of the scans and there's another one called eg. by mkfs or 'btrfs dev scan':
Mount Scan
----- ----scan_one_device(dev1, fsid1)
scan_one_device(dev2, fsid2)
add the device
free stale devices
fsid1fs_devices::opened== 0
find fsid1:dev1 free fsid1:dev1 if it's the last one,
freefs_devicesof fsid1 too
open_devices(dev1, fsid1)
dev1 not found
Quoted 7 lines
When fixed, the uuid mutex will make sure that mount will increase
fs_devices::openedand this will not be touched by the racing scan ioctl.Reported-and-tested-by [email protected]Reported-and-tested-by [email protected]Signed-off-by David Sterba <[email protected]>
As I said, the fsids concurrency scan/mount are coming up on top these patches so..
Thanks, Anand
Quoted 35 lines
--- fs/btrfs/super.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-)fs/btrfs/super.c +6
-6@@ -1557,19 +1557,19 @@ static struct dentry *btrfs_mount_root(struct file_system_type *fs_type,mutex_lock (&uuid_mutex); error =btrfs_parse_early_options (data, mode, fs_type, &fs_devices); -mutex_unlock (&uuid_mutex); -if (error) +if (error) { +mutex_unlock (&uuid_mutex); goto error_fs_info; + } -mutex_lock (&uuid_mutex); error =btrfs_scan_one_device (device_name, mode, fs_type, &fs_devices); -mutex_unlock (&uuid_mutex); -if (error) +if (error) { +mutex_unlock (&uuid_mutex); goto error_fs_info; + } fs_info->fs_devices = fs_devices; -mutex_lock (&uuid_mutex); error =btrfs_open_devices (fs_devices, mode, fs_type);mutex_unlock (&uuid_mutex);if (error)
Quoted 26 lines · David Sterba
This patchset fixes the bugs recently reported by syzbot. I've tried to use patches from Anand [1] to fix that but in the end there were fixes not suitable for merging to 4.18 and my final fix took a different approach.
In short,
fs_devices::openedis protected byuuid_mutexand this mutex can be used to exclude mount and scanning to interfere.The fstests pass and 2 syzbot reproducers reported no problems. I'd like to push the patchset to 4.18 but not rc2 as it's too close. I'll add the patchset to for-next soon if there are no major problems found, but otherwise I'm open to comments.
[1]
https://patchwork.kernel.org/patch/10446779/
https://patchwork.kernel.org/patch/10437707/ 1-6David Sterba (7):
btrfs: restoreuuid_mutexinbtrfs_open_devices
btrfs: extend critical section when scanning a new device
btrfs: liftuuid_mutexto callers ofbtrfs_scan_one_device
btrfs: liftuuid_mutexto callers ofbtrfs_open_devices
btrfs: liftuuid_mutexto callers ofbtrfs_parse_early_options
btrfs: reorder initialization before the mount locksuuid_mutex
btrfs: fix mount and ioctl device scan ioctl race
Quoted 4 lines
fs/btrfs/super.c | 38 +++++++++++++++++++++++++------------- fs/btrfs/volumes.c | 11 ++++++----- 2 files changed, 31 insertions(+), 18 deletions(-)
It's good you've added lockdep asserts in functions whose locking is lifted to the caller. However, I'm a bit hesitant about extending the locking section so much. I.e i parse_early_opt you are covering quite some string manipulation functionality and only the single call to btrfs_scan_one_device. I wonder if the whole device scanning/mounting code could be refactored to not rely on 2 locks (device list mutex + uuid mutex, and that's after you merged your series refactoring locking).
Anyways, the series LGTM:
Quoted 33 lines · Nikolay Borisov
Quoted 26 lines · David Sterba
This patchset fixes the bugs recently reported by syzbot. I've tried to use patches from Anand [1] to fix that but in the end there were fixes not suitable for merging to 4.18 and my final fix took a different approach.
In short,
fs_devices::openedis protected byuuid_mutexand this mutex can be used to exclude mount and scanning to interfere.The fstests pass and 2 syzbot reproducers reported no problems. I'd like to push the patchset to 4.18 but not rc2 as it's too close. I'll add the patchset to for-next soon if there are no major problems found, but otherwise I'm open to comments.
[1]
https://patchwork.kernel.org/patch/10446779/
https://patchwork.kernel.org/patch/10437707/ 1-6David Sterba (7):
btrfs: restoreuuid_mutexinbtrfs_open_devices
btrfs: extend critical section when scanning a new device
btrfs: liftuuid_mutexto callers ofbtrfs_scan_one_device
btrfs: liftuuid_mutexto callers ofbtrfs_open_devices
btrfs: liftuuid_mutexto callers ofbtrfs_parse_early_options
btrfs: reorder initialization before the mount locksuuid_mutex
btrfs: fix mount and ioctl device scan ioctl raceQuoted 4 lines
fs/btrfs/super.c | 38 +++++++++++++++++++++++++------------- fs/btrfs/volumes.c | 11 ++++++----- 2 files changed, 31 insertions(+), 18 deletions(-)
Quoted 5 lines
It's good you've added lockdep asserts in functions whose locking is lifted to the caller. However, I'm a bit hesitant about extending the locking section so much. I.e i
parse_early_optyou are covering quite some string manipulation functionality and only the single call tobtrfs_scan_one_device.
Are string manipulations under a lock a problem? I'd be more worried about memory allocations or IO that could hold the lock and block other mount or scan.
As both are very infrequent operations and callers need to wait anyway, I don't think this will be noticeable in practice.
Quoted 5 lines
I wonder if the whole device scanning/mounting
code could be refactored to not rely on 2 locks (device list mutex + uuid mutex, and that's after you merged your series refactoring locking).From what I've seen, there's not much potential for significant changes.
We need the locks to prevent either the data structures or for the operation exclusion. The rest would be reducing the size of the critical sections by moving unrelated calls out.