All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH 0/2] fuse: io-uring: fix two UAFs in dev_uring.c teardown
       [not found] ` <2026051703-equinox-multitude-91e2@gregkh>
@ 2026-05-17 12:59   ` Berkant Koc
  2026-05-17 12:59     ` [PATCH 1/2] fuse: io-uring: clear ent->fuse_req in commit_fetch error path Berkant Koc
                       ` (2 more replies)
  0 siblings, 3 replies; 20+ messages in thread
From: Berkant Koc @ 2026-05-17 12:59 UTC (permalink / raw)
  To: Greg KH, Miklos Szeredi, Bernd Schubert
  Cc: security, Joanne Koong, linux-fuse, linux-kernel

Applied on top of 6916d5703ddf. Thanks Greg for asking to take this
on-list; the original off-list report to security@kernel.org included
a defensive third patch that added cancel_delayed_work_sync() in
fuse_conn_put(), but it triggered a WARN_ON in queue_work() because
the work item is not always initialized at that point. That patch is
dropped from this series.

Patch 1/2 is the static-analysis fix: fuse_uring_commit_fetch() leaks
a dangling ent->fuse_req on the set_commit error branch. The patch
routes that branch through the existing fuse_uring_req_end() helper
so ent->fuse_req is cleared under queue->lock.

Patch 2/2 is the KASAN-reproducible fix: fuse_dev_release() on the
last fuse_dev drops the connection ref before the io-uring
async_teardown_work has stopped, so delayed_release() can kfree() ring
entries the work is still walking. Adding fuse_wait_aborted() between
fuse_abort_conn() and fuse_conn_put() drains queue_refs first.

KASAN-tested at HEAD 6916d5703ddf + this series, 50 iterations x 16
worker threads against an io-uring fuse daemon: 0 KASAN trips, 0
warnings. KASAN log on request.

Joanne Koong's [PATCH v2 0/3] (Message-ID
<20260516021138.2759874-1-joannelkoong@gmail.com>) is in flight on a
separate fuse-next base; this series targets mainline 6916d5703ddf and
does not overlap with hers in code or fix scope.

CCing linux-fuse and linux-kernel now per the on-list workflow.

Berkant Koc (2):
  fuse: io-uring: clear ent->fuse_req in commit_fetch error path
  fuse: wait for aborted connection before releasing last fuse_dev

 fs/fuse/dev.c       | 1 +
 fs/fuse/dev_uring.c | 4 +---
 2 files changed, 2 insertions(+), 3 deletions(-)

--
2.47.3

^ permalink raw reply	[flat|nested] 20+ messages in thread

* [PATCH 1/2] fuse: io-uring: clear ent->fuse_req in commit_fetch error path
  2026-05-17 12:59   ` [PATCH 0/2] fuse: io-uring: fix two UAFs in dev_uring.c teardown Berkant Koc
@ 2026-05-17 12:59     ` Berkant Koc
  2026-05-17 14:11       ` Bernd Schubert
  2026-05-17 12:59     ` [PATCH 2/2] fuse: wait for aborted connection before releasing last fuse_dev Berkant Koc
  2026-05-17 13:14     ` [PATCH 0/2] fuse: io-uring: fix two UAFs in dev_uring.c teardown Berkant Koc
  2 siblings, 1 reply; 20+ messages in thread
From: Berkant Koc @ 2026-05-17 12:59 UTC (permalink / raw)
  To: Greg KH, Miklos Szeredi, Bernd Schubert
  Cc: security, Joanne Koong, linux-fuse, linux-kernel

From: Berkant Koc <me@berkoc.com>

fuse_uring_commit_fetch() locates a request, removes it from the
processing queue, clears req->ring_entry, then calls
fuse_ring_ent_set_commit() under queue->lock. On the error branch
(set_commit returning non-zero because the entry is not in
FRRS_USERSPACE) the function unlocks the queue and ends the request
directly with fuse_request_end(), but it never clears ent->fuse_req.

ent->fuse_req then keeps pointing at the freed fuse_req while the entry
remains on a queue list. Subsequent teardown via
fuse_uring_entry_teardown() reads ent->fuse_req under queue->lock and
hands the dangling pointer to fuse_uring_stop_fuse_req_end(), which
dereferences it and calls fuse_request_end() a second time on freed
memory.

Route the error branch through fuse_uring_req_end() instead. That
helper acquires queue->lock, clears ent->fuse_req under the lock,
removes the request from any list it is still on, drops the lock, sets
req->out.h.error, clears FR_SENT and ends the request. The
ent->fuse_req = NULL store under the lock is what closes the window
for the later teardown reader.

Fixes: c090c8abae4b ("fuse: Add io-uring sqe commit and fetch support")
Cc: stable@vger.kernel.org # 6.14+
Signed-off-by: Berkant Koc <me@berkoc.com>
---
 fs/fuse/dev_uring.c | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)

diff --git a/fs/fuse/dev_uring.c b/fs/fuse/dev_uring.c
index 7b9822e8837b..7523569ffdce 100644
--- a/fs/fuse/dev_uring.c
+++ b/fs/fuse/dev_uring.c
@@ -924,9 +924,7 @@ static int fuse_uring_commit_fetch(struct io_uring_cmd *cmd, int issue_flags,
 		pr_info_ratelimited("qid=%d commit_id %llu state %d",
 				    queue->qid, commit_id, ent->state);
 		spin_unlock(&queue->lock);
-		req->out.h.error = err;
-		clear_bit(FR_SENT, &req->flags);
-		fuse_request_end(req);
+		fuse_uring_req_end(ent, req, err);
 		return err;
 	}

--
2.47.3

^ permalink raw reply related	[flat|nested] 20+ messages in thread

* [PATCH 2/2] fuse: wait for aborted connection before releasing last fuse_dev
  2026-05-17 12:59   ` [PATCH 0/2] fuse: io-uring: fix two UAFs in dev_uring.c teardown Berkant Koc
  2026-05-17 12:59     ` [PATCH 1/2] fuse: io-uring: clear ent->fuse_req in commit_fetch error path Berkant Koc
@ 2026-05-17 12:59     ` Berkant Koc
  2026-05-17 15:00       ` Bernd Schubert
  2026-05-17 13:14     ` [PATCH 0/2] fuse: io-uring: fix two UAFs in dev_uring.c teardown Berkant Koc
  2 siblings, 1 reply; 20+ messages in thread
From: Berkant Koc @ 2026-05-17 12:59 UTC (permalink / raw)
  To: Greg KH, Miklos Szeredi, Bernd Schubert
  Cc: security, Joanne Koong, linux-fuse, linux-kernel

From: Berkant Koc <me@berkoc.com>

fuse_dev_release() on the last fuse_dev of a connection calls
fuse_abort_conn(fc) and then immediately fuse_conn_put(fc). For io-uring
backed connections fuse_abort_conn() reaches fuse_uring_abort(), which
runs fuse_uring_teardown_all_queues() synchronously once and then
schedules ring->async_teardown_work to run after
FUSE_URING_TEARDOWN_INTERVAL (HZ/20). If the synchronous pass left
queue_refs > 0 the work owns further accesses to ring->queues[*]->
ent_avail_queue and ent_in_userspace entries.

Meanwhile fuse_conn_put() can drop the last reference and arm
delayed_release() via call_rcu(). After the RCU grace period
delayed_release() calls fuse_uring_destruct(), which kfree()s the ring
entries on each queue->ent_released list. The previously scheduled
async_teardown_work then runs and walks per-queue lists that contain
freed entries, producing a slab-use-after-free reported by KASAN at
fuse_uring_teardown_all_queues+0xee reading ent->list.next from a
freed kmalloc-192 region.

fuse_wait_aborted() already exists for this purpose: it waits on
fc->blocked_waitq for num_waiting to drain and then calls
fuse_uring_wait_stopped_queues(), which waits for ring->queue_refs to
reach zero. Call it between fuse_abort_conn() and fuse_conn_put() on
the last-device path so the io-uring teardown work has fully drained
before the connection can be torn down.

Fixes: c090c8abae4b ("fuse: Add io-uring sqe commit and fetch support")
Cc: stable@vger.kernel.org # 6.14+
Tested-by: Berkant Koc <me@berkoc.com>
Signed-off-by: Berkant Koc <me@berkoc.com>
---
 fs/fuse/dev.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c
index 5dda7080f4a9..7d9c06654a98 100644
--- a/fs/fuse/dev.c
+++ b/fs/fuse/dev.c
@@ -2566,6 +2566,7 @@ int fuse_dev_release(struct inode *inode, struct file *file)
 		if (last) {
 			WARN_ON(fc->iq.fasync != NULL);
 			fuse_abort_conn(fc);
+			fuse_wait_aborted(fc);
 		}
 		fuse_conn_put(fc);
 	}
--
2.47.3

^ permalink raw reply related	[flat|nested] 20+ messages in thread

* Re: [PATCH 0/2] fuse: io-uring: fix two UAFs in dev_uring.c teardown
  2026-05-17 12:59   ` [PATCH 0/2] fuse: io-uring: fix two UAFs in dev_uring.c teardown Berkant Koc
  2026-05-17 12:59     ` [PATCH 1/2] fuse: io-uring: clear ent->fuse_req in commit_fetch error path Berkant Koc
  2026-05-17 12:59     ` [PATCH 2/2] fuse: wait for aborted connection before releasing last fuse_dev Berkant Koc
@ 2026-05-17 13:14     ` Berkant Koc
  2026-05-17 13:43       ` Bernd Schubert
  2 siblings, 1 reply; 20+ messages in thread
From: Berkant Koc @ 2026-05-17 13:14 UTC (permalink / raw)
  To: Greg KH, Miklos Szeredi, Bernd Schubert
  Cc: linux-fsdevel, linux-kernel, security, Joanne Koong

Quick correction on the Cc list of this series:

linux-fuse@vger.kernel.org does not exist as a vger list and the
three patch mails bounced from it (550 5.1.1 User unknown).
Per MAINTAINERS, the FUSE list is linux-fsdevel@vger.kernel.org;
adding it now so the series shows up in the lore.kernel.org archive
for the FUSE-fsdevel readership. The original patches and KASAN
context are in this thread via In-Reply-To.

No content change to the patches; this is purely a list-routing fix.

Apologies for the noise.

Berkant

^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH 0/2] fuse: io-uring: fix two UAFs in dev_uring.c teardown
  2026-05-17 13:14     ` [PATCH 0/2] fuse: io-uring: fix two UAFs in dev_uring.c teardown Berkant Koc
@ 2026-05-17 13:43       ` Bernd Schubert
  2026-05-17 14:02         ` Berkant Koc
  0 siblings, 1 reply; 20+ messages in thread
From: Bernd Schubert @ 2026-05-17 13:43 UTC (permalink / raw)
  To: Berkant Koc, Greg KH, Miklos Szeredi, Bernd Schubert
  Cc: linux-fsdevel, linux-kernel, security, Joanne Koong



On 5/17/26 15:14, Berkant Koc wrote:
> Quick correction on the Cc list of this series:
> 
> linux-fuse@vger.kernel.org does not exist as a vger list and the
> three patch mails bounced from it (550 5.1.1 User unknown).

The right list is fuse-devel@lists.linux.dev. MAINTAINERS file is in the
process to get updated.

> Per MAINTAINERS, the FUSE list is linux-fsdevel@vger.kernel.org;
> adding it now so the series shows up in the lore.kernel.org archive
> for the FUSE-fsdevel readership. The original patches and KASAN
> context are in this thread via In-Reply-To.
> 
> No content change to the patches; this is purely a list-routing fix.
> 
> Apologies for the noise.
> 
> Berkant
> 


Thanks,
Bernd

^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH 0/2] fuse: io-uring: fix two UAFs in dev_uring.c teardown
  2026-05-17 13:43       ` Bernd Schubert
@ 2026-05-17 14:02         ` Berkant Koc
  0 siblings, 0 replies; 20+ messages in thread
From: Berkant Koc @ 2026-05-17 14:02 UTC (permalink / raw)
  To: Bernd Schubert
  Cc: Miklos Szeredi, Greg KH, security, Joanne Koong, linux-fsdevel,
	linux-kernel, fuse-devel

Thanks Bernd, adding fuse-devel@lists.linux.dev to Cc now so this
subthread lands in the right archive. Will use that as the FUSE
mailing list going forward.

Berkant

^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH 1/2] fuse: io-uring: clear ent->fuse_req in commit_fetch error path
  2026-05-17 12:59     ` [PATCH 1/2] fuse: io-uring: clear ent->fuse_req in commit_fetch error path Berkant Koc
@ 2026-05-17 14:11       ` Bernd Schubert
  2026-05-17 14:24         ` Berkant Koc
  0 siblings, 1 reply; 20+ messages in thread
From: Bernd Schubert @ 2026-05-17 14:11 UTC (permalink / raw)
  To: Berkant Koc, Greg KH, Miklos Szeredi
  Cc: security, Joanne Koong, linux-fuse, linux-kernel, Zhenghang Xiao

On 5/17/26 14:59, Berkant Koc wrote:
> [You don't often get email from me@berkoc.com. Learn why this is important at https://aka.ms/LearnAboutSenderIdentification ]
> 
> From: Berkant Koc <me@berkoc.com>
> 
> fuse_uring_commit_fetch() locates a request, removes it from the
> processing queue, clears req->ring_entry, then calls
> fuse_ring_ent_set_commit() under queue->lock. On the error branch
> (set_commit returning non-zero because the entry is not in
> FRRS_USERSPACE) the function unlocks the queue and ends the request
> directly with fuse_request_end(), but it never clears ent->fuse_req.
> 
> ent->fuse_req then keeps pointing at the freed fuse_req while the entry
> remains on a queue list. Subsequent teardown via
> fuse_uring_entry_teardown() reads ent->fuse_req under queue->lock and
> hands the dangling pointer to fuse_uring_stop_fuse_req_end(), which
> dereferences it and calls fuse_request_end() a second time on freed
> memory.
> 
> Route the error branch through fuse_uring_req_end() instead. That
> helper acquires queue->lock, clears ent->fuse_req under the lock,
> removes the request from any list it is still on, drops the lock, sets
> req->out.h.error, clears FR_SENT and ends the request. The
> ent->fuse_req = NULL store under the lock is what closes the window
> for the later teardown reader.
> 
> Fixes: c090c8abae4b ("fuse: Add io-uring sqe commit and fetch support")
> Cc: stable@vger.kernel.org # 6.14+
> Signed-off-by: Berkant Koc <me@berkoc.com>
> ---
>  fs/fuse/dev_uring.c | 4 +---
>  1 file changed, 1 insertion(+), 3 deletions(-)
> 
> diff --git a/fs/fuse/dev_uring.c b/fs/fuse/dev_uring.c
> index 7b9822e8837b..7523569ffdce 100644
> --- a/fs/fuse/dev_uring.c
> +++ b/fs/fuse/dev_uring.c
> @@ -924,9 +924,7 @@ static int fuse_uring_commit_fetch(struct io_uring_cmd *cmd, int issue_flags,
>                 pr_info_ratelimited("qid=%d commit_id %llu state %d",
>                                     queue->qid, commit_id, ent->state);
>                 spin_unlock(&queue->lock);
> -               req->out.h.error = err;
> -               clear_bit(FR_SENT, &req->flags);
> -               fuse_request_end(req);
> +               fuse_uring_req_end(ent, req, err);
>                 return err;
>         }
> 
> --
> 2.47.3

We already had a security report for that on Friday


> 
> [You don't often get email from kipreyyy@gmail.com. Learn why this is important at https://aka.ms/LearnAboutSenderIdentification ]
> 
> fuse_uring_commit_fetch() error path calls fuse_request_end(req) without
> clearing ent->fuse_req when fuse_ring_ent_set_commit() fails. The
> still-pending fuse_uring_send_in_task() task-work later dereferences the
> dangling pointer through fuse_uring_prepare_send(), causing a
> use-after-free.
> 
> Clear ent->fuse_req under queue->lock in the error path, matching the
> pattern in fuse_uring_req_end(). Add a NULL check in
> fuse_uring_send_in_task() so the entry is gracefully recycled if the
> request was detached.
> 
> Fixes: c090c8abae4b ("fuse: Add io-uring sqe commit and fetch support")
> Signed-off-by: Zhenghang Xiao
> ---
>  fs/fuse/dev_uring.c | 16 ++++++++++++++++
>  1 file changed, 16 insertions(+)
> 
> diff --git a/fs/fuse/dev_uring.c b/fs/fuse/dev_uring.c
> index 7b9822e8837b..5e51c36ae2a0 100644
> --- a/fs/fuse/dev_uring.c
> +++ b/fs/fuse/dev_uring.c
> @@ -921,6 +921,13 @@ static int fuse_uring_commit_fetch(struct io_uring_cmd *cmd, int issue_flags,
> 
>         err = fuse_ring_ent_set_commit(ent);
>         if (err != 0) {
> +               /*
> +                * Entry is not in FRRS_USERSPACE state. Clear the
> +                * back-pointer to prevent the still-pending
> +                * fuse_uring_send_in_task() from dereferencing a request
> +                * that is about to be freed.
> +                */
> +               ent->fuse_req = NULL;
>                 pr_info_ratelimited("qid=%d commit_id %llu state %d",
>                                     queue->qid, commit_id, ent->state);
>                 spin_unlock(&queue->lock);
> @@ -1220,6 +1227,15 @@ static void fuse_uring_send_in_task(struct io_tw_req tw_req, io_tw_token_t tw)
>         int err;
> 
>         if (!tw.cancel) {
> +               /*
> +                * If the request was detached (e.g. by fuse_uring_commit_fetch
> +                * error path), fuse_req will be NULL. Bail out and recycle the
> +                * entry for the next request.
> +                */
> +               if (!ent->fuse_req) {
> +                       fuse_uring_next_fuse_req(ent, queue, issue_flags);
> +                       return;
> +               }
>                 err = fuse_uring_prepare_send(ent, ent->fuse_req);
>                 if (err) {
>                         fuse_uring_next_fuse_req(ent, queue, issue_flags);
> --




I had already replied to Zhenghang on Friday, I don't think it is
enough. This condition

>         err = fuse_ring_ent_set_commit(ent);
>         if (err != 0) {

can also be triggered by a bad fuse-server / user-space and a teardown
race. The tear down race is easy, I think.

Fuse-server that wants to trick us is harder, as checking for
ent->fuse_req == NULL in fuse_uring_send_in_task() is not enough, the
race is valid all over the copy operation (fuse_uring_prepare_send()). I
didn't come to it yesterday (was working for main work), going to look
into it a bit later today.


Thanks,
Bernd



^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH 1/2] fuse: io-uring: clear ent->fuse_req in commit_fetch error path
  2026-05-17 14:11       ` Bernd Schubert
@ 2026-05-17 14:24         ` Berkant Koc
  0 siblings, 0 replies; 20+ messages in thread
From: Berkant Koc @ 2026-05-17 14:24 UTC (permalink / raw)
  To: Bernd Schubert
  Cc: Miklos Szeredi, Greg KH, security, Joanne Koong, fuse-devel,
	linux-fsdevel, linux-kernel

On 2026-05-17 16:11, Bernd Schubert wrote:
> We already had a security report for that on Friday [...] I had
> already replied to Zhenghang on Friday, I don't think it is enough.
> [...] valid all over the copy operation (fuse_uring_prepare_send())

Thanks for the context. P1 is a duplicate of Zhenghang's Friday report,
please consider it withdrawn.

You are right that clearing ent->fuse_req only in the commit_fetch error
path is not sufficient. The same window is reachable across the whole
copy path in fuse_uring_prepare_send(), so a single-point clear leaves
the race open on the other exits. I will not push a v2 for this one and
leave the scope call to you.

P2 ([PATCH 2/2] serialize ring teardown and per-ent setup against
ent->state writers) is a separate path: ent->state being written without
the queue lock while teardown frees the ring. If that overlaps with what
you are looking at today, I will hold off on P2 as well. If it is out of
scope for your work, a short note is enough and I will keep tracking it
independently.

KASAN config and the repro harness (qemu + libfuse uring example with
abort-on-mount) are set up here, happy to test your fix once it is on
the list.

Thanks,
Berkant

^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH 2/2] fuse: wait for aborted connection before releasing last fuse_dev
  2026-05-17 12:59     ` [PATCH 2/2] fuse: wait for aborted connection before releasing last fuse_dev Berkant Koc
