| From d92d88f0568e97c437eeb79d9c9609bd8277406f Mon Sep 17 00:00:00 2001 |
| From: Greg Kurz <groug@kaod.org> |
| Date: Fri, 4 Jun 2021 18:11:50 +0200 |
| Subject: fuse: Fix crash in fuse_dentry_automount() error path |
| |
| From: Greg Kurz <groug@kaod.org> |
| |
| commit d92d88f0568e97c437eeb79d9c9609bd8277406f upstream. |
| |
| If fuse_fill_super_submount() returns an error, the error path |
| triggers a crash: |
| |
| [ 26.206673] BUG: kernel NULL pointer dereference, address: 0000000000000000 |
| [...] |
| [ 26.226362] RIP: 0010:__list_del_entry_valid+0x25/0x90 |
| [...] |
| [ 26.247938] Call Trace: |
| [ 26.248300] fuse_mount_remove+0x2c/0x70 [fuse] |
| [ 26.248892] virtio_kill_sb+0x22/0x160 [virtiofs] |
| [ 26.249487] deactivate_locked_super+0x36/0xa0 |
| [ 26.250077] fuse_dentry_automount+0x178/0x1a0 [fuse] |
| |
| The crash happens because fuse_mount_remove() assumes that the FUSE |
| mount was already added to list under the FUSE connection, but this |
| only done after fuse_fill_super_submount() has returned success. |
| |
| This means that until fuse_fill_super_submount() has returned success, |
| the FUSE mount isn't actually owned by the superblock. We should thus |
| reclaim ownership by clearing sb->s_fs_info, which will skip the call |
| to fuse_mount_remove(), and perform rollback, like virtio_fs_get_tree() |
| already does for the root sb. |
| |
| Fixes: bf109c64040f ("fuse: implement crossmounts") |
| Cc: stable@vger.kernel.org # v5.10+ |
| Signed-off-by: Greg Kurz <groug@kaod.org> |
| Reviewed-by: Max Reitz <mreitz@redhat.com> |
| Signed-off-by: Miklos Szeredi <mszeredi@redhat.com> |
| Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| |
| --- |
| fs/fuse/dir.c | 6 +++++- |
| 1 file changed, 5 insertions(+), 1 deletion(-) |
| |
| --- a/fs/fuse/dir.c |
| +++ b/fs/fuse/dir.c |
| @@ -340,8 +340,12 @@ static struct vfsmount *fuse_dentry_auto |
| |
| /* Initialize superblock, making @mp_fi its root */ |
| err = fuse_fill_super_submount(sb, mp_fi); |
| - if (err) |
| + if (err) { |
| + fuse_conn_put(fc); |
| + kfree(fm); |
| + sb->s_fs_info = NULL; |
| goto out_put_sb; |
| + } |
| |
| sb->s_flags |= SB_ACTIVE; |
| fsc->root = dget(sb->s_root); |