Linux kernel -stable discussions
 help / color / mirror / Atom feed
* [PATCH v2 1/3] fuse: fix missing barrier when checking io-uring readiness
       [not found] <20260715174305.336261-1-joannelkoong@gmail.com>
@ 2026-07-15 17:43 ` Joanne Koong
  2026-07-15 22:26   ` Bernd Schubert
  2026-07-15 17:43 ` [PATCH v2 3/3] fuse: publish io-uring queues with release semantics Joanne Koong
  1 sibling, 1 reply; 6+ messages in thread
From: Joanne Koong @ 2026-07-15 17:43 UTC (permalink / raw)
  To: miklos, bernd; +Cc: fuse-devel, stable

fuse_block_alloc() reads fch->initialized and then fch->io_uring.
fch->io_uring is set before fch->initialized, ordered by the smp_wmb()
in fuse_chan_set_intialized(), but fuse_block_alloc() has no matching
read barrier between the two loads.

This may lead a CPU to observe fch->initialized=1 but fch->io_uring=0,
and skip the check that blocks request allocation until the io-uring
queues are ready. This can reintroduce the lock-order inversion deadlock
that commit 3393ff964e0f prevents.

Add an smp_rmb() barrier to pair with the smp_wmb() in
fuse_chan_set_initialized() to prevent this.

Fixes: 3393ff964e0f ("fuse: block request allocation until io-uring init is complete")
Cc: stable@vger.kernel.org
Signed-off-by: Joanne Koong <joannelkoong@gmail.com>
---
 fs/fuse/dev.c | 8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)

diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c
index 5763a7cd3b37..b70c536d7e25 100644
--- a/fs/fuse/dev.c
+++ b/fs/fuse/dev.c
@@ -85,7 +85,13 @@ void fuse_chan_set_initialized(struct fuse_chan *fch, struct fuse_chan_param *pa
 
 static bool fuse_block_alloc(struct fuse_chan *fch, bool for_background)
 {
-	return !fch->initialized || (for_background && fch->blocked) ||
+	if (!fch->initialized)
+		return true;
+
+	/* Pairs with smp_wmb() in fuse_chan_set_initialized() */
+	smp_rmb();
+
+	return (for_background && fch->blocked) ||
 	       (fch->io_uring && fch->connected && !fuse_uring_ready(fch));
 }
 
-- 
2.52.0


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

* [PATCH v2 3/3] fuse: publish io-uring queues with release semantics
       [not found] <20260715174305.336261-1-joannelkoong@gmail.com>
  2026-07-15 17:43 ` [PATCH v2 1/3] fuse: fix missing barrier when checking io-uring readiness Joanne Koong
@ 2026-07-15 17:43 ` Joanne Koong
  2026-07-15 22:54   ` Bernd Schubert
  1 sibling, 1 reply; 6+ messages in thread
From: Joanne Koong @ 2026-07-15 17:43 UTC (permalink / raw)
  To: miklos, bernd; +Cc: fuse-devel, stable

fuse_uring_create_queue() initializes a fuse_ring_queue and then
publishes the pointer into ring->queues[qid] with WRITE_ONCE() under the
fch->lock. There are several readers that may concurrently be fetching
that pointer locklessly and then deferencing it.

WRITE_ONCE() doesn't ensure ordering of the queue's field
initialization before the ring->queues[qid] pointer assignment. The
queue must be published with smp_store_release() so the field
initialization is guaranteed to happen before.

Readers in paths where the read may happen concurrently with the store
need to use READ_ONCE() because any race involving a plain access is
undefined.

Fixes: 24fe962c86f5 ("fuse: {io-uring} Handle SQEs - register commands")
Cc: stable@vger.kernel.org
Signed-off-by: Joanne Koong <joannelkoong@gmail.com>
---
 fs/fuse/dev_uring.c | 14 ++++++++------
 1 file changed, 8 insertions(+), 6 deletions(-)

diff --git a/fs/fuse/dev_uring.c b/fs/fuse/dev_uring.c
index 51f985154aa1..bf9d51f2a508 100644
--- a/fs/fuse/dev_uring.c
+++ b/fs/fuse/dev_uring.c
@@ -321,9 +321,11 @@ static struct fuse_ring_queue *fuse_uring_create_queue(struct fuse_ring *ring,
 	}
 
 	/*
-	 * write_once and lock as the caller mostly doesn't take the lock at all
+	 * fch->lock serializes concurrent creators for this qid.
+	 * smp_store_release() are for the lockless readers who must see a
+	 * fully initialized queue after &ring->queues[qid] is set
 	 */