@ 2026-05-17 15:00       ` Bernd Schubert
  2026-05-18  1:13         ` Berkant Koc
  2026-05-18  9:06         ` Pavel Begunkov
  0 siblings, 2 replies; 20+ messages in thread
From: Bernd Schubert @ 2026-05-17 15:00 UTC (permalink / raw)
  To: Berkant Koc, Greg KH, Miklos Szeredi
  Cc: security@kernel.org, Joanne Koong, linux-fuse@vger.kernel.org,
	linux-kernel@vger.kernel.org, io-uring@vger.kernel.org,
	Jens Axboe, Pavel Begunkov, fuse-devel

On 5/17/26 14:59, Berkant Koc wrote:
> [You don't often get email from me@berkoc.com. Learn why this is important at https://aka.ms/LearnAboutSenderIdentification ]
> 
> From: Berkant Koc <me@berkoc.com>
> 
> fuse_dev_release() on the last fuse_dev of a connection calls
> fuse_abort_conn(fc) and then immediately fuse_conn_put(fc). For io-uring
> backed connections fuse_abort_conn() reaches fuse_uring_abort(), which
> runs fuse_uring_teardown_all_queues() synchronously once and then
> schedules ring->async_teardown_work to run after
> FUSE_URING_TEARDOWN_INTERVAL (HZ/20). If the synchronous pass left
> queue_refs > 0 the work owns further accesses to ring->queues[*]->
> ent_avail_queue and ent_in_userspace entries.
> 
> Meanwhile fuse_conn_put() can drop the last reference and arm
> delayed_release() via call_rcu(). After the RCU grace period
> delayed_release() calls fuse_uring_destruct(), which kfree()s the ring
> entries on each queue->ent_released list. The previously scheduled
> async_teardown_work then runs and walks per-queue lists that contain
> freed entries, producing a slab-use-after-free reported by KASAN at
> fuse_uring_teardown_all_queues+0xee reading ent->list.next from a
> freed kmalloc-192 region.
> 
> fuse_wait_aborted() already exists for this purpose: it waits on
> fc->blocked_waitq for num_waiting to drain and then calls
> fuse_uring_wait_stopped_queues(), which waits for ring->queue_refs to
> reach zero. Call it between fuse_abort_conn() and fuse_conn_put() on
> the last-device path so the io-uring teardown work has fully drained
> before the connection can be torn down.
> 
> Fixes: c090c8abae4b ("fuse: Add io-uring sqe commit and fetch support")
> Cc: stable@vger.kernel.org # 6.14+
> Tested-by: Berkant Koc <me@berkoc.com>
> Signed-off-by: Berkant Koc <me@berkoc.com>
> ---
>  fs/fuse/dev.c | 1 +
>  1 file changed, 1 insertion(+)
> 
> diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c
> index 5dda7080f4a9..7d9c06654a98 100644
> --- a/fs/fuse/dev.c
> +++ b/fs/fuse/dev.c
> @@ -2566,6 +2566,7 @@ int fuse_dev_release(struct inode *inode, struct file *file)
>                 if (last) {
>                         WARN_ON(fc->iq.fasync != NULL);
>                         fuse_abort_conn(fc);
> +                       fuse_wait_aborted(fc);
>                 }
>                 fuse_conn_put(fc);
>         }

I might be wrong, but I don't think it is possible, Maybe Pavel or Jens
could help (added to CC). Basically as long as
fuse_uring_async_stop_queues() runs we do not have completed all
io-uring commands via io_uring_cmd_done() and as long as we do not have
completed these io-uring commands.

References are taken here

io_issue_sqe
  io_assign_file
     io_file_get_normal
        fget


  __io_uring_cmd_done()
    req->io_task_work.func = io_req_task_complete
    io_req_task_work_add(req)             io_uring/uring_cmd.c:179



io_req_task_complete
   io_req_complete_defer
     wq_list_add_tail

__io_submit_flush_completions
   io_free_batch_list
     io_put_file
       fput


I also look it up for fixed file and for fixed file it needs
io_ring_ctx_free(), which is then only one after completing all
uring_cmd objects (io_uring_cmd_done).


I don't mind adding fuse_wait_aborted() here, but IMHO it is a no-op and
no security issue.


Thanks
Bernd

^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH 2/2] fuse: wait for aborted connection before releasing last fuse_dev
  2026-05-17 15:00       ` Bernd Schubert
@ 2026-05-18  1:13         ` Berkant Koc
  2026-05-18  9:55           ` Bernd Schubert
  2026-05-18  9:06         ` Pavel Begunkov
  1 sibling, 1 reply; 20+ messages in thread
From: Berkant Koc @ 2026-05-18  1:13 UTC (permalink / raw)
  To: Bernd Schubert, Greg KH, Miklos Szeredi
  Cc: security, Joanne Koong, linux-fuse, linux-kernel, io-uring,
	Jens Axboe, Pavel Begunkov, fuse-devel

[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #1: Type: text/plain, Size: 1867 bytes --]

Bernd, thanks for pushing back. Stepping through this against the trace:

fuse_conn_destroy() in fs/fuse/inode.c calls fuse_wait_aborted()
between fuse_abort_conn() and the eventual fuse_conn_put() (from
fuse_sb_destroy). fuse_dev_release() in fs/fuse/dev.c does not wait
between its fuse_abort_conn() and fuse_conn_put(). That asymmetry is
the race.

On topologies where the last fud release IS the last conn ref
(no superblock mount, no other fud open — exactly the PoC setup),
fuse_conn_put() drops the count to zero, call_rcu schedules
delayed_release, and fuse_uring_destruct kfrees ring/queue/ent_released
slabs. async_teardown_work, scheduled by fuse_uring_async_stop_queues
via the teardown-interval delayed_work, then runs on freed memory.

The KASAN trace at top-finding/kasan-trace.txt shows exactly that
interleaving:

  free site: fuse_uring_destruct ← delayed_release ← rcu_core
  use site:  fuse_uring_teardown_all_queues ← async_teardown_work
             (workqueue), reading ent->list.next from
             kmalloc-192 freed by destruct

Your in-flight cmd ref invariant holds on both fixed and non-fixed
paths (non-fixed via per-cmd io_put_file in io_free_batch_list, fixed
via the io_uring file table slot pinning struct file → fud → fuse_conn).
But neither covers the gap between fuse_abort_conn (which schedules
the async work and returns immediately) and the RCU callback. The
PoC topology removes every other ref-holder, so that gap becomes the
last conn ref.

The patch restores symmetry with fuse_conn_destroy by waiting on
ring->queue_refs == 0 (via fuse_wait_aborted → fuse_uring_wait_stopped_queues)
before the put. That guarantees async_teardown_work has finished
before RCU is armed.

The race is reproducible with mdelay-widening; without widening I see
0 trips in 50 iter, but the window is in the code paths.

Berkant

^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH 2/2] fuse: wait for aborted connection before releasing last fuse_dev
  2026-05-17 15:00       ` Bernd Schubert
  2026-05-18  1:13         ` Berkant Koc
@ 2026-05-18  9:06         ` Pavel Begunkov
  2026-05-18  9:50           ` Bernd Schubert
  1 sibling, 1 reply; 20+ messages in thread
From: Pavel Begunkov @ 2026-05-18  9:06 UTC (permalink / raw)
  To: Bernd Schubert, Berkant Koc, Greg KH, Miklos Szeredi
  Cc: security@kernel.org, Joanne Koong, linux-fuse@vger.kernel.org,
	linux-kernel@vger.kernel.org, io-uring@vger.kernel.org,
	Jens Axboe, fuse-devel

On 5/17/26 16:00, Bernd Schubert wrote:
> On 5/17/26 14:59, Berkant Koc wrote:
>> [You don't often get email from me@berkoc.com. Learn why this is important at https://aka.ms/LearnAboutSenderIdentification ]
>>
>> From: Berkant Koc <me@berkoc.com>
>>
>> fuse_dev_release() on the last fuse_dev of a connection calls
>> fuse_abort_conn(fc) and then immediately fuse_conn_put(fc). For io-uring
>> backed connections fuse_abort_conn() reaches fuse_uring_abort(), which
>> runs fuse_uring_teardown_all_queues() synchronously once and then
>> schedules ring->async_teardown_work to run after
>> FUSE_URING_TEARDOWN_INTERVAL (HZ/20). If the synchronous pass left
>> queue_refs > 0 the work owns further accesses to ring->queues[*]->
>> ent_avail_queue and ent_in_userspace entries.
>>
>> Meanwhile fuse_conn_put() can drop the last reference and arm
>> delayed_release() via call_rcu(). After the RCU grace period
>> delayed_release() calls fuse_uring_destruct(), which kfree()s the ring
>> entries on each queue->ent_released list. The previously scheduled
>> async_teardown_work then runs and walks per-queue lists that contain
>> freed entries, producing a slab-use-after-free reported by KASAN at
>> fuse_uring_teardown_all_queues+0xee reading ent->list.next from a
>> freed kmalloc-192 region.
>>
>> fuse_wait_aborted() already exists for this purpose: it waits on
>> fc->blocked_waitq for num_waiting to drain and then calls
>> fuse_uring_wait_stopped_queues(), which waits for ring->queue_refs to
>> reach zero. Call it between fuse_abort_conn() and fuse_conn_put() on
>> the last-device path so the io-uring teardown work has fully drained
>> before the connection can be torn down.
>>
>> Fixes: c090c8abae4b ("fuse: Add io-uring sqe commit and fetch support")
>> Cc: stable@vger.kernel.org # 6.14+
>> Tested-by: Berkant Koc <me@berkoc.com>
>> Signed-off-by: Berkant Koc <me@berkoc.com>
>> ---
>>   fs/fuse/dev.c | 1 +
>>   1 file changed, 1 insertion(+)
>>
>> diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c
>> index 5dda7080f4a9..7d9c06654a98 100644
>> --- a/fs/fuse/dev.c
>> +++ b/fs/fuse/dev.c
>> @@ -2566,6 +2566,7 @@ int fuse_dev_release(struct inode *inode, struct file *file)
>>                  if (last) {
>>                          WARN_ON(fc->iq.fasync != NULL);
>>                          fuse_abort_conn(fc);
>> +                       fuse_wait_aborted(fc);
>>                  }
>>                  fuse_conn_put(fc);
>>          }
> 
> I might be wrong, but I don't think it is possible, Maybe Pavel or Jens
> could help (added to CC). Basically as long as
> fuse_uring_async_stop_queues() runs we do not have completed all
> io-uring commands via io_uring_cmd_done() and as long as we do not have
> completed these io-uring commands.

If I understand the question right, yes, fuse io_uring cmd requests hold
a reference to the fuse file, so until you complete them the file will
not get released.

-- 
Pavel Begunkov


^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH 2/2] fuse: wait for aborted connection before releasing last fuse_dev
  2026-05-18  9:06         ` Pavel Begunkov
@ 2026-05-18  9:50           ` Bernd Schubert
  2026-05-18 10:32             ` Pavel Begunkov
  0 siblings, 1 reply; 20+ messages in thread
From: Bernd Schubert @ 2026-05-18  9:50 UTC (permalink / raw)
  To: Pavel Begunkov, Bernd Schubert, Berkant Koc, Greg KH,
	Miklos Szeredi
  Cc: security@kernel.org, Joanne Koong, linux-kernel@vger.kernel.org,
	io-uring@vger.kernel.org, Jens Axboe, fuse-devel



On 5/18/26 11:06, Pavel Begunkov wrote:
> On 5/17/26 16:00, Bernd Schubert wrote:
>> On 5/17/26 14:59, Berkant Koc wrote:
>>> [You don't often get email from me@berkoc.com. Learn why this is important at https://aka.ms/LearnAboutSenderIdentification ]
>>>
>>> From: Berkant Koc <me@berkoc.com>
>>>
>>> fuse_dev_release() on the last fuse_dev of a connection calls
>>> fuse_abort_conn(fc) and then immediately fuse_conn_put(fc). For io-uring
>>> backed connections fuse_abort_conn() reaches fuse_uring_abort(), which
>>> runs fuse_uring_teardown_all_queues() synchronously once and then
>>> schedules ring->async_teardown_work to run after
>>> FUSE_URING_TEARDOWN_INTERVAL (HZ/20). If the synchronous pass left
>>> queue_refs > 0 the work owns further accesses to ring->queues[*]->
>>> ent_avail_queue and ent_in_userspace entries.
>>>
>>> Meanwhile fuse_conn_put() can drop the last reference and arm
>>> delayed_release() via call_rcu(). After the RCU grace period
>>> delayed_release() calls fuse_uring_destruct(), which kfree()s the ring
>>> entries on each queue->ent_released list. The previously scheduled
>>> async_teardown_work then runs and walks per-queue lists that contain
>>> freed entries, producing a slab-use-after-free reported by KASAN at
>>> fuse_uring_teardown_all_queues+0xee reading ent->list.next from a
>>> freed kmalloc-192 region.
>>>
>>> fuse_wait_aborted() already exists for this purpose: it waits on
>>> fc->blocked_waitq for num_waiting to drain and then calls
>>> fuse_uring_wait_stopped_queues(), which waits for ring->queue_refs to
>>> reach zero. Call it between fuse_abort_conn() and fuse_conn_put() on
>>> the last-device path so the io-uring teardown work has fully drained
>>> before the connection can be torn down.
>>>
>>> Fixes: c090c8abae4b ("fuse: Add io-uring sqe commit and fetch support")
>>> Cc: stable@vger.kernel.org # 6.14+
>>> Tested-by: Berkant Koc <me@berkoc.com>
>>> Signed-off-by: Berkant Koc <me@berkoc.com>
>>> ---
>>>   fs/fuse/dev.c | 1 +
>>>   1 file changed, 1 insertion(+)
>>>
>>> diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c
>>> index 5dda7080f4a9..7d9c06654a98 100644
>>> --- a/fs/fuse/dev.c
>>> +++ b/fs/fuse/dev.c
>>> @@ -2566,6 +2566,7 @@ int fuse_dev_release(struct inode *inode, struct file *file)
>>>                  if (last) {
>>>                          WARN_ON(fc->iq.fasync != NULL);
>>>                          fuse_abort_conn(fc);
>>> +                       fuse_wait_aborted(fc);
>>>                  }
>>>                  fuse_conn_put(fc);
>>>          }
>>
>> I might be wrong, but I don't think it is possible, Maybe Pavel or Jens
>> could help (added to CC). Basically as long as
>> fuse_uring_async_stop_queues() runs we do not have completed all
>> io-uring commands via io_uring_cmd_done() and as long as we do not have
>> completed these io-uring commands.
> 
> If I understand the question right, yes, fuse io_uring cmd requests hold
> a reference to the fuse file, so until you complete them the file will
> not get released.


Sorry, had totally messed up the phrase, can't read it myself.

What I mean is that the io-uring was set up with /dev/fuse as file and
as long as fuse holds non-completed 'struct io_uring_cmd *cmd' objects
there is a reference on the /dev/fuse fd, which blocks the call of
fuse_dev_release().


Thanks,
Bernd

