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

Zhihao Cheng posted 1 patch 4 weeks, 1 day ago
fs/fuse/file.c | 19 +++++++++++++------
1 file changed, 13 insertions(+), 6 deletions(-)
[PATCH] fuse: virtiofs: fix illegal inode address access in fuse_release_end
Posted by Zhihao Cheng 4 weeks, 1 day ago
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
Re: [PATCH] fuse: virtiofs: fix illegal inode address access in fuse_release_end
Posted by Darrick J. Wong 3 weeks, 6 days ago
[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
>
Re: [PATCH] fuse: virtiofs: fix illegal inode address access in fuse_release_end
Posted by Zhihao Cheng 3 weeks, 6 days ago
在 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
>>
> 
> .
>