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

From: Zhihao Cheng

Date: Fri May 15 2026 - 23:20:51 EST


在 2026/5/16 5:07, Darrick J. Wong 写道:
[add fuse-devel]

On Thu, May 14, 2026 at 08:41:02PM +0800, Zhihao Cheng wrote:
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.

Does the weird AIO "kernel breaks if you close the fd before all the IOs
complete" behavior happen on virtiofsd?


Hi Darrick,
IMHO, the problem fixed by commit 26e5c67deb2e1f42a9("fuse: fix livelock in synchronous file put from fuseblk workers") happens as following process:
fuse user fuse daemon
fd = open(file)
io_submit_one
req->ki_filp = fget(iocb->aio_fildes)
close(fd) // file->f_ref = 1, file won't be released
process FUSE_WRITE req
write(/dev/fuse, reply)
fuse_request_end
fuse_aio_complete_req
aio_complete_rw
fput(iocb->ki_filp)
task_work_add
task_work_run
__fput
fuse_release
__fuse_simple_request
request_wait_answer

For viriofs, the user(virtiofs) runs in kernel A(guest), the daemon(virtiofsd) runs kernel B(host), and the fuse_request_end is called by virtio_fs_requests_done_work -> virtio_fs_complete_req_work -> virtio_fs_request_complete, which is under the kworker context(kernel A, guest), so it won't block the daemon thread(kernel B, host).

--D


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


.