^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH 2/2] fuse: wait for aborted connection before releasing last fuse_dev
  2026-05-18  1:13         ` Berkant Koc
@ 2026-05-18  9:55           ` Bernd Schubert
  2026-05-18 11:47             ` Bernd Schubert
  0 siblings, 1 reply; 20+ messages in thread
From: Bernd Schubert @ 2026-05-18  9:55 UTC (permalink / raw)
  To: Berkant Koc, Greg KH, Miklos Szeredi
  Cc: security@kernel.org, Joanne Koong, linux-fuse@vger.kernel.org,
	linux-kernel@vger.kernel.org, io-uring@vger.kernel.org,
	Jens Axboe, Pavel Begunkov, fuse-devel

On 5/18/26 03:13, Berkant Koc wrote:
> [You don't often get email from me@berkoc.com. Learn why this is important at https://aka.ms/LearnAboutSenderIdentification ]
> 
> Bernd, thanks for pushing back. Stepping through this against the trace:
> 
> fuse_conn_destroy() in fs/fuse/inode.c calls fuse_wait_aborted()
> between fuse_abort_conn() and the eventual fuse_conn_put() (from
> fuse_sb_destroy). fuse_dev_release() in fs/fuse/dev.c does not wait
> between its fuse_abort_conn() and fuse_conn_put(). That asymmetry is
> the race.
> 
> On topologies where the last fud release IS the last conn ref
> (no superblock mount, no other fud open — exactly the PoC setup),
> fuse_conn_put() drops the count to zero, call_rcu schedules
> delayed_release, and fuse_uring_destruct kfrees ring/queue/ent_released
> slabs. async_teardown_work, scheduled by fuse_uring_async_stop_queues
> via the teardown-interval delayed_work, then runs on freed memory.
> 
> The KASAN trace at top-finding/kasan-trace.txt shows exactly that
> interleaving:
> 
>   free site: fuse_uring_destruct ← delayed_release ← rcu_core
>   use site:  fuse_uring_teardown_all_queues ← async_teardown_work
>              (workqueue), reading ent->list.next from
>              kmalloc-192 freed by destruct
> 
> Your in-flight cmd ref invariant holds on both fixed and non-fixed
> paths (non-fixed via per-cmd io_put_file in io_free_batch_list, fixed
> via the io_uring file table slot pinning struct file → fud → fuse_conn).
> But neither covers the gap between fuse_abort_conn (which schedules
> the async work and returns immediately) and the RCU callback. The
> PoC topology removes every other ref-holder, so that gap becomes the
> last conn ref.
> 
> The patch restores symmetry with fuse_conn_destroy by waiting on
> ring->queue_refs == 0 (via fuse_wait_aborted → fuse_uring_wait_stopped_queues)
> before the put. That guarantees async_teardown_work has finished
> before RCU is armed.
> 
> The race is reproducible with mdelay-widening; without widening I see
> 0 trips in 50 iter, but the window is in the code paths.

I think I see what the actual issue is, we need an fc (or in linux-next
struct fuse_chan) reference as long as fuse_uring_async_stop_queues()
runs. Patch follows.


Thanks,
Bernd

^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH 2/2] fuse: wait for aborted connection before releasing last fuse_dev
  2026-05-18  9:50           ` Bernd Schubert
@ 2026-05-18 10:32             ` Pavel Begunkov
  0 siblings, 0 replies; 20+ messages in thread
From: Pavel Begunkov @ 2026-05-18 10:32 UTC (permalink / raw)
  To: Bernd Schubert, Bernd Schubert, Berkant Koc, Greg KH,
	Miklos Szeredi
  Cc: security@kernel.org, Joanne Koong, linux-kernel@vger.kernel.org,
	io-uring@vger.kernel.org, Jens Axboe, fuse-devel

On 5/18/26 10:50, Bernd Schubert wrote:

>>>> diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c
>>>> index 5dda7080f4a9..7d9c06654a98 100644
>>>> --- a/fs/fuse/dev.c
>>>> +++ b/fs/fuse/dev.c
>>>> @@ -2566,6 +2566,7 @@ int fuse_dev_release(struct inode *inode, struct file *file)
>>>>                   if (last) {
>>>>                           WARN_ON(fc->iq.fasync != NULL);
>>>>                           fuse_abort_conn(fc);
>>>> +                       fuse_wait_aborted(fc);
>>>>                   }
>>>>                   fuse_conn_put(fc);
>>>>           }
>>>
>>> I might be wrong, but I don't think it is possible, Maybe Pavel or Jens
>>> could help (added to CC). Basically as long as
>>> fuse_uring_async_stop_queues() runs we do not have completed all
>>> io-uring commands via io_uring_cmd_done() and as long as we do not have
>>> completed these io-uring commands.
>>
>> If I understand the question right, yes, fuse io_uring cmd requests hold
>> a reference to the fuse file, so until you complete them the file will
>> not get released.
> 
> 
> Sorry, had totally messed up the phrase, can't read it myself.
> 
> What I mean is that the io-uring was set up with /dev/fuse as file and
> as long as fuse holds non-completed 'struct io_uring_cmd *cmd' objects
> there is a reference on the /dev/fuse fd, which blocks the call of
> fuse_dev_release().

Yep, that's right

-- 
Pavel Begunkov


^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH 2/2] fuse: wait for aborted connection before releasing last fuse_dev
  2026-05-18  9:55           ` Bernd Schubert
@ 2026-05-18 11:47             ` Bernd Schubert
  2026-05-18 14:32               ` Berkant Koc
  0 siblings, 1 reply; 20+ messages in thread
From: Bernd Schubert @ 2026-05-18 11:47 UTC (permalink / raw)
  To: Berkant Koc, Greg KH, Miklos Szeredi
  Cc: security@kernel.org, Joanne Koong, linux-kernel@vger.kernel.org,
	io-uring@vger.kernel.org, Jens Axboe, Pavel Begunkov, fuse-devel

[-- Attachment #1: Type: text/plain, Size: 2480 bytes --]

On 5/18/26 11:55, Bernd Schubert wrote:
> On 5/18/26 03:13, Berkant Koc wrote:
>> [You don't often get email from me@berkoc.com. Learn why this is important at https://aka.ms/LearnAboutSenderIdentification ]
>>
>> Bernd, thanks for pushing back. Stepping through this against the trace:
>>
>> fuse_conn_destroy() in fs/fuse/inode.c calls fuse_wait_aborted()
>> between fuse_abort_conn() and the eventual fuse_conn_put() (from
>> fuse_sb_destroy). fuse_dev_release() in fs/fuse/dev.c does not wait
>> between its fuse_abort_conn() and fuse_conn_put(). That asymmetry is
>> the race.
>>
>> On topologies where the last fud release IS the last conn ref
>> (no superblock mount, no other fud open — exactly the PoC setup),
>> fuse_conn_put() drops the count to zero, call_rcu schedules
>> delayed_release, and fuse_uring_destruct kfrees ring/queue/ent_released
>> slabs. async_teardown_work, scheduled by fuse_uring_async_stop_queues
>> via the teardown-interval delayed_work, then runs on freed memory.
>>
>> The KASAN trace at top-finding/kasan-trace.txt shows exactly that
>> interleaving:
>>
>>   free site: fuse_uring_destruct ← delayed_release ← rcu_core
>>   use site:  fuse_uring_teardown_all_queues ← async_teardown_work
>>              (workqueue), reading ent->list.next from
>>              kmalloc-192 freed by destruct
>>
>> Your in-flight cmd ref invariant holds on both fixed and non-fixed
>> paths (non-fixed via per-cmd io_put_file in io_free_batch_list, fixed
>> via the io_uring file table slot pinning struct file → fud → fuse_conn).
>> But neither covers the gap between fuse_abort_conn (which schedules
>> the async work and returns immediately) and the RCU callback. The
>> PoC topology removes every other ref-holder, so that gap becomes the
>> last conn ref.
>>
>> The patch restores symmetry with fuse_conn_destroy by waiting on
>> ring->queue_refs == 0 (via fuse_wait_aborted → fuse_uring_wait_stopped_queues)
>> before the put. That guarantees async_teardown_work has finished
>> before RCU is armed.
>>
>> The race is reproducible with mdelay-widening; without widening I see
>> 0 trips in 50 iter, but the window is in the code paths.
> 
> I think I see what the actual issue is, we need an fc (or in linux-next
> struct fuse_chan) reference as long as fuse_uring_async_stop_queues()
> runs. Patch follows.
> 

Would it be possible for you to test the attached patch?


Thanks,
Bernd


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: fuse_uring_async_stop_queues.patch --]
[-- Type: text/x-patch; name="fuse_uring_async_stop_queues.patch", Size: 1561 bytes --]

commit b6f63cc1d2f7b8d690835c965d600fda47ffd79b
Author: Bernd Schubert <bernd@bsbernd.com>
Date:   Mon May 18 13:30:30 2026 +0200

    fuse: Avoid use-after-free in fuse_uring_async_stop_queues
    
    fuse_uring_async_stop_queues() might run when the last reference
    on ring->queue_refs was already dropped.
    
    In order to avoid an early destruction a reference on struct fuse_conn
    is now taken before starting fuse_uring_async_stop_queues() and that
    reference is only released when that delayed work queue terminates.
    
    Fixes: 4a9bfb9b6850 ("fuse: {io-uring} Handle teardown of ring entries")
    Cc: stable@kernel.org # 6.14
    Reported-by: Berkant Koc <me@berkoc.com>
    Signed-off-by: Bernd Schubert <bernd@bsbernd.com>

diff --git a/fs/fuse/dev_uring.c b/fs/fuse/dev_uring.c
index e467b23e6895..c96d72250830 100644
--- a/fs/fuse/dev_uring.c
+++ b/fs/fuse/dev_uring.c
@@ -465,7 +465,10 @@ static void fuse_uring_async_stop_queues(struct work_struct *work)
 		schedule_delayed_work(&ring->async_teardown_work,
 				      FUSE_URING_TEARDOWN_INTERVAL);
 	} else {
+		struct fuse_chan *chan = ring->chan;
+
 		wake_up_all(&ring->stop_waitq);
+		fuse_conn_put(chan->conn);
 	}
 }
 
