From: sashiko-bot@kernel.org
To: Qihang <q.h.hack.winter@gmail.com>
Cc: virtualization@lists.linux.dev,
"Michael S. Tsirkin" <mst@redhat.com>,
"Eugenio Perez" <eperezma@redhat.com>
Subject: Re: [PATCH v2] fuse: virtio-fs: stop new requests before draining queues
Date: Sun, 08 Nov 2026 14:29:22 +0000 [thread overview]
Message-ID: <sashiko-outbox-203803@kernel.org> (raw)
In-Reply-To: <20261108135830.29924-1-q.h.hack.winter@gmail.com>
Thank you for your contribution! Sashiko AI review found 3 potential
issues to consider:
- [Medium] fuse: virtio-fs: race on -ENOSPC in virtio_fs_send_req()
bypasses aborting
- [Medium] fuse: virtio-fs: self-deadlock in
virtio_fs_request_dispatch_work()
- [Low] fuse: virtio-fs: missing Fixes trailer for unmount teardown race
fix
--
commit 183d70093cd447a74de7ef9f20f4895fc3aef144
Author: Qihang <q.h.hack.winter@gmail.com>
> fuse: virtio-fs: stop new requests before draining queues
>
> virtio_fs_conn_destroy() drains the virtqueues and then calls
> fuse_conn_destroy(), which aborts the connection. The abort ends every
> request the fuse device still owns, and each ended request is freed as
> soon as its waiter wakes up.
>
> The drain only covers the requests that were already counted in
> fsvq->in_flight when it started. A request submitted after it returns is
> not covered, but is still handed to the device before the abort runs:
>
> CPU 0 (umount) CPU 1
> --------------------------------- --------------------------------
> virtio_fs_drain_all_queues()
> /* in_flight drops to 0 and
> the drain returns */
> virtio_fs_enqueue_req()
> virtqueue_add_sgs()
> inc_in_flight_req() -> 1
> fuse_conn_destroy()
> fuse_send_destroy()
> fuse_chan_abort()
> /* ends and frees the
> request above */
> fuse_chan_wait_aborted()
> /* the device still owns the
> virtqueue descriptors and
> DMA-writes into the freed
> request and its argbuf */
>
> Stop admitting requests on each queue before that queue is drained.
> The flag is set per queue, but that is sufficient: each queue is
> drained after its own flag is set, and request admission,
> virtqueue_add_sgs() and, on the submission path, the in_flight
> increment are serialized by the same fsvq->lock. A request is
> therefore either rejected before the device can see it, or already
> counted in fsvq->in_flight and covered by the drain. There is no
> window in between.
>
> Rejected requests use the existing -ENOTCONN error path and are ended by
> virtio_fs_request_dispatch_work() through end_reqs, so no new request
> completion or ownership state is introduced. Nothing has to be
> discarded in the completion path, and fsvq->in_flight remains the only
> state that has to be tracked.
>
> The new state is not folded into fsvq->connected because the request
> queues have to stay usable: fuse_conn_destroy() sends FUSE_DESTROY
> through them, with args.force set so that fuse_simple_request() waits
> for the reply. Clearing fsvq->connected would make that request fail
> with -ENOTCONN and the server would never see the destroy.
>
> FUSE_DESTROY is the sole exception while aborting is set. It is sent
> only after the first drain, and its completion is waited for before
> fuse_chan_abort() can end requests, so the destroy is the only request
> the device sees after the drain. virtio-fs exposes no userspace
> /dev/fuse fd (and no_control disables the fusectl interface), so
> userspace cannot inject another request.
>
> virtio_fs_start_all_queues() clears the flag, so a mount that follows an
> unmount starts with admission enabled again.
>
> Signed-off-by: Qihang <q.h.hack.winter@gmail.com>
[Severity: Low]
Since this fixes the unmount teardown race present since virtio-fs was
introduced, should it include a Fixes: tag?
Fixes: a62a8ef9d97d ("virtio-fs: add virtiofs filesystem")
> diff --git a/fs/fuse/virtio_fs.c b/fs/fuse/virtio_fs.c
> index ba8d79c4bcac6..c6d2120157340 100644
> --- a/fs/fuse/virtio_fs.c
> +++ b/fs/fuse/virtio_fs.c
[ ... ]
> @@ -1479,7 +1503,8 @@ static int virtio_fs_enqueue_req(struct virtio_fs_vq *fsvq,
>
> spin_lock(&fsvq->lock);
>
> - if (!fsvq->connected) {
> + if (!fsvq->connected ||
> + (fsvq->aborting && req->in.h.opcode != FUSE_DESTROY)) {
> spin_unlock(&fsvq->lock);
> ret = -ENOTCONN;
> goto out;
[Severity: Medium]
Can a request that hits -ENOSPC in virtqueue_add_sgs() bypass fsvq->aborting
and hang unmount?
When virtqueue_add_sgs() returns -ENOSPC, virtio_fs_enqueue_req() drops
fsvq->lock without incrementing fsvq->in_flight or queueing req onto
fsvq->queued_reqs, and virtio_fs_send_req() re-acquires fsvq->lock in a
separate critical section without re-checking fsvq->aborting:
fs/fuse/virtio_fs.c:virtio_fs_send_req() {
...
ret = virtio_fs_enqueue_req(fsvq, req, false, GFP_ATOMIC);
if (ret < 0) {
if (ret == -ENOSPC) {
spin_lock(&fsvq->lock);
list_add_tail(&req->list, &fsvq->queued_reqs);
inc_in_flight_req(fsvq);
spin_unlock(&fsvq->lock);
return;
}
...
}
If the remaining in-flight requests complete and the queue is drained in
that unlocked window, req is stranded on fsvq->queued_reqs:
CPU0 CPU1 (umount)
virtio_fs_send_req()
virtio_fs_enqueue_req()
spin_lock(&fsvq->lock)
/* fsvq->aborting is false */
virtqueue_add_sgs() -> -ENOSPC
spin_unlock(&fsvq->lock)
/* in-flight reqs complete;
done_work sees queued_reqs
empty, in_flight == 0 */
virtio_fs_stop_new_requests()
fsvq->aborting = true
virtio_fs_drain_all_queues()
spin_lock(&fsvq->lock)
list_add_tail(&req->list,
&fsvq->queued_reqs)
inc_in_flight_req(fsvq) -> 1
spin_unlock(&fsvq->lock)
fuse_conn_destroy()
fuse_chan_wait_aborted()
<-- hangs waiting for req
Because fuse_chan_abort() does not walk fsvq->queued_reqs and nothing
schedules fsvq->dispatch_work again on that queue, req is never ended,
hanging fuse_chan_wait_aborted() or the second virtio_fs_drain_all_queues().
A similar unlocked window exists in virtio_fs_request_dispatch_work() when
virtio_fs_enqueue_req() returns -ENOSPC and req is re-added to
fsvq->queued_reqs without re-checking fsvq->aborting.
> @@ -1669,6 +1694,9 @@ static void virtio_fs_conn_destroy(struct fuse_mount *fm)
> spin_lock(&fsvq->lock);
> fsvq->connected = false;
> spin_unlock(&fsvq->lock);
> +
> + /* Nothing may be queued after the drain below */
> + virtio_fs_stop_new_requests(vfs);
> virtio_fs_drain_all_queues(vfs);
>
> fuse_conn_destroy(fm);
[Severity: Medium]
Can setting fsvq->aborting before fuse_chan_abort() clears fch->connected
cause virtio_fs_request_dispatch_work() to deadlock on itself?
When virtio_fs_enqueue_req() rejects an asynchronous FUSE_RELEASE for an
unlinked DAX inode with -ENOTCONN while SB_ACTIVE and fch->connected are
still set, virtio_fs_request_dispatch_work() ends it directly instead of
offloading req->args->may_block requests to a separate worker the way
virtio_fs_requests_done_work() does:
virtio_fs_request_dispatch_work()
fuse_request_end(req)
fuse_release_end()
iput()
...
fuse_evict_inode()
fuse_vdax_inode_cleanup()
...
fuse_send_removemapping()
fuse_simple_request()
virtio_fs_send_req()
virtio_fs_enqueue_req() -> -ENOTCONN
list_add_tail(&req->list, &fsvq->end_reqs)
schedule_work(&fsvq->dispatch_work)
request_wait_answer() <-- waits for dispatch_work
Because fuse_simple_request() blocks inside virtio_fs_request_dispatch_work()
waiting for fsvq->dispatch_work to process fsvq->end_reqs, the work item can
never run again and unmount deadlocks.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261108135830.29924-1-q.h.hack.winter@gmail.com?part=1
next prev parent reply other threads:[~2026-11-08 14:29 UTC|newest]
Thread overview: 3+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-08 13:58 [PATCH v2] fuse: virtio-fs: stop new requests before draining queues Qihang
2026-11-08 14:29 ` sashiko-bot [this message]
2026-11-09 2:40 ` Qihang
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=sashiko-outbox-203803@kernel.org \
--to=sashiko-bot@kernel.org \
--cc=eperezma@redhat.com \
--cc=mst@redhat.com \
--cc=q.h.hack.winter@gmail.com \
--cc=sashiko-reviews@lists.linux.dev \
--cc=virtualization@lists.linux.dev \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox