[PATCH] fuse: virtiofs: fix illegal inode address access in fuse_release_end

From: Zhihao Cheng

Date: Thu May 14 2026 - 08:47:31 EST


When a submount (e.g. virtiofs with fc->auto_submounts=true) is umounted
while an async RELEASE request is still pending, an illegal inode address
accessing occurs in fuse_release_end()->iput().

Trigger process:
1. virtiofs has a submount; user opens and closes a file under it
2. Close calls fuse_file_put() with sync=false, sending RELEASE
asynchronously
3. fuse_release_end() is scheduled to run later via igrab() holding
inode ref
4. File is freed, mount/dentry refcounts are released
5. User umounts the submount; fuse connection detects remaining
superblock and does NOT flush the connection's requests
6. generic_shutdown_super() destroys the superblock and poisons busy
inodes' inode->i_sb = VFS_PTR_POISON
7. Later, fuse_request_end() calls fuse_release_end() which does
iput(inode)
8. iput() accesses inode->i_sb->s_op at the poisoned address, crash!

There are two solutions to fix it:
Solution A: Hold path reference in fuse_file_put, and put path
synchronously, which could reintroduce the issue fixed by commit
5a18ec176c934 ("fuse: fix hang of single threaded fuseblk filesystem").

Solution B (chosen): Use synchronous RELEASE for auto_submounts (which
is only supported by virtiofs). The virtiofsd(fuse daemon) and virtiofs
won't work together under one same kernel instance, so the problem fixed
by commit 26e5c67deb2e ("fuse: fix livelock in synchronous file put from
fuseblk workers") won't be brought back in virtiofs.

Fetch a reproducer in https://bugzilla.kernel.org/show_bug.cgi?id=221519.

Fixes: 26e5c67deb2e ("fuse: fix livelock in synchronous file put from fuseblk workers")
Signed-off-by: Zhihao Cheng <chengzhihao1@xxxxxxxxxx>
---
fs/fuse/file.c | 19 +++++++++++++------
1 file changed, 13 insertions(+), 6 deletions(-)

diff --git a/fs/fuse/file.c b/fs/fuse/file.c
index c59452d60b8d..a6192b96d861 100644
--- a/fs/fuse/file.c
+++ b/fs/fuse/file.c
@@ -375,13 +375,20 @@ void fuse_file_release(struct inode *inode, struct fuse_file *ff,
* synchronous RELEASE is allowed (and desirable) in this case
* because the server can be trusted not to screw up.
*
- * Always use the asynchronous file put because the current thread
- * might be the fuse server. This can happen if a process starts some
- * aio and closes the fd before the aio completes. Since aio takes its
- * own ref to the file, the IO completion has to drop the ref, which is
- * how the fuse server can end up closing its clients' files.
+ * For auto_submounts (e.g. virtiofs), always use synchronous
+ * release to avoid illegal inode address access when umount
+ * happens before async release completes. The async release
+ * holds inode reference via igrab(), but umount can shutdown
+ * superblock and poison inode->i_sb before release ends,
+ * causing crash in fuse_release_end()->iput(). Otherwise,
+ * always use the asynchronous file put because the current
+ * thread might be the fuse server. This can happen if a
+ * process starts some aio and closes the fd before the aio
+ * completes. Since aio takes its own ref to the file, the IO
+ * completion has to drop the ref, which is how the fuse server
+ * can end up closing its clients' files.
*/
- fuse_file_put(ff, false);
+ fuse_file_put(ff, ff->fm->fc->auto_submounts);
}

void fuse_release_common(struct file *file, bool isdir)
--
2.52.0