@@ -477,6 +480,9 @@ void fuse_uring_stop_queues(struct fuse_ring *ring)
 	fuse_uring_teardown_all_queues(ring);
 
 	if (atomic_read(&ring->queue_refs) > 0) {
+		struct fuse_chan *chan = ring->chan;
+
+		fuse_conn_get(chan->conn);
 		ring->teardown_time = jiffies;
 		INIT_DELAYED_WORK(&ring->async_teardown_work,
 				  fuse_uring_async_stop_queues);

^ permalink raw reply related	[flat|nested] 20+ messages in thread

* Re: [PATCH 2/2] fuse: wait for aborted connection before releasing last fuse_dev
  2026-05-18 11:47             ` Bernd Schubert
@ 2026-05-18 14:32               ` Berkant Koc
  2026-05-18 14:46                 ` Bernd Schubert
  0 siblings, 1 reply; 20+ messages in thread
From: Berkant Koc @ 2026-05-18 14:32 UTC (permalink / raw)
  To: Bernd Schubert
  Cc: Greg KH, Miklos Szeredi, security, Joanne Koong, linux-kernel,
	io-uring, Jens Axboe, Pavel Begunkov, fuse-devel

On Mon, 18 May 2026 11:47:00 +0000, Bernd Schubert <bschubert@ddn.com> wrote:
> Would it be possible for you to test the attached patch?

Reproducer and KASAN harness from the PATCH 2/2 series are staged.
Two-arm plan: revert vs apply, race-widening debug hunk kept in both
arms, 2x50 iterations each against torvalds/master tip, KASAN + lockdep
+ kmemleak enabled. Results back within the day once the base resolves.

Blocker before I build. The patch references ring->chan and chan->conn.
On mainline fs/fuse/dev_uring_i.h declares struct fuse_ring with
struct fuse_conn *fc at line 110, no chan member; grep fuse_chan
across fs/fuse/ returns zero hits. As-is the patch fails to compile
with "struct fuse_ring has no member named chan".

Is this based on a DDN topic branch that introduces a fuse_chan
abstraction not yet upstream? If so, point me at the base tree or
branch URL and I will rebase the test against that. If the references
were meant to be ring->fc and fc against current mainline, confirm and
I will adjust before the run.

Assisted-by: Claude:claude-opus-4-7 berkoc-pipeline

^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH 2/2] fuse: wait for aborted connection before releasing last fuse_dev
  2026-05-18 14:32               ` Berkant Koc
@ 2026-05-18 14:46                 ` Bernd Schubert
  2026-05-18 15:35                   ` Joanne Koong
  2026-05-18 15:47                   ` Berkant Koc
  0 siblings, 2 replies; 20+ messages in thread
From: Bernd Schubert @ 2026-05-18 14:46 UTC (permalink / raw)
  To: Berkant Koc, Bernd Schubert
  Cc: Greg KH, Miklos Szeredi, security, Joanne Koong, linux-kernel,
	io-uring, Jens Axboe, Pavel Begunkov, fuse-devel



On 5/18/26 16:32, Berkant Koc wrote:
> On Mon, 18 May 2026 11:47:00 +0000, Bernd Schubert <bschubert@ddn.com> wrote:
>> Would it be possible for you to test the attached patch?
> 
> Reproducer and KASAN harness from the PATCH 2/2 series are staged.
> Two-arm plan: revert vs apply, race-widening debug hunk kept in both
> arms, 2x50 iterations each against torvalds/master tip, KASAN + lockdep
> + kmemleak enabled. Results back within the day once the base resolves.
> 
> Blocker before I build. The patch references ring->chan and chan->conn.
> On mainline fs/fuse/dev_uring_i.h declares struct fuse_ring with
> struct fuse_conn *fc at line 110, no chan member; grep fuse_chan
> across fs/fuse/ returns zero hits. As-is the patch fails to compile
> with "struct fuse_ring has no member named chan".
> 
> Is this based on a DDN topic branch that introduces a fuse_chan
> abstraction not yet upstream? If so, point me at the base tree or
> branch URL and I will rebase the test against that. If the references
> were meant to be ring->fc and fc against current mainline, confirm and
> I will adjust before the run.
> 
> Assisted-by: Claude:claude-opus-4-7 berkoc-pipeline
> 

Ah, it is based on Miklos' for-next branch, which is also in linux-next
(I think). Yeah, we have a bit back port headache here.


Thanks,
Bernd

^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH 2/2] fuse: wait for aborted connection before releasing last fuse_dev
  2026-05-18 14:46                 ` Bernd Schubert
@ 2026-05-18 15:35                   ` Joanne Koong
  2026-05-18 17:49                     ` Berkant Koc
  2026-05-18 15:47                   ` Berkant Koc
  1 sibling, 1 reply; 20+ messages in thread
From: Joanne Koong @ 2026-05-18 15:35 UTC (permalink / raw)
  To: Bernd Schubert
  Cc: Berkant Koc, Bernd Schubert, Greg KH, Miklos Szeredi, security,
	linux-kernel, io-uring, Jens Axboe, Pavel Begunkov, fuse-devel

On Mon, May 18, 2026 at 7:46 AM Bernd Schubert <bernd@bsbernd.com> wrote:
>
>
>
> On 5/18/26 16:32, Berkant Koc wrote:
> > On Mon, 18 May 2026 11:47:00 +0000, Bernd Schubert <bschubert@ddn.com> wrote:
> >> Would it be possible for you to test the attached patch?

The fix looks right to me.

Reviewed-by: Joanne Koong <joannelkoong@gmail.com>

> >
> > Reproducer and KASAN harness from the PATCH 2/2 series are staged.
> > Two-arm plan: revert vs apply, race-widening debug hunk kept in both
> > arms, 2x50 iterations each against torvalds/master tip, KASAN + lockdep
> > + kmemleak enabled. Results back within the day once the base resolves.
> >
> > Blocker before I build. The patch references ring->chan and chan->conn.
> > On mainline fs/fuse/dev_uring_i.h declares struct fuse_ring with
> > struct fuse_conn *fc at line 110, no chan member; grep fuse_chan
> > across fs/fuse/ returns zero hits. As-is the patch fails to compile
> > with "struct fuse_ring has no member named chan".
> >
> > Is this based on a DDN topic branch that introduces a fuse_chan
> > abstraction not yet upstream? If so, point me at the base tree or
> > branch URL and I will rebase the test against that. If the references
> > were meant to be ring->fc and fc against current mainline, confirm and
> > I will adjust before the run.

Yes, on mainline the references are meant to be ring->fc, eg

diff --git a/fs/fuse/dev_uring.c b/fs/fuse/dev_uring.c
index 5c00fd047c8b..27c417fd9451 100644
--- a/fs/fuse/dev_uring.c
+++ b/fs/fuse/dev_uring.c
@@ -570,6 +570,7 @@ static void fuse_uring_async_stop_queues(struct
work_struct *work)
                                      FUSE_URING_TEARDOWN_INTERVAL);
        } else {
                wake_up_all(&ring->stop_waitq);
+               fuse_conn_put(ring->fc);
        }
 }

@@ -599,6 +600,7 @@ void fuse_uring_stop_queues(struct fuse_ring *ring)

        if (atomic_read(&ring->queue_refs) > 0) {
                ring->teardown_time = jiffies;
+               fuse_conn_get(ring->fc);
                INIT_DELAYED_WORK(&ring->async_teardown_work,
                                  fuse_uring_async_stop_queues);
                schedule_delayed_work(&ring->async_teardown_work,

Thanks,
Joanne

> >
> > Assisted-by: Claude:claude-opus-4-7 berkoc-pipeline
> >
>
> Ah, it is based on Miklos' for-next branch, which is also in linux-next
> (I think). Yeah, we have a bit back port headache here.
>
>
> Thanks,
> Bernd

^ permalink raw reply related	[flat|nested] 20+ messages in thread

* Re: [PATCH 2/2] fuse: wait for aborted connection before releasing last fuse_dev
  2026-05-18 14:46                 ` Bernd Schubert
  2026-05-18 15:35                   ` Joanne Koong
@ 2026-05-18 15:47                   ` Berkant Koc
  1 sibling, 0 replies; 20+ messages in thread
From: Berkant Koc @ 2026-05-18 15:47 UTC (permalink / raw)
  To: Bernd Schubert
  Cc: Bernd Schubert, Greg KH, Miklos Szeredi, security, Joanne Koong,
	linux-kernel, io-uring, Jens Axboe, Pavel Begunkov, fuse-devel

On Mon, 18 May 2026 16:46:08 +0200, Bernd Schubert <bernd@bsbernd.com> wrote:
> Ah, it is based on Miklos' for-next branch, which is also in linux-next
> (I think). Yeah, we have a bit back port headache here.

Got it, that resolves the field-name confusion on my side. I will rebase
the test harness onto Miklos' fuse.git for-next tip (and cross-check
against linux-next), then rerun the two-arm comparison against that base:
revert vs apply with the abort/release race-widening kept in place,
2x50 iterations each under KASAN + lockdep + kmemleak, with full
splats and reproducer attached on report-back.

