Skip to content

Commit 2d8a2b5

Browse files
authored
Fix zpl_test_super race with zfs_umount
We cannot call zpl_enter in zpl_test_super, because zpl_test_super is under spinlock so we can't sleep, and also because zpl_test_super is called without sb->s_umount taken, so it's possible we would race with zfs_umount and call zpl_enter on freed zfsvfs. Here's an stack trace when this happens: [ 2379.114837] VERIFY(cvp->cv_magic == CV_MAGIC) failed [ 2379.114845] PANIC at spl-condvar.c:497:__cv_broadcast() [ 2379.114854] Kernel panic - not syncing: VERIFY(cvp->cv_magic == CV_MAGIC) failed [ 2379.115012] Call Trace: [ 2379.115019] dump_stack+0x74/0x96 [ 2379.115024] panic+0x114/0x2f6 [ 2379.115035] spl_panic+0xcf/0xfc [spl] [ 2379.115477] __cv_broadcast+0x68/0xa0 [spl] [ 2379.115585] rrw_exit+0xb8/0x310 [zfs] [ 2379.115696] rrm_exit+0x4a/0x80 [zfs] [ 2379.115808] zpl_test_super+0xa9/0xd0 [zfs] [ 2379.115920] sget+0xd1/0x230 [ 2379.116033] zpl_mount+0xdc/0x230 [zfs] [ 2379.116037] legacy_get_tree+0x28/0x50 [ 2379.116039] vfs_get_tree+0x27/0xc0 [ 2379.116045] path_mount+0x2fe/0xa70 [ 2379.116048] do_mount+0x80/0xa0 [ 2379.116050] __x64_sys_mount+0x8b/0xe0 [ 2379.116052] do_syscall_64+0x35/0x50 [ 2379.116054] entry_SYSCALL_64_after_hwframe+0x61/0xc6 [ 2379.116057] RIP: 0033:0x7f9912e8b26a Reviewed-by: Brian Behlendorf <[email protected]> Signed-off-by: Chunwei Chen <[email protected]> Closes #15077
1 parent d9bb583 commit 2d8a2b5

File tree

2 files changed

+25
-15
lines changed

2 files changed

+25
-15
lines changed

module/os/linux/zfs/zfs_vfsops.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1662,6 +1662,7 @@ zfs_umount(struct super_block *sb)
16621662
}
16631663

16641664
zfsvfs_free(zfsvfs);
1665+
sb->s_fs_info = NULL;
16651666
return (0);
16661667
}
16671668

module/os/linux/zfs/zpl_super.c

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -277,28 +277,14 @@ zpl_test_super(struct super_block *s, void *data)
277277
{
278278
zfsvfs_t *zfsvfs = s->s_fs_info;
279279
objset_t *os = data;
280-
int match;
281-
282280
/*
283281
* If the os doesn't match the z_os in the super_block, assume it is
284282
* not a match. Matching would imply a multimount of a dataset. It is
285283
* possible that during a multimount, there is a simultaneous operation
286284
* that changes the z_os, e.g., rollback, where the match will be
287285
* missed, but in that case the user will get an EBUSY.
288286
*/
289-
if (zfsvfs == NULL || os != zfsvfs->z_os)
290-
return (0);
291-
292-
/*
293-
* If they do match, recheck with the lock held to prevent mounting the
294-
* wrong dataset since z_os can be stale when the teardown lock is held.
295-
*/
296-
if (zpl_enter(zfsvfs, FTAG) != 0)
297-
return (0);
298-
match = (os == zfsvfs->z_os);
299-
zpl_exit(zfsvfs, FTAG);
300-
301-
return (match);
287+
return (zfsvfs != NULL && os == zfsvfs->z_os);
302288
}
303289

304290
static struct super_block *
@@ -324,12 +310,35 @@ zpl_mount_impl(struct file_system_type *fs_type, int flags, zfs_mnt_t *zm)
324310

325311
s = sget(fs_type, zpl_test_super, set_anon_super, flags, os);
326312

313+
/*
314+
* Recheck with the lock held to prevent mounting the wrong dataset
315+
* since z_os can be stale when the teardown lock is held.
316+
*
317+
* We can't do this in zpl_test_super in since it's under spinlock and
318+
* also s_umount lock is not held there so it would race with
319+
* zfs_umount and zfsvfs can be freed.
320+
*/
321+
if (!IS_ERR(s) && s->s_fs_info != NULL) {
322+
zfsvfs_t *zfsvfs = s->s_fs_info;
323+
if (zpl_enter(zfsvfs, FTAG) == 0) {
324+
if (os != zfsvfs->z_os)
325+
err = -SET_ERROR(EBUSY);
326+
zpl_exit(zfsvfs, FTAG);
327+
} else {
328+
err = -SET_ERROR(EBUSY);
329+
}
330+
}
327331
dsl_dataset_long_rele(dmu_objset_ds(os), FTAG);
328332
dsl_dataset_rele(dmu_objset_ds(os), FTAG);
329333

330334
if (IS_ERR(s))
331335
return (ERR_CAST(s));
332336

337+
if (err) {
338+
deactivate_locked_super(s);
339+
return (ERR_PTR(err));
340+
}
341+
333342
if (s->s_root == NULL) {
334343
err = zpl_fill_super(s, zm, flags & SB_SILENT ? 1 : 0);
335344
if (err) {

0 commit comments

Comments
 (0)