fs/fuse/file.c | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-)
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@huawei.com>
---
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
[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?
--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@huawei.com>
> ---
> 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
>
在 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@huawei.com>
>> ---
>> 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
>>
>
> .
>
© 2016 - 2026 Red Hat, Inc.