The stable-backport question is parked until the for-next arm lands
clean. Once the trace set is in hand I will fold the v6.14 field-name
delta into a separate backport note rather than mixing it into the
mainline result.

Assisted-by: Claude:claude-opus-4-7 berkoc-pipeline

^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH 2/2] fuse: wait for aborted connection before releasing last fuse_dev
  2026-05-18 15:35                   ` Joanne Koong
@ 2026-05-18 17:49                     ` Berkant Koc
  0 siblings, 0 replies; 20+ messages in thread
From: Berkant Koc @ 2026-05-18 17:49 UTC (permalink / raw)
  To: Joanne Koong
  Cc: Bernd Schubert, Bernd Schubert, Greg KH, Miklos Szeredi, security,
	linux-kernel, io-uring, Jens Axboe, Pavel Begunkov, fuse-devel

On Mon, 18 May 2026 08:35:16 -0700, Joanne Koong <joannelkoong@gmail.com> wrote:
> Yes, on mainline the references are meant to be ring->fc, eg

Confirmed, ring->fc on mainline. Reviewed-by recorded for Bernd's v6.14 backport path.

Bernd has since published the full 4-patch series via B4-Relay at 18:37 CEST, rebased onto Miklos' for-next (base-commit 040d71ac6470). On that branch the fuse_chan abstraction is in place, so the teardown path uses ring->chan / chan->conn rather than ring->fc. The series stays as-is for for-next.

Your inline diff is the basis for the mainline stable-backport once for-next lands.

I will run the two-arm Tested-by against for-next-base 040d71ac6470 (KASAN + lockdep + kmemleak, async-teardown race + baseline) and report numbers on-thread.

Assisted-by: Claude:claude-opus-4-7 berkoc-pipeline

^ permalink raw reply	[flat|nested] 20+ messages in thread

end of thread, other threads:[~2026-05-18 17:51 UTC | newest]

Thread overview: 20+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
     [not found] <20260517095846.fuse-iouring-uaf.dc5f5dbb71dc@berkoc.com>
     [not found] ` <2026051703-equinox-multitude-91e2@gregkh>
2026-05-17 12:59   ` [PATCH 0/2] fuse: io-uring: fix two UAFs in dev_uring.c teardown Berkant Koc
2026-05-17 12:59     ` [PATCH 1/2] fuse: io-uring: clear ent->fuse_req in commit_fetch error path Berkant Koc
2026-05-17 14:11       ` Bernd Schubert
2026-05-17 14:24         ` Berkant Koc
2026-05-17 12:59     ` [PATCH 2/2] fuse: wait for aborted connection before releasing last fuse_dev Berkant Koc
2026-05-17 15:00       ` Bernd Schubert
2026-05-18  1:13         ` Berkant Koc
2026-05-18  9:55           ` Bernd Schubert
2026-05-18 11:47             ` Bernd Schubert
2026-05-18 14:32               ` Berkant Koc
2026-05-18 14:46                 ` Bernd Schubert
2026-05-18 15:35                   ` Joanne Koong
2026-05-18 17:49                     ` Berkant Koc
2026-05-18 15:47                   ` Berkant Koc
2026-05-18  9:06         ` Pavel Begunkov
2026-05-18  9:50           ` Bernd Schubert
2026-05-18 10:32             ` Pavel Begunkov
2026-05-17 13:14     ` [PATCH 0/2] fuse: io-uring: fix two UAFs in dev_uring.c teardown Berkant Koc
2026-05-17 13:43       ` Bernd Schubert
2026-05-17 14:02         ` Berkant Koc

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.