-	WRITE_ONCE(ring->queues[qid], queue);
+	smp_store_release(&ring->queues[qid], queue);
 	spin_unlock(&fch->lock);
 
 	return queue;
@@ -434,7 +436,7 @@ static void fuse_uring_log_ent_state(struct fuse_ring *ring)
 	struct fuse_ring_ent *ent;
 
 	for (qid = 0; qid < ring->nr_queues; qid++) {
-		struct fuse_ring_queue *queue = ring->queues[qid];
+		struct fuse_ring_queue *queue = READ_ONCE(ring->queues[qid]);
 
 		if (!queue)
 			continue;
@@ -967,7 +969,7 @@ static int fuse_uring_commit_fetch(struct io_uring_cmd *cmd, int issue_flags,
 	if (qid >= ring->nr_queues)
 		return -EINVAL;
 
-	queue = ring->queues[qid];
+	queue = READ_ONCE(ring->queues[qid]);
 	if (!queue)
 		return err;
 	fpq = &queue->fpq;
@@ -1035,7 +1037,7 @@ static bool is_ring_ready(struct fuse_ring *ring, int current_qid)
 		if (current_qid == qid)
 			continue;
 
-		queue = ring->queues[qid];
+		queue = READ_ONCE(ring->queues[qid]);
 		if (!queue) {
 			ready = false;
 			break;
@@ -1191,7 +1193,7 @@ static int fuse_uring_register(struct io_uring_cmd *cmd,
 		return -EINVAL;
 	}
 
-	queue = ring->queues[qid];
+	queue = READ_ONCE(ring->queues[qid]);
 	if (!queue) {
 		queue = fuse_uring_create_queue(ring, qid);
 		if (!queue)
-- 
2.52.0


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

* Re: [PATCH v2 1/3] fuse: fix missing barrier when checking io-uring readiness
  2026-07-15 17:43 ` [PATCH v2 1/3] fuse: fix missing barrier when checking io-uring readiness Joanne Koong
@ 2026-07-15 22:26   ` Bernd Schubert
  2026-07-16 18:17     ` Joanne Koong
  0 siblings, 1 reply; 6+ messages in thread
From: Bernd Schubert @ 2026-07-15 22:26 UTC (permalink / raw)
  To: Joanne Koong, miklos; +Cc: fuse-devel, stable



On 7/15/26 19:43, Joanne Koong wrote:
> fuse_block_alloc() reads fch->initialized and then fch->io_uring.
> fch->io_uring is set before fch->initialized, ordered by the smp_wmb()
> in fuse_chan_set_intialized(), but fuse_block_alloc() has no matching
> read barrier between the two loads.
> 
> This may lead a CPU to observe fch->initialized=1 but fch->io_uring=0,
> and skip the check that blocks request allocation until the io-uring
> queues are ready. This can reintroduce the lock-order inversion deadlock
> that commit 3393ff964e0f prevents.
> 
> Add an smp_rmb() barrier to pair with the smp_wmb() in
> fuse_chan_set_initialized() to prevent this.
> 
> Fixes: 3393ff964e0f ("fuse: block request allocation until io-uring init is complete")
> Cc: stable@vger.kernel.org
> Signed-off-by: Joanne Koong <joannelkoong@gmail.com>
> ---
>  fs/fuse/dev.c | 8 +++++++-
>  1 file changed, 7 insertions(+), 1 deletion(-)
> 
> diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c
> index 5763a7cd3b37..b70c536d7e25 100644
> --- a/fs/fuse/dev.c
> +++ b/fs/fuse/dev.c
> @@ -85,7 +85,13 @@ void fuse_chan_set_initialized(struct fuse_chan *fch, struct fuse_chan_param *pa
>  
>  static bool fuse_block_alloc(struct fuse_chan *fch, bool for_background)
>  {
> -	return !fch->initialized || (for_background && fch->blocked) ||
> +	if (!fch->initialized)
> +		return true;
> +
> +	/* Pairs with smp_wmb() in fuse_chan_set_initialized() */
> +	smp_rmb();
> +
> +	return (for_background && fch->blocked) ||
>  	       (fch->io_uring && fch->connected && !fuse_uring_ready(fch));
>  }
>  

I wonder if we could remove smp_rmb() in fuse_get_req(), it follows
fuse_block_alloc().

Otherwise,

Reviewed-by: Bernd Schubert <bernd@bsbernd.com>



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

* Re: [PATCH v2 3/3] fuse: publish io-uring queues with release semantics
  2026-07-15 17:43 ` [PATCH v2 3/3] fuse: publish io-uring queues with release semantics Joanne Koong
@ 2026-07-15 22:54   ` Bernd Schubert
  2026-07-16 18:21     ` Joanne Koong
  0 siblings, 1 reply; 6+ messages in thread
From: Bernd Schubert @ 2026-07-15 22:54 UTC (permalink / raw)
  To: Joanne Koong, miklos; +Cc: fuse-devel, stable



On 7/15/26 19:43, Joanne Koong wrote:
> fuse_uring_create_queue() initializes a fuse_ring_queue and then
> publishes the pointer into ring->queues[qid] with WRITE_ONCE() under the
> fch->lock. There are several readers that may concurrently be fetching
> that pointer locklessly and then deferencing it.
> 
> WRITE_ONCE() doesn't ensure ordering of the queue's field
> initialization before the ring->queues[qid] pointer assignment. The
> queue must be published with smp_store_release() so the field
> initialization is guaranteed to happen before.
> 
> Readers in paths where the read may happen concurrently with the store
> need to use READ_ONCE() because any race involving a plain access is
> undefined.
> 
> Fixes: 24fe962c86f5 ("fuse: {io-uring} Handle SQEs - register commands")
> Cc: stable@vger.kernel.org
> Signed-off-by: Joanne Koong <joannelkoong@gmail.com>
> ---
>  fs/fuse/dev_uring.c | 14 ++++++++------
>  1 file changed, 8 insertions(+), 6 deletions(-)
> 
> diff --git a/fs/fuse/dev_uring.c b/fs/fuse/dev_uring.c
> index 51f985154aa1..bf9d51f2a508 100644
> --- a/fs/fuse/dev_uring.c
> +++ b/fs/fuse/dev_uring.c
> @@ -321,9 +321,11 @@ static struct fuse_ring_queue *fuse_uring_create_queue(struct fuse_ring *ring,
>  	}
>  
>  	/*
> -	 * write_once and lock as the caller mostly doesn't take the lock at all
> +	 * fch->lock serializes concurrent creators for this qid.
> +	 * smp_store_release() are for the lockless readers who must see a
> +	 * fully initialized queue after &ring->queues[qid] is set
>  	 */
> -	WRITE_ONCE(ring->queues[qid], queue);
> +	smp_store_release(&ring->queues[qid], queue);
>  	spin_unlock(&fch->lock);
>  
>  	return queue;
> @@ -434,7 +436,7 @@ static void fuse_uring_log_ent_state(struct fuse_ring *ring)
>  	struct fuse_ring_ent *ent;
>  
>  	for (qid = 0; qid < ring->nr_queues; qid++) {
> -		struct fuse_ring_queue *queue = ring->queues[qid];
> +		struct fuse_ring_queue *queue = READ_ONCE(ring->queues[qid]);
>  
>  		if (!queue)
>  			continue;
> @@ -967,7 +969,7 @@ static int fuse_uring_commit_fetch(struct io_uring_cmd *cmd, int issue_flags,
>  	if (qid >= ring->nr_queues)
>  		return -EINVAL;
>  
> -	queue = ring->queues[qid];
> +	queue = READ_ONCE(ring->queues[qid]);
>  	if (!queue)
>  		return err;
>  	fpq = &queue->fpq;
> @@ -1035,7 +1037,7 @@ static bool is_ring_ready(struct fuse_ring *ring, int current_qid)
>  		if (current_qid == qid)
>  			continue;
>  
> -		queue = ring->queues[qid];
> +		queue = READ_ONCE(ring->queues[qid]);
>  		if (!queue) {
>  			ready = false;
>  			break;
> @@ -1191,7 +1193,7 @@ static int fuse_uring_register(struct io_uring_cmd *cmd,
>  		return -EINVAL;
>  	}
>  
> -	queue = ring->queues[qid];
> +	queue = READ_ONCE(ring->queues[qid]);
>  	if (!queue) {
>  		queue = fuse_uring_create_queue(ring, qid);
>  		if (!queue)


For consistency, maybe READ_ONCE/WRITE_ONCE in fuse_uring_destruct?
Otherwise,

Reviewed-by: Bernd Schubert <bernd@bsbernd.com>

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

* Re: [PATCH v2 1/3] fuse: fix missing barrier when checking io-uring readiness
  2026-07-15 22:26   ` Bernd Schubert
@ 2026-07-16 18:17     ` Joanne Koong
  0 siblings, 0 replies; 6+ messages in thread
From: Joanne Koong @ 2026-07-16 18:17 UTC (permalink / raw)
  To: Bernd Schubert; +Cc: miklos, fuse-devel, stable

On Wed, Jul 15, 2026 at 3:26 PM Bernd Schubert <bernd@bsbernd.com> wrote:
>
> On 7/15/26 19:43, Joanne Koong wrote:
> > fuse_block_alloc() reads fch->initialized and then fch->io_uring.
> > fch->io_uring is set before fch->initialized, ordered by the smp_wmb()
> > in fuse_chan_set_intialized(), but fuse_block_alloc() has no matching
> > read barrier between the two loads.
> >
> > This may lead a CPU to observe fch->initialized=1 but fch->io_uring=0,
> > and skip the check that blocks request allocation until the io-uring
> > queues are ready. This can reintroduce the lock-order inversion deadlock
> > that commit 3393ff964e0f prevents.
> >
> > Add an smp_rmb() barrier to pair with the smp_wmb() in
> > fuse_chan_set_initialized() to prevent this.
> >
> > Fixes: 3393ff964e0f ("fuse: block request allocation until io-uring init is complete")
> > Cc: stable@vger.kernel.org
> > Signed-off-by: Joanne Koong <joannelkoong@gmail.com>
> > ---
> >  fs/fuse/dev.c | 8 +++++++-
> >  1 file changed, 7 insertions(+), 1 deletion(-)
> >
> > diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c
> > index 5763a7cd3b37..b70c536d7e25 100644
> > --- a/fs/fuse/dev.c
> > +++ b/fs/fuse/dev.c
> > @@ -85,7 +85,13 @@ void fuse_chan_set_initialized(struct fuse_chan *fch, struct fuse_chan_param *pa
> >
> >  static bool fuse_block_alloc(struct fuse_chan *fch, bool for_background)
> >  {
> > -     return !fch->initialized || (for_background && fch->blocked) ||
> > +     if (!fch->initialized)
> > +             return true;
> > +
> > +     /* Pairs with smp_wmb() in fuse_chan_set_initialized() */
> > +     smp_rmb();
> > +
> > +     return (for_background && fch->blocked) ||
> >              (fch->io_uring && fch->connected && !fuse_uring_ready(fch));
> >  }
> >
>
> I wonder if we could remove smp_rmb() in fuse_get_req(), it follows
> fuse_block_alloc().

Nicely spotted. This is removed in the 2nd patch ("fuse: use
release/acquire for fch->initialized") but I can move deleting that
line into this patch.

Thanks,
Joanne

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

* Re: [PATCH v2 3/3] fuse: publish io-uring queues with release semantics
  2026-07-15 22:54   ` Bernd Schubert
@ 2026-07-16 18:21     ` Joanne Koong
  0 siblings, 0 replies; 6+ messages in thread
From: Joanne Koong @ 2026-07-16 18:21 UTC (permalink / raw)
  To: Bernd Schubert; +Cc: miklos, fuse-devel, stable

On Wed, Jul 15, 2026 at 3:54 PM Bernd Schubert <bernd@bsbernd.com> wrote:
>
>
>
> On 7/15/26 19:43, Joanne Koong wrote:
> > fuse_uring_create_queue() initializes a fuse_ring_queue and then
> > publishes the pointer into ring->queues[qid] with WRITE_ONCE() under the
> > fch->lock. There are several readers that may concurrently be fetching
> > that pointer locklessly and then deferencing it.
> >
> > WRITE_ONCE() doesn't ensure ordering of the queue's field
> > initialization before the ring->queues[qid] pointer assignment. The
> > queue must be published with smp_store_release() so the field
> > initialization is guaranteed to happen before.
> >
> > Readers in paths where the read may happen concurrently with the store
> > need to use READ_ONCE() because any race involving a plain access is
> > undefined.
> >
> > Fixes: 24fe962c86f5 ("fuse: {io-uring} Handle SQEs - register commands")
> > Cc: stable@vger.kernel.org
> > Signed-off-by: Joanne Koong <joannelkoong@gmail.com>
> > ---
> >  fs/fuse/dev_uring.c | 14 ++++++++------
> >  1 file changed, 8 insertions(+), 6 deletions(-)
> >
> > diff --git a/fs/fuse/dev_uring.c b/fs/fuse/dev_uring.c
> > index 51f985154aa1..bf9d51f2a508 100644
> > --- a/fs/fuse/dev_uring.c
> > +++ b/fs/fuse/dev_uring.c
> > @@ -321,9 +321,11 @@ static struct fuse_ring_queue *fuse_uring_create_queue(struct fuse_ring *ring,
> >       }
> >
> >       /*
> > -      * write_once and lock as the caller mostly doesn't take the lock at all
> > +      * fch->lock serializes concurrent creators for this qid.
> > +      * smp_store_release() are for the lockless readers who must see a
> > +      * fully initialized queue after &ring->queues[qid] is set
> >        */
> > -     WRITE_ONCE(ring->queues[qid], queue);
> > +     smp_store_release(&ring->queues[qid], queue);
> >       spin_unlock(&fch->lock);
> >
> >       return queue;
> > @@ -434,7 +436,7 @@ static void fuse_uring_log_ent_state(struct fuse_ring *ring)
> >       struct fuse_ring_ent *ent;
> >
> >       for (qid = 0; qid < ring->nr_queues; qid++) {
> > -             struct fuse_ring_queue *queue = ring->queues[qid];
> > +             struct fuse_ring_queue *queue = READ_ONCE(ring->queues[qid]);
> >
> >               if (!queue)
> >                       continue;
> > @@ -967,7 +969,7 @@ static int fuse_uring_commit_fetch(struct io_uring_cmd *cmd, int issue_flags,
> >       if (qid >= ring->nr_queues)
> >               return -EINVAL;
> >
> > -     queue = ring->queues[qid];
> > +     queue = READ_ONCE(ring->queues[qid]);
> >       if (!queue)
> >               return err;
> >       fpq = &queue->fpq;
> > @@ -1035,7 +1037,7 @@ static bool is_ring_ready(struct fuse_ring *ring, int current_qid)
> >               if (current_qid == qid)
> >                       continue;
> >
> > -             queue = ring->queues[qid];
> > +             queue = READ_ONCE(ring->queues[qid]);
> >               if (!queue) {
> >                       ready = false;
> >                       break;
> > @@ -1191,7 +1193,7 @@ static int fuse_uring_register(struct io_uring_cmd *cmd,
> >               return -EINVAL;
> >       }
> >
> > -     queue = ring->queues[qid];
> > +     queue = READ_ONCE(ring->queues[qid]);
> >       if (!queue) {
> >               queue = fuse_uring_create_queue(ring, qid);
> >               if (!queue)
>
>
> For consistency, maybe READ_ONCE/WRITE_ONCE in fuse_uring_destruct?

I didn't add it because I don't think it's necessary (because
fuse_uring_destruct can only run after the queue has been created),
but I can add it to make things more uniform, if you have a preference
for one way or the other. I'll send out v3 with this change.

Thanks,
Joanne

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

end of thread, other threads:[~2026-07-16 18:21 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
     [not found] <20260715174305.336261-1-joannelkoong@gmail.com>
2026-07-15 17:43 ` [PATCH v2 1/3] fuse: fix missing barrier when checking io-uring readiness Joanne Koong
2026-07-15 22:26   ` Bernd Schubert
2026-07-16 18:17     ` Joanne Koong
2026-07-15 17:43 ` [PATCH v2 3/3] fuse: publish io-uring queues with release semantics Joanne Koong
2026-07-15 22:54   ` Bernd Schubert
2026-07-16 18:21     ` Joanne Koong

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox