Andrew Price <anprice@xxxxxxxxxx> wrote: > > + up_write(&s->s_umount); > > + blkdev_put(bdev, fc->bdev_mode); > > + down_write(&s->s_umount); > > fc->bdev should be NULLed here (or, on the way out of sget_fc() might be more > appropriate) otherwise we get a double-blkdev_put() leading to NULL pointer > derefs later. This happens when I mount a device twice and then unmount them, > or mount it 3 times. How about the attached changes? Note that they conflict a bit with the mtd changes from where I took the destructor piece. David --- commit 161602f963ae5a05bdb834461f7a4896286fbde0 Author: David Howells <dhowells@xxxxxxxxxx> Date: Wed Mar 27 11:12:57 2019 +0000 bdev handling fix diff --git a/fs/fs_context.c b/fs/fs_context.c index 9e22fe6aafe7..fc8fda4b9fe9 100644 --- a/fs/fs_context.c +++ b/fs/fs_context.c @@ -425,10 +425,8 @@ void put_fs_context(struct fs_context *fc) if (fc->need_free && fc->ops && fc->ops->free) fc->ops->free(fc); -#ifdef CONFIG_BLOCK - if (fc->bdev) - blkdev_put(fc->bdev, fc->bdev_mode); -#endif + if (fc->dev_destructor) + fc->dev_destructor(fc); security_free_mnt_opts(&fc->security); put_net(fc->net_ns); diff --git a/fs/super.c b/fs/super.c index 85851adb0f19..e9e678d2c37c 100644 --- a/fs/super.c +++ b/fs/super.c @@ -1211,8 +1211,17 @@ int vfs_get_super(struct fs_context *fc, EXPORT_SYMBOL(vfs_get_super); #ifdef CONFIG_BLOCK +static void fc_bdev_destructor(struct fs_context *fc) +{ + if (fc->bdev) { + blkdev_put(fc->bdev, fc->bdev_mode); + fc->bdev = NULL; + } +} + static int set_bdev_super_fc(struct super_block *s, struct fs_context *fc) { + s->s_mode = fc->bdev_mode; s->s_bdev = fc->bdev; s->s_dev = s->s_bdev->bd_dev; s->s_bdi = bdi_get(s->s_bdev->bd_bdi); @@ -1252,6 +1261,9 @@ int vfs_get_block_super(struct fs_context *fc, return PTR_ERR(bdev); } + fc->dev_destructor = fc_bdev_destructor; + fc->bdev = bdev; + /* Once the superblock is inserted into the list by sget_fc(), s_umount * will protect the lockfs code from trying to start a snapshot while * we are mounting @@ -1260,18 +1272,14 @@ int vfs_get_block_super(struct fs_context *fc, if (bdev->bd_fsfreeze_count > 0) { mutex_unlock(&bdev->bd_fsfreeze_mutex); warnf(fc, "%pg: Can't mount, blockdev is frozen", bdev); - error = -EBUSY; - goto error_bdev; + return -EBUSY; } - fc->bdev = bdev; fc->sb_flags |= SB_NOSEC; s = sget_fc(fc, test_bdev_super_fc, set_bdev_super_fc); mutex_unlock(&bdev->bd_fsfreeze_mutex); - if (IS_ERR(s)) { - error = PTR_ERR(s); - goto error_bdev; - } + if (IS_ERR(s)) + return PTR_ERR(s); if (s->s_root) { /* Don't summarily change the RO/RW state. */ @@ -1281,16 +1289,10 @@ int vfs_get_block_super(struct fs_context *fc, goto error_sb; } - /* s_umount nests inside bd_mutex during __invalidate_device(). - * blkdev_put() acquires bd_mutex and can't be called under - * s_umount. Drop s_umount temporarily. This is safe as we're - * holding an active reference. + /* Leave fc->bdev to fc_bdev_destructor() to clean up to avoid + * locking conflicts. */ - up_write(&s->s_umount); - blkdev_put(bdev, fc->bdev_mode); - down_write(&s->s_umount); } else { - s->s_mode = fc->bdev_mode; snprintf(s->s_id, sizeof(s->s_id), "%pg", bdev); sb_set_blocksize(s, block_size(bdev)); error = fill_super(s, fc); @@ -1307,11 +1309,7 @@ int vfs_get_block_super(struct fs_context *fc, error_sb: deactivate_locked_super(s); -error_bdev: - if (fc->bdev) { - blkdev_put(fc->bdev, fc->bdev_mode); - fc->bdev = NULL; - } + /* Leave fc->bdev to fc_bdev_destructor() to clean up */ return error; } EXPORT_SYMBOL(vfs_get_block_super); @@ -1521,8 +1519,13 @@ int vfs_get_tree(struct fs_context *fc) * on the superblock. */ error = fc->ops->get_tree(fc); - if (error < 0) + if (error < 0) { + if (fc->dev_destructor) { + fc->dev_destructor(fc); + fc->dev_destructor = NULL; + } return error; + } if (!fc->root) { pr_err("Filesystem %s get_tree() didn't set fc->root\n", diff --git a/include/linux/fs_context.h b/include/linux/fs_context.h index cb49b92f02af..9af788a3ef8e 100644 --- a/include/linux/fs_context.h +++ b/include/linux/fs_context.h @@ -76,7 +76,9 @@ struct fs_context { const struct fs_context_operations *ops; struct file_system_type *fs_type; void *fs_private; /* The filesystem's context */ - struct block_device *bdev; /* The backing blockdev (if applicable) */ + union { + struct block_device *bdev; /* The backing blockdev (if applicable) */ + }; struct dentry *root; /* The root and superblock */ struct user_namespace *user_ns; /* The user namespace for this mount */ struct net *net_ns; /* The network namespace for this mount */ @@ -93,6 +95,7 @@ struct fs_context { enum fs_context_purpose purpose:8; bool need_free:1; /* Need to call ops->free() */ bool global:1; /* Goes into &init_user_ns */ + void (*dev_destructor)(struct fs_context *fc); /* For block or mtd */ }; struct fs_context_operations {