* [PATCH v2] fs/fuse: fix potential memory leak from fuse_uring_cancel
@ 2025-09-11 7:33 Jian Huang Li
2025-09-11 7:37 ` Jian Huang Li
2025-09-15 18:15 ` Joanne Koong
0 siblings, 2 replies; 10+ messages in thread
From: Jian Huang Li @ 2025-09-11 7:33 UTC (permalink / raw)
To: linux-fsdevel; +Cc: miklos, bschubert
This issue could be observed sometimes during libfuse xfstests, from
dmseg prints some like "kernel: WARNING: CPU: 4 PID: 0 at
fs/fuse/dev_uring.c:204 fuse_uring_destruct+0x1f5/0x200 [fuse]".
The cause is, if when fuse daemon just submitted
FUSE_IO_URING_CMD_REGISTER SQEs, then umount or fuse daemon quits at
this very early stage. After all uring queues stopped, might have one or
more unprocessed FUSE_IO_URING_CMD_REGISTER SQEs get processed then some
new ring entities are created and added to ent_avail_queue, and
immediately fuse_uring_cancel moves them to ent_in_userspace after SQEs
get canceled. These ring entities will not be moved to ent_released, and
will stay in ent_in_userspace when fuse_uring_destruct is called, needed
be freed by the function.
Fixes: b6236c8407cb ("fuse: {io-uring} Prevent mount point hang on
fuse-server termination")
Signed-off-by: Jian Huang Li <ali@ddn.com>
---
fs/fuse/dev_uring.c | 15 ++++++++++++++-
1 file changed, 14 insertions(+), 1 deletion(-)
diff --git a/fs/fuse/dev_uring.c b/fs/fuse/dev_uring.c
index 249b210becb1..eed0fc6c8b05 100644
--- a/fs/fuse/dev_uring.c
+++ b/fs/fuse/dev_uring.c
@@ -201,7 +201,20 @@ void fuse_uring_destruct(struct fuse_conn *fc)
WARN_ON(!list_empty(&queue->ent_avail_queue));
WARN_ON(!list_empty(&queue->ent_w_req_queue));
WARN_ON(!list_empty(&queue->ent_commit_queue));
- WARN_ON(!list_empty(&queue->ent_in_userspace));
+
+ /*
+ * ent_in_userspace might not be empty, because
+ * FUSE_IO_URING_CMD_REGISTER is not accounted yet
+ * in ring->queue_refs and fuse_uring_wait_stopped_queues()
+ * then passes too early. fuse_uring_cancel() adds these
+ * commands to queue->ent_in_userspace - they need
+ * to be freed here
+ */
+ list_for_each_entry_safe(ent, next, &queue->ent_in_userspace,
+ list) {
+ list_del_init(&ent->list);
+ kfree(ent);
+ }
list_for_each_entry_safe(ent, next, &queue->ent_released,
list) {
--
2.47.1
^ permalink raw reply related [flat|nested] 10+ messages in thread* Re: [PATCH v2] fs/fuse: fix potential memory leak from fuse_uring_cancel 2025-09-11 7:33 [PATCH v2] fs/fuse: fix potential memory leak from fuse_uring_cancel Jian Huang Li @ 2025-09-11 7:37 ` Jian Huang Li 2025-09-15 18:15 ` Joanne Koong 1 sibling, 0 replies; 10+ messages in thread From: Jian Huang Li @ 2025-09-11 7:37 UTC (permalink / raw) To: linux-fsdevel; +Cc: miklos, bschubert Changelog --------- v1: https://lore.kernel.org/all/4a599306-5ef1-4531-b733-4984d09b97a1@ddn.com/ v1 -> v2: * Instead of introducing a new list, keep to use ent_in_userspace to handle not-in-using entities when SQEs get canceled, and iterate/free on the ent_in_userspace list in fuse_uring_destruct. ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v2] fs/fuse: fix potential memory leak from fuse_uring_cancel 2025-09-11 7:33 [PATCH v2] fs/fuse: fix potential memory leak from fuse_uring_cancel Jian Huang Li 2025-09-11 7:37 ` Jian Huang Li @ 2025-09-15 18:15 ` Joanne Koong 2025-09-15 20:15 ` Bernd Schubert 1 sibling, 1 reply; 10+ messages in thread From: Joanne Koong @ 2025-09-15 18:15 UTC (permalink / raw) To: Jian Huang Li; +Cc: linux-fsdevel, miklos, bschubert On Thu, Sep 11, 2025 at 3:34 AM Jian Huang Li <ali@ddn.com> wrote: > > This issue could be observed sometimes during libfuse xfstests, from > dmseg prints some like "kernel: WARNING: CPU: 4 PID: 0 at > fs/fuse/dev_uring.c:204 fuse_uring_destruct+0x1f5/0x200 [fuse]". > > The cause is, if when fuse daemon just submitted > FUSE_IO_URING_CMD_REGISTER SQEs, then umount or fuse daemon quits at > this very early stage. After all uring queues stopped, might have one or > more unprocessed FUSE_IO_URING_CMD_REGISTER SQEs get processed then some > new ring entities are created and added to ent_avail_queue, and > immediately fuse_uring_cancel moves them to ent_in_userspace after SQEs > get canceled. These ring entities will not be moved to ent_released, and > will stay in ent_in_userspace when fuse_uring_destruct is called, needed > be freed by the function. Hi Jian, Does it suffice to fix this race by tearing down the entries from the available queue first before tearing down the entries in the userspace queue? eg something like static void fuse_uring_teardown_entries(struct fuse_ring_queue *queue) { - fuse_uring_stop_list_entries(&queue->ent_in_userspace, queue, - FRRS_USERSPACE); fuse_uring_stop_list_entries(&queue->ent_avail_queue, queue, FRRS_AVAILABLE); + fuse_uring_stop_list_entries(&queue->ent_in_userspace, queue, + FRRS_USERSPACE); } AFAICT, the race happens right now because when fuse_uring_cancel() moves the FRRS_AVAILABLE entries on the ent_avail_queue to the ent_in_userspace queue, fuse_uring_teardown_entries() may have already called fuse_uring_stop_list_entries() on the ent_in_userspace queue, thereby now missing the just-moved entries altogether, eg this logical flow -> fuse_uring_stop_list_entries(&queue->ent_in_userspace, ...); -> fuse_uring_cancel() moves entry from avail q to userspace q -> fuse_uring_stop_list_entries(&queue->ent_avail_queue, ...); If instead fuse_uring_teardown_entries() stops the available queue first, then -> fuse_uring_stop_list_entries(&queue->ent_avail_queue, ...); -> fuse_uring_cancel() -> fuse_uring_stop_list_entries(&queue->ent_in_userspace, ...); seems fine now and fuse_uring_cancel() would basically be a no-op since ent->state is now FRRS_TEARDOWN. Thanks, Joanne > > Fixes: b6236c8407cb ("fuse: {io-uring} Prevent mount point hang on > fuse-server termination") > Signed-off-by: Jian Huang Li <ali@ddn.com> > --- > fs/fuse/dev_uring.c | 15 ++++++++++++++- > 1 file changed, 14 insertions(+), 1 deletion(-) > > diff --git a/fs/fuse/dev_uring.c b/fs/fuse/dev_uring.c > index 249b210becb1..eed0fc6c8b05 100644 > --- a/fs/fuse/dev_uring.c > +++ b/fs/fuse/dev_uring.c > @@ -201,7 +201,20 @@ void fuse_uring_destruct(struct fuse_conn *fc) > WARN_ON(!list_empty(&queue->ent_avail_queue)); > WARN_ON(!list_empty(&queue->ent_w_req_queue)); > WARN_ON(!list_empty(&queue->ent_commit_queue)); > - WARN_ON(!list_empty(&queue->ent_in_userspace)); > + > + /* > + * ent_in_userspace might not be empty, because > + * FUSE_IO_URING_CMD_REGISTER is not accounted yet > + * in ring->queue_refs and fuse_uring_wait_stopped_queues() > + * then passes too early. fuse_uring_cancel() adds these > + * commands to queue->ent_in_userspace - they need > + * to be freed here > + */ > + list_for_each_entry_safe(ent, next, &queue->ent_in_userspace, > + list) { > + list_del_init(&ent->list); > + kfree(ent); > + } > > list_for_each_entry_safe(ent, next, &queue->ent_released, > list) { > -- > 2.47.1 > ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v2] fs/fuse: fix potential memory leak from fuse_uring_cancel 2025-09-15 18:15 ` Joanne Koong @ 2025-09-15 20:15 ` Bernd Schubert 2025-09-15 21:23 ` Joanne Koong 0 siblings, 1 reply; 10+ messages in thread From: Bernd Schubert @ 2025-09-15 20:15 UTC (permalink / raw) To: Joanne Koong, Jian Huang Li; +Cc: linux-fsdevel, miklos Hi Joanne, thanks for looking into this. On 9/15/25 20:15, Joanne Koong wrote: > On Thu, Sep 11, 2025 at 3:34 AM Jian Huang Li <ali@ddn.com> wrote: >> >> This issue could be observed sometimes during libfuse xfstests, from >> dmseg prints some like "kernel: WARNING: CPU: 4 PID: 0 at >> fs/fuse/dev_uring.c:204 fuse_uring_destruct+0x1f5/0x200 [fuse]". >> >> The cause is, if when fuse daemon just submitted >> FUSE_IO_URING_CMD_REGISTER SQEs, then umount or fuse daemon quits at >> this very early stage. After all uring queues stopped, might have one or >> more unprocessed FUSE_IO_URING_CMD_REGISTER SQEs get processed then some >> new ring entities are created and added to ent_avail_queue, and >> immediately fuse_uring_cancel moves them to ent_in_userspace after SQEs >> get canceled. These ring entities will not be moved to ent_released, and >> will stay in ent_in_userspace when fuse_uring_destruct is called, needed >> be freed by the function. > > Hi Jian, > > Does it suffice to fix this race by tearing down the entries from the > available queue first before tearing down the entries in the userspace > queue? eg something like > > static void fuse_uring_teardown_entries(struct fuse_ring_queue *queue) > { > - fuse_uring_stop_list_entries(&queue->ent_in_userspace, queue, > - FRRS_USERSPACE); > fuse_uring_stop_list_entries(&queue->ent_avail_queue, queue, > FRRS_AVAILABLE); > + fuse_uring_stop_list_entries(&queue->ent_in_userspace, queue, > + FRRS_USERSPACE); > } > > AFAICT, the race happens right now because when fuse_uring_cancel() > moves the FRRS_AVAILABLE entries on the ent_avail_queue to the > ent_in_userspace queue, fuse_uring_teardown_entries() may have already > called fuse_uring_stop_list_entries() on the ent_in_userspace queue, > thereby now missing the just-moved entries altogether, eg this logical > flow > > -> fuse_uring_stop_list_entries(&queue->ent_in_userspace, ...); > -> fuse_uring_cancel() moves entry from avail q to userspace q > -> fuse_uring_stop_list_entries(&queue->ent_avail_queue, ...); > > If instead fuse_uring_teardown_entries() stops the available queue first, then > -> fuse_uring_stop_list_entries(&queue->ent_avail_queue, ...); > -> fuse_uring_cancel() > -> fuse_uring_stop_list_entries(&queue->ent_in_userspace, ...); > > seems fine now and fuse_uring_cancel() would basically be a no-op > since ent->state is now FRRS_TEARDOWN. > I'm not sure. Let's say we have task 1 task2 fuse_uring_cmd() fuse_uring_register() [slowness here] fuse_abort_conn() fuse_uring_teardown_entries() [slowness continue] fuse_uring_do_register() fuse_uring_prepare_cancel() fuse_uring_ent_avail() I.e. fuse_uring_teardown_entries() might be called before the command gets marked cancel-able and before it is moved to the avail queue. I think we should extend the patch and actually not set the ring to ready when fc->connected is set to 0. Thanks, Bernd ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v2] fs/fuse: fix potential memory leak from fuse_uring_cancel 2025-09-15 20:15 ` Bernd Schubert @ 2025-09-15 21:23 ` Joanne Koong 2025-09-15 21:46 ` Bernd Schubert 0 siblings, 1 reply; 10+ messages in thread From: Joanne Koong @ 2025-09-15 21:23 UTC (permalink / raw) To: Bernd Schubert; +Cc: Jian Huang Li, linux-fsdevel, miklos On Mon, Sep 15, 2025 at 1:15 PM Bernd Schubert <bernd@bsbernd.com> wrote: > > Hi Joanne, > > thanks for looking into this. > > On 9/15/25 20:15, Joanne Koong wrote: > > On Thu, Sep 11, 2025 at 3:34 AM Jian Huang Li <ali@ddn.com> wrote: > >> > >> This issue could be observed sometimes during libfuse xfstests, from > >> dmseg prints some like "kernel: WARNING: CPU: 4 PID: 0 at > >> fs/fuse/dev_uring.c:204 fuse_uring_destruct+0x1f5/0x200 [fuse]". > >> > >> The cause is, if when fuse daemon just submitted > >> FUSE_IO_URING_CMD_REGISTER SQEs, then umount or fuse daemon quits at > >> this very early stage. After all uring queues stopped, might have one or > >> more unprocessed FUSE_IO_URING_CMD_REGISTER SQEs get processed then some > >> new ring entities are created and added to ent_avail_queue, and > >> immediately fuse_uring_cancel moves them to ent_in_userspace after SQEs > >> get canceled. These ring entities will not be moved to ent_released, and > >> will stay in ent_in_userspace when fuse_uring_destruct is called, needed > >> be freed by the function. > > > > Hi Jian, > > > > Does it suffice to fix this race by tearing down the entries from the > > available queue first before tearing down the entries in the userspace > > queue? eg something like > > > > static void fuse_uring_teardown_entries(struct fuse_ring_queue *queue) > > { > > - fuse_uring_stop_list_entries(&queue->ent_in_userspace, queue, > > - FRRS_USERSPACE); > > fuse_uring_stop_list_entries(&queue->ent_avail_queue, queue, > > FRRS_AVAILABLE); > > + fuse_uring_stop_list_entries(&queue->ent_in_userspace, queue, > > + FRRS_USERSPACE); > > } > > > > AFAICT, the race happens right now because when fuse_uring_cancel() > > moves the FRRS_AVAILABLE entries on the ent_avail_queue to the > > ent_in_userspace queue, fuse_uring_teardown_entries() may have already > > called fuse_uring_stop_list_entries() on the ent_in_userspace queue, > > thereby now missing the just-moved entries altogether, eg this logical > > flow > > > > -> fuse_uring_stop_list_entries(&queue->ent_in_userspace, ...); > > -> fuse_uring_cancel() moves entry from avail q to userspace q > > -> fuse_uring_stop_list_entries(&queue->ent_avail_queue, ...); > > > > If instead fuse_uring_teardown_entries() stops the available queue first, then > > -> fuse_uring_stop_list_entries(&queue->ent_avail_queue, ...); > > -> fuse_uring_cancel() > > -> fuse_uring_stop_list_entries(&queue->ent_in_userspace, ...); > > > > seems fine now and fuse_uring_cancel() would basically be a no-op > > since ent->state is now FRRS_TEARDOWN. > > > > I'm not sure. Let's say we have > > task 1 task2 > fuse_uring_cmd() > fuse_uring_register() > [slowness here] > fuse_abort_conn() > fuse_uring_teardown_entries() > [slowness continue] > fuse_uring_do_register() > fuse_uring_prepare_cancel() > fuse_uring_ent_avail() > > > I.e. fuse_uring_teardown_entries() might be called before > the command gets marked cancel-able and before it is > moved to the avail queue. I think we should extend the patch > and actually not set the ring to ready when fc->connected > is set to 0. > Hi Bernd, I think this is a separate race from the fuse_uring_cancel one. afaics, this race can happen even if the user doesn't call fuse_uring_cancel(). imo I think the cleanest solution to this registration vs teardown race is to check queue->stopped in fuse_uring_do_register() after we grab the queue spinlock, and if queue->stopped is true, then just clean up the entry ourselves with fuse_uring_entry_teardown()). Thanks, Joanne > > Thanks, > Bernd ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v2] fs/fuse: fix potential memory leak from fuse_uring_cancel 2025-09-15 21:23 ` Joanne Koong @ 2025-09-15 21:46 ` Bernd Schubert 2025-09-15 21:57 ` Bernd Schubert 0 siblings, 1 reply; 10+ messages in thread From: Bernd Schubert @ 2025-09-15 21:46 UTC (permalink / raw) To: Joanne Koong; +Cc: Jian Huang Li, linux-fsdevel, miklos On 9/15/25 23:23, Joanne Koong wrote: > On Mon, Sep 15, 2025 at 1:15 PM Bernd Schubert <bernd@bsbernd.com> wrote: >> >> Hi Joanne, >> >> thanks for looking into this. >> >> On 9/15/25 20:15, Joanne Koong wrote: >>> On Thu, Sep 11, 2025 at 3:34 AM Jian Huang Li <ali@ddn.com> wrote: >>>> >>>> This issue could be observed sometimes during libfuse xfstests, from >>>> dmseg prints some like "kernel: WARNING: CPU: 4 PID: 0 at >>>> fs/fuse/dev_uring.c:204 fuse_uring_destruct+0x1f5/0x200 [fuse]". >>>> >>>> The cause is, if when fuse daemon just submitted >>>> FUSE_IO_URING_CMD_REGISTER SQEs, then umount or fuse daemon quits at >>>> this very early stage. After all uring queues stopped, might have one or >>>> more unprocessed FUSE_IO_URING_CMD_REGISTER SQEs get processed then some >>>> new ring entities are created and added to ent_avail_queue, and >>>> immediately fuse_uring_cancel moves them to ent_in_userspace after SQEs >>>> get canceled. These ring entities will not be moved to ent_released, and >>>> will stay in ent_in_userspace when fuse_uring_destruct is called, needed >>>> be freed by the function. >>> >>> Hi Jian, >>> >>> Does it suffice to fix this race by tearing down the entries from the >>> available queue first before tearing down the entries in the userspace >>> queue? eg something like >>> >>> static void fuse_uring_teardown_entries(struct fuse_ring_queue *queue) >>> { >>> - fuse_uring_stop_list_entries(&queue->ent_in_userspace, queue, >>> - FRRS_USERSPACE); >>> fuse_uring_stop_list_entries(&queue->ent_avail_queue, queue, >>> FRRS_AVAILABLE); >>> + fuse_uring_stop_list_entries(&queue->ent_in_userspace, queue, >>> + FRRS_USERSPACE); >>> } >>> >>> AFAICT, the race happens right now because when fuse_uring_cancel() >>> moves the FRRS_AVAILABLE entries on the ent_avail_queue to the >>> ent_in_userspace queue, fuse_uring_teardown_entries() may have already >>> called fuse_uring_stop_list_entries() on the ent_in_userspace queue, >>> thereby now missing the just-moved entries altogether, eg this logical >>> flow >>> >>> -> fuse_uring_stop_list_entries(&queue->ent_in_userspace, ...); >>> -> fuse_uring_cancel() moves entry from avail q to userspace q >>> -> fuse_uring_stop_list_entries(&queue->ent_avail_queue, ...); >>> >>> If instead fuse_uring_teardown_entries() stops the available queue first, then >>> -> fuse_uring_stop_list_entries(&queue->ent_avail_queue, ...); >>> -> fuse_uring_cancel() >>> -> fuse_uring_stop_list_entries(&queue->ent_in_userspace, ...); >>> >>> seems fine now and fuse_uring_cancel() would basically be a no-op >>> since ent->state is now FRRS_TEARDOWN. >>> >> >> I'm not sure. Let's say we have >> >> task 1 task2 >> fuse_uring_cmd() >> fuse_uring_register() >> [slowness here] >> fuse_abort_conn() >> fuse_uring_teardown_entries() >> [slowness continue] >> fuse_uring_do_register() >> fuse_uring_prepare_cancel() >> fuse_uring_ent_avail() >> >> >> I.e. fuse_uring_teardown_entries() might be called before >> the command gets marked cancel-able and before it is >> moved to the avail queue. I think we should extend the patch >> and actually not set the ring to ready when fc->connected >> is set to 0. >> > > Hi Bernd, > > I think this is a separate race from the fuse_uring_cancel one. > afaics, this race can happen even if the user doesn't call > fuse_uring_cancel(). imo I think the cleanest solution to this > registration vs teardown race is to check queue->stopped in > fuse_uring_do_register() after we grab the queue spinlock, and if > queue->stopped is true, then just clean up the entry ourselves with > fuse_uring_entry_teardown()). What speaks against just doing as in the existing patch and freeing the ent_in_userspace entries fuse_uring_destruct()? IMO it covers both races, missing is just to avoid setting the ring as ready. Thanks, Bernd ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v2] fs/fuse: fix potential memory leak from fuse_uring_cancel 2025-09-15 21:46 ` Bernd Schubert @ 2025-09-15 21:57 ` Bernd Schubert 2025-09-15 23:04 ` Joanne Koong 0 siblings, 1 reply; 10+ messages in thread From: Bernd Schubert @ 2025-09-15 21:57 UTC (permalink / raw) To: Joanne Koong; +Cc: Jian Huang Li, linux-fsdevel, miklos On 9/15/25 23:46, Bernd Schubert wrote: > > > On 9/15/25 23:23, Joanne Koong wrote: >> On Mon, Sep 15, 2025 at 1:15 PM Bernd Schubert <bernd@bsbernd.com> wrote: >>> >>> Hi Joanne, >>> >>> thanks for looking into this. >>> >>> On 9/15/25 20:15, Joanne Koong wrote: >>>> On Thu, Sep 11, 2025 at 3:34 AM Jian Huang Li <ali@ddn.com> wrote: >>>>> >>>>> This issue could be observed sometimes during libfuse xfstests, from >>>>> dmseg prints some like "kernel: WARNING: CPU: 4 PID: 0 at >>>>> fs/fuse/dev_uring.c:204 fuse_uring_destruct+0x1f5/0x200 [fuse]". >>>>> >>>>> The cause is, if when fuse daemon just submitted >>>>> FUSE_IO_URING_CMD_REGISTER SQEs, then umount or fuse daemon quits at >>>>> this very early stage. After all uring queues stopped, might have one or >>>>> more unprocessed FUSE_IO_URING_CMD_REGISTER SQEs get processed then some >>>>> new ring entities are created and added to ent_avail_queue, and >>>>> immediately fuse_uring_cancel moves them to ent_in_userspace after SQEs >>>>> get canceled. These ring entities will not be moved to ent_released, and >>>>> will stay in ent_in_userspace when fuse_uring_destruct is called, needed >>>>> be freed by the function. >>>> >>>> Hi Jian, >>>> >>>> Does it suffice to fix this race by tearing down the entries from the >>>> available queue first before tearing down the entries in the userspace >>>> queue? eg something like >>>> >>>> static void fuse_uring_teardown_entries(struct fuse_ring_queue *queue) >>>> { >>>> - fuse_uring_stop_list_entries(&queue->ent_in_userspace, queue, >>>> - FRRS_USERSPACE); >>>> fuse_uring_stop_list_entries(&queue->ent_avail_queue, queue, >>>> FRRS_AVAILABLE); >>>> + fuse_uring_stop_list_entries(&queue->ent_in_userspace, queue, >>>> + FRRS_USERSPACE); >>>> } >>>> >>>> AFAICT, the race happens right now because when fuse_uring_cancel() >>>> moves the FRRS_AVAILABLE entries on the ent_avail_queue to the >>>> ent_in_userspace queue, fuse_uring_teardown_entries() may have already >>>> called fuse_uring_stop_list_entries() on the ent_in_userspace queue, >>>> thereby now missing the just-moved entries altogether, eg this logical >>>> flow >>>> >>>> -> fuse_uring_stop_list_entries(&queue->ent_in_userspace, ...); >>>> -> fuse_uring_cancel() moves entry from avail q to userspace q >>>> -> fuse_uring_stop_list_entries(&queue->ent_avail_queue, ...); >>>> >>>> If instead fuse_uring_teardown_entries() stops the available queue first, then >>>> -> fuse_uring_stop_list_entries(&queue->ent_avail_queue, ...); >>>> -> fuse_uring_cancel() >>>> -> fuse_uring_stop_list_entries(&queue->ent_in_userspace, ...); >>>> >>>> seems fine now and fuse_uring_cancel() would basically be a no-op >>>> since ent->state is now FRRS_TEARDOWN. >>>> >>> >>> I'm not sure. Let's say we have >>> >>> task 1 task2 >>> fuse_uring_cmd() >>> fuse_uring_register() >>> [slowness here] >>> fuse_abort_conn() >>> fuse_uring_teardown_entries() >>> [slowness continue] >>> fuse_uring_do_register() >>> fuse_uring_prepare_cancel() >>> fuse_uring_ent_avail() >>> >>> >>> I.e. fuse_uring_teardown_entries() might be called before >>> the command gets marked cancel-able and before it is >>> moved to the avail queue. I think we should extend the patch >>> and actually not set the ring to ready when fc->connected >>> is set to 0. >>> >> >> Hi Bernd, >> >> I think this is a separate race from the fuse_uring_cancel one. >> afaics, this race can happen even if the user doesn't call >> fuse_uring_cancel(). imo I think the cleanest solution to this >> registration vs teardown race is to check queue->stopped in >> fuse_uring_do_register() after we grab the queue spinlock, and if >> queue->stopped is true, then just clean up the entry ourselves with >> fuse_uring_entry_teardown()). > > What speaks against just doing as in the existing patch and freeing > the ent_in_userspace entries fuse_uring_destruct()? > IMO it covers both races, missing is just to avoid setting the ring > as ready. Well, maybe cleaner, I don't have a strong opinion. We could skip the comment and explanation with your approach. Thanks, Bernd ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v2] fs/fuse: fix potential memory leak from fuse_uring_cancel 2025-09-15 21:57 ` Bernd Schubert @ 2025-09-15 23:04 ` Joanne Koong 2025-09-16 9:17 ` Bernd Schubert 0 siblings, 1 reply; 10+ messages in thread From: Joanne Koong @ 2025-09-15 23:04 UTC (permalink / raw) To: Bernd Schubert; +Cc: Jian Huang Li, linux-fsdevel, miklos On Mon, Sep 15, 2025 at 2:57 PM Bernd Schubert <bernd@bsbernd.com> wrote: > On 9/15/25 23:46, Bernd Schubert wrote: > > On 9/15/25 23:23, Joanne Koong wrote: > >> On Mon, Sep 15, 2025 at 1:15 PM Bernd Schubert <bernd@bsbernd.com> wrote: > >>> > >>> Hi Joanne, > >>> > >>> thanks for looking into this. > >>> > >>> On 9/15/25 20:15, Joanne Koong wrote: > >>>> On Thu, Sep 11, 2025 at 3:34 AM Jian Huang Li <ali@ddn.com> wrote: > >>>>> > >>>>> This issue could be observed sometimes during libfuse xfstests, from > >>>>> dmseg prints some like "kernel: WARNING: CPU: 4 PID: 0 at > >>>>> fs/fuse/dev_uring.c:204 fuse_uring_destruct+0x1f5/0x200 [fuse]". > >>>>> > >>>>> The cause is, if when fuse daemon just submitted > >>>>> FUSE_IO_URING_CMD_REGISTER SQEs, then umount or fuse daemon quits at > >>>>> this very early stage. After all uring queues stopped, might have one or > >>>>> more unprocessed FUSE_IO_URING_CMD_REGISTER SQEs get processed then some > >>>>> new ring entities are created and added to ent_avail_queue, and > >>>>> immediately fuse_uring_cancel moves them to ent_in_userspace after SQEs > >>>>> get canceled. These ring entities will not be moved to ent_released, and > >>>>> will stay in ent_in_userspace when fuse_uring_destruct is called, needed > >>>>> be freed by the function. > >>>> > >>>> Hi Jian, > >>>> > >>>> Does it suffice to fix this race by tearing down the entries from the > >>>> available queue first before tearing down the entries in the userspace > >>>> queue? eg something like > >>>> > >>>> static void fuse_uring_teardown_entries(struct fuse_ring_queue *queue) > >>>> { > >>>> - fuse_uring_stop_list_entries(&queue->ent_in_userspace, queue, > >>>> - FRRS_USERSPACE); > >>>> fuse_uring_stop_list_entries(&queue->ent_avail_queue, queue, > >>>> FRRS_AVAILABLE); > >>>> + fuse_uring_stop_list_entries(&queue->ent_in_userspace, queue, > >>>> + FRRS_USERSPACE); > >>>> } > >>>> > >>>> AFAICT, the race happens right now because when fuse_uring_cancel() > >>>> moves the FRRS_AVAILABLE entries on the ent_avail_queue to the > >>>> ent_in_userspace queue, fuse_uring_teardown_entries() may have already > >>>> called fuse_uring_stop_list_entries() on the ent_in_userspace queue, > >>>> thereby now missing the just-moved entries altogether, eg this logical > >>>> flow > >>>> > >>>> -> fuse_uring_stop_list_entries(&queue->ent_in_userspace, ...); > >>>> -> fuse_uring_cancel() moves entry from avail q to userspace q > >>>> -> fuse_uring_stop_list_entries(&queue->ent_avail_queue, ...); > >>>> > >>>> If instead fuse_uring_teardown_entries() stops the available queue first, then > >>>> -> fuse_uring_stop_list_entries(&queue->ent_avail_queue, ...); > >>>> -> fuse_uring_cancel() > >>>> -> fuse_uring_stop_list_entries(&queue->ent_in_userspace, ...); > >>>> > >>>> seems fine now and fuse_uring_cancel() would basically be a no-op > >>>> since ent->state is now FRRS_TEARDOWN. > >>>> > >>> > >>> I'm not sure. Let's say we have > >>> > >>> task 1 task2 > >>> fuse_uring_cmd() > >>> fuse_uring_register() > >>> [slowness here] > >>> fuse_abort_conn() > >>> fuse_uring_teardown_entries() > >>> [slowness continue] > >>> fuse_uring_do_register() > >>> fuse_uring_prepare_cancel() > >>> fuse_uring_ent_avail() > >>> > >>> > >>> I.e. fuse_uring_teardown_entries() might be called before > >>> the command gets marked cancel-able and before it is > >>> moved to the avail queue. I think we should extend the patch > >>> and actually not set the ring to ready when fc->connected > >>> is set to 0. > >>> > >> > >> Hi Bernd, > >> > >> I think this is a separate race from the fuse_uring_cancel one. > >> afaics, this race can happen even if the user doesn't call > >> fuse_uring_cancel(). imo I think the cleanest solution to this > >> registration vs teardown race is to check queue->stopped in > >> fuse_uring_do_register() after we grab the queue spinlock, and if > >> queue->stopped is true, then just clean up the entry ourselves with > >> fuse_uring_entry_teardown()). > > > > What speaks against just doing as in the existing patch and freeing > > the ent_in_userspace entries fuse_uring_destruct()? > > IMO it covers both races, missing is just to avoid setting the ring > > as ready. Couldn't the entry in fuse_uring_do_register() be in the available queue when we get to fuse_uring_destruct() which means the existing patch would also have to iterate through the available queue too? eg i'm imagining something like fuse_uring_do_register() -> fuse_uring_prepare_cancel() *** fuse_uring_cancel() + teardown run on other threads -> fuse_uring_ent_avail() imo, I think this scenario is its own separate race (between registration and cancellation, that can happen irregardless of teardown) that should be fixed by calling fuse_uring_prepare_cancel() only after the fuse_uring_ent_avail() call, but I think this underscores a bit that explicitly checking against torn down entries is more robust when dealing with these races. > > Well, maybe cleaner, I don't have a strong opinion. We could skip the > comment and explanation with your approach. I don't really have a strong opinion on this either, just wanted to share my thoughts. If you'd rather go with the existing patch, then we should do that. Thanks, Joanne > > > Thanks, > Bernd ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v2] fs/fuse: fix potential memory leak from fuse_uring_cancel 2025-09-15 23:04 ` Joanne Koong @ 2025-09-16 9:17 ` Bernd Schubert 2025-09-16 20:12 ` Joanne Koong 0 siblings, 1 reply; 10+ messages in thread From: Bernd Schubert @ 2025-09-16 9:17 UTC (permalink / raw) To: Joanne Koong; +Cc: Jian Huang Li, linux-fsdevel, miklos On 9/16/25 01:04, Joanne Koong wrote: > On Mon, Sep 15, 2025 at 2:57 PM Bernd Schubert <bernd@bsbernd.com> wrote: >> On 9/15/25 23:46, Bernd Schubert wrote: >>> On 9/15/25 23:23, Joanne Koong wrote: >>>> On Mon, Sep 15, 2025 at 1:15 PM Bernd Schubert <bernd@bsbernd.com> wrote: >>>>> >>>>> Hi Joanne, >>>>> >>>>> thanks for looking into this. >>>>> >>>>> On 9/15/25 20:15, Joanne Koong wrote: >>>>>> On Thu, Sep 11, 2025 at 3:34 AM Jian Huang Li <ali@ddn.com> wrote: >>>>>>> >>>>>>> This issue could be observed sometimes during libfuse xfstests, from >>>>>>> dmseg prints some like "kernel: WARNING: CPU: 4 PID: 0 at >>>>>>> fs/fuse/dev_uring.c:204 fuse_uring_destruct+0x1f5/0x200 [fuse]". >>>>>>> >>>>>>> The cause is, if when fuse daemon just submitted >>>>>>> FUSE_IO_URING_CMD_REGISTER SQEs, then umount or fuse daemon quits at >>>>>>> this very early stage. After all uring queues stopped, might have one or >>>>>>> more unprocessed FUSE_IO_URING_CMD_REGISTER SQEs get processed then some >>>>>>> new ring entities are created and added to ent_avail_queue, and >>>>>>> immediately fuse_uring_cancel moves them to ent_in_userspace after SQEs >>>>>>> get canceled. These ring entities will not be moved to ent_released, and >>>>>>> will stay in ent_in_userspace when fuse_uring_destruct is called, needed >>>>>>> be freed by the function. >>>>>> >>>>>> Hi Jian, >>>>>> >>>>>> Does it suffice to fix this race by tearing down the entries from the >>>>>> available queue first before tearing down the entries in the userspace >>>>>> queue? eg something like >>>>>> >>>>>> static void fuse_uring_teardown_entries(struct fuse_ring_queue *queue) >>>>>> { >>>>>> - fuse_uring_stop_list_entries(&queue->ent_in_userspace, queue, >>>>>> - FRRS_USERSPACE); >>>>>> fuse_uring_stop_list_entries(&queue->ent_avail_queue, queue, >>>>>> FRRS_AVAILABLE); >>>>>> + fuse_uring_stop_list_entries(&queue->ent_in_userspace, queue, >>>>>> + FRRS_USERSPACE); >>>>>> } >>>>>> >>>>>> AFAICT, the race happens right now because when fuse_uring_cancel() >>>>>> moves the FRRS_AVAILABLE entries on the ent_avail_queue to the >>>>>> ent_in_userspace queue, fuse_uring_teardown_entries() may have already >>>>>> called fuse_uring_stop_list_entries() on the ent_in_userspace queue, >>>>>> thereby now missing the just-moved entries altogether, eg this logical >>>>>> flow >>>>>> >>>>>> -> fuse_uring_stop_list_entries(&queue->ent_in_userspace, ...); >>>>>> -> fuse_uring_cancel() moves entry from avail q to userspace q >>>>>> -> fuse_uring_stop_list_entries(&queue->ent_avail_queue, ...); >>>>>> >>>>>> If instead fuse_uring_teardown_entries() stops the available queue first, then >>>>>> -> fuse_uring_stop_list_entries(&queue->ent_avail_queue, ...); >>>>>> -> fuse_uring_cancel() >>>>>> -> fuse_uring_stop_list_entries(&queue->ent_in_userspace, ...); >>>>>> >>>>>> seems fine now and fuse_uring_cancel() would basically be a no-op >>>>>> since ent->state is now FRRS_TEARDOWN. >>>>>> >>>>> >>>>> I'm not sure. Let's say we have >>>>> >>>>> task 1 task2 >>>>> fuse_uring_cmd() >>>>> fuse_uring_register() >>>>> [slowness here] >>>>> fuse_abort_conn() >>>>> fuse_uring_teardown_entries() >>>>> [slowness continue] >>>>> fuse_uring_do_register() >>>>> fuse_uring_prepare_cancel() >>>>> fuse_uring_ent_avail() >>>>> >>>>> >>>>> I.e. fuse_uring_teardown_entries() might be called before >>>>> the command gets marked cancel-able and before it is >>>>> moved to the avail queue. I think we should extend the patch >>>>> and actually not set the ring to ready when fc->connected >>>>> is set to 0. >>>>> >>>> >>>> Hi Bernd, >>>> >>>> I think this is a separate race from the fuse_uring_cancel one. >>>> afaics, this race can happen even if the user doesn't call >>>> fuse_uring_cancel(). imo I think the cleanest solution to this >>>> registration vs teardown race is to check queue->stopped in >>>> fuse_uring_do_register() after we grab the queue spinlock, and if >>>> queue->stopped is true, then just clean up the entry ourselves with >>>> fuse_uring_entry_teardown()). >>> >>> What speaks against just doing as in the existing patch and freeing >>> the ent_in_userspace entries fuse_uring_destruct()? >>> IMO it covers both races, missing is just to avoid setting the ring >>> as ready. > > Couldn't the entry in fuse_uring_do_register() be in the available > queue when we get to fuse_uring_destruct() which means the existing We would never go into fuse_uring_destruct(), because io-uring would still hold references on the fuse device / fuse_conn. > patch would also have to iterate through the available queue too? eg > i'm imagining something like > > fuse_uring_do_register() > -> fuse_uring_prepare_cancel() > *** fuse_uring_cancel() + teardown run on other threads > -> fuse_uring_ent_avail() Up to hear it is right, but then see io_ring_exit_work() in io_uring.c. I.e. it will run io_uring_try_cancel_requests() in a loop. fuse_uring_cancel() is a noop as long as ent->state != FRRS_AVAILABLE If commands are not cancelled at all, io_ring_exit_work() will print warnings in intervals - initially I hadn't figured out about IO_URING_F_CANCEL (and maybe it also didn't exist in earlier kernel versions) and then had added workarounds into fuse_dev_release(), because of the io-uring references. > > imo, I think this scenario is its own separate race (between > registration and cancellation, that can happen irregardless of > teardown) that should be fixed by calling fuse_uring_prepare_cancel() > only after the fuse_uring_ent_avail() call, but I think this > underscores a bit that explicitly checking against torn down entries > is more robust when dealing with these races. > >> >> Well, maybe cleaner, I don't have a strong opinion. We could skip the >> comment and explanation with your approach. > > I don't really have a strong opinion on this either, just wanted to > share my thoughts. If you'd rather go with the existing patch, then we > should do that. I still think Jians patch works as it is, although there is a bit magic behind it. The mere fact that we have the discussion above it probably reason enough to at least try to find a way to make easier to read. Thanks, Bernd ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v2] fs/fuse: fix potential memory leak from fuse_uring_cancel 2025-09-16 9:17 ` Bernd Schubert @ 2025-09-16 20:12 ` Joanne Koong 0 siblings, 0 replies; 10+ messages in thread From: Joanne Koong @ 2025-09-16 20:12 UTC (permalink / raw) To: Bernd Schubert; +Cc: Jian Huang Li, linux-fsdevel, miklos On Tue, Sep 16, 2025 at 2:17 AM Bernd Schubert <bernd@bsbernd.com> wrote: > > On 9/16/25 01:04, Joanne Koong wrote: > > On Mon, Sep 15, 2025 at 2:57 PM Bernd Schubert <bernd@bsbernd.com> wrote: > >> On 9/15/25 23:46, Bernd Schubert wrote: > >>> On 9/15/25 23:23, Joanne Koong wrote: > >>>> On Mon, Sep 15, 2025 at 1:15 PM Bernd Schubert <bernd@bsbernd.com> wrote: > >>>>> > >>>>> Hi Joanne, > >>>>> > >>>>> thanks for looking into this. > >>>>> > >>>>> On 9/15/25 20:15, Joanne Koong wrote: > >>>>>> On Thu, Sep 11, 2025 at 3:34 AM Jian Huang Li <ali@ddn.com> wrote: > >>>>>>> > >>>>>>> This issue could be observed sometimes during libfuse xfstests, from > >>>>>>> dmseg prints some like "kernel: WARNING: CPU: 4 PID: 0 at > >>>>>>> fs/fuse/dev_uring.c:204 fuse_uring_destruct+0x1f5/0x200 [fuse]". > >>>>>>> > >>>>>>> The cause is, if when fuse daemon just submitted > >>>>>>> FUSE_IO_URING_CMD_REGISTER SQEs, then umount or fuse daemon quits at > >>>>>>> this very early stage. After all uring queues stopped, might have one or > >>>>>>> more unprocessed FUSE_IO_URING_CMD_REGISTER SQEs get processed then some > >>>>>>> new ring entities are created and added to ent_avail_queue, and > >>>>>>> immediately fuse_uring_cancel moves them to ent_in_userspace after SQEs > >>>>>>> get canceled. These ring entities will not be moved to ent_released, and > >>>>>>> will stay in ent_in_userspace when fuse_uring_destruct is called, needed > >>>>>>> be freed by the function. > >>>>>> > >>>>>> Hi Jian, > >>>>>> > >>>>>> Does it suffice to fix this race by tearing down the entries from the > >>>>>> available queue first before tearing down the entries in the userspace > >>>>>> queue? eg something like > >>>>>> > >>>>>> static void fuse_uring_teardown_entries(struct fuse_ring_queue *queue) > >>>>>> { > >>>>>> - fuse_uring_stop_list_entries(&queue->ent_in_userspace, queue, > >>>>>> - FRRS_USERSPACE); > >>>>>> fuse_uring_stop_list_entries(&queue->ent_avail_queue, queue, > >>>>>> FRRS_AVAILABLE); > >>>>>> + fuse_uring_stop_list_entries(&queue->ent_in_userspace, queue, > >>>>>> + FRRS_USERSPACE); > >>>>>> } > >>>>>> > >>>>>> AFAICT, the race happens right now because when fuse_uring_cancel() > >>>>>> moves the FRRS_AVAILABLE entries on the ent_avail_queue to the > >>>>>> ent_in_userspace queue, fuse_uring_teardown_entries() may have already > >>>>>> called fuse_uring_stop_list_entries() on the ent_in_userspace queue, > >>>>>> thereby now missing the just-moved entries altogether, eg this logical > >>>>>> flow > >>>>>> > >>>>>> -> fuse_uring_stop_list_entries(&queue->ent_in_userspace, ...); > >>>>>> -> fuse_uring_cancel() moves entry from avail q to userspace q > >>>>>> -> fuse_uring_stop_list_entries(&queue->ent_avail_queue, ...); > >>>>>> > >>>>>> If instead fuse_uring_teardown_entries() stops the available queue first, then > >>>>>> -> fuse_uring_stop_list_entries(&queue->ent_avail_queue, ...); > >>>>>> -> fuse_uring_cancel() > >>>>>> -> fuse_uring_stop_list_entries(&queue->ent_in_userspace, ...); > >>>>>> > >>>>>> seems fine now and fuse_uring_cancel() would basically be a no-op > >>>>>> since ent->state is now FRRS_TEARDOWN. > >>>>>> > >>>>> > >>>>> I'm not sure. Let's say we have > >>>>> > >>>>> task 1 task2 > >>>>> fuse_uring_cmd() > >>>>> fuse_uring_register() > >>>>> [slowness here] > >>>>> fuse_abort_conn() > >>>>> fuse_uring_teardown_entries() > >>>>> [slowness continue] > >>>>> fuse_uring_do_register() > >>>>> fuse_uring_prepare_cancel() > >>>>> fuse_uring_ent_avail() > >>>>> > >>>>> > >>>>> I.e. fuse_uring_teardown_entries() might be called before > >>>>> the command gets marked cancel-able and before it is > >>>>> moved to the avail queue. I think we should extend the patch > >>>>> and actually not set the ring to ready when fc->connected > >>>>> is set to 0. > >>>>> > >>>> > >>>> Hi Bernd, > >>>> > >>>> I think this is a separate race from the fuse_uring_cancel one. > >>>> afaics, this race can happen even if the user doesn't call > >>>> fuse_uring_cancel(). imo I think the cleanest solution to this > >>>> registration vs teardown race is to check queue->stopped in > >>>> fuse_uring_do_register() after we grab the queue spinlock, and if > >>>> queue->stopped is true, then just clean up the entry ourselves with > >>>> fuse_uring_entry_teardown()). > >>> > >>> What speaks against just doing as in the existing patch and freeing > >>> the ent_in_userspace entries fuse_uring_destruct()? > >>> IMO it covers both races, missing is just to avoid setting the ring > >>> as ready. > > > > Couldn't the entry in fuse_uring_do_register() be in the available > > queue when we get to fuse_uring_destruct() which means the existing > > We would never go into fuse_uring_destruct(), because io-uring would > still hold references on the fuse device / fuse_conn. > > > patch would also have to iterate through the available queue too? eg > > i'm imagining something like > > > > fuse_uring_do_register() > > -> fuse_uring_prepare_cancel() > > *** fuse_uring_cancel() + teardown run on other threads > > -> fuse_uring_ent_avail() > > Up to hear it is right, but then see io_ring_exit_work() in io_uring.c. > I.e. it will run io_uring_try_cancel_requests() in a loop. > fuse_uring_cancel() is a noop as long as ent->state != FRRS_AVAILABLE > > If commands are not cancelled at all, io_ring_exit_work() will print > warnings in intervals - initially I hadn't figured out about > IO_URING_F_CANCEL (and maybe it also didn't exist in earlier kernel > versions) and then had added workarounds into fuse_dev_release(), > because of the io-uring references. I took a look at the io-uring code (thanks for the pointer to io_ring_exit_work()) and now imo I think fuse_uring_cancel() should tear down the entry directly instead of moving it to the userspace queue. If I'm understanding it correctly, IO_URING_F_CANCEL is only sent on io-uring or process teardown (eg when the last refcount on the io-uring fd is dropped, or when a process exits) so the intended behavior with the cancel is to tear down the entry. imo the cleanest approach here is to just tear down the entry on cancel instead of moving it to the userspace queue to have fuse_uring_teardown_entries() try to handle it. Unless I'm missing some other reason it needs to be moved to the userspace queue? Thanks, Joanne > > > > > imo, I think this scenario is its own separate race (between > > registration and cancellation, that can happen irregardless of > > teardown) that should be fixed by calling fuse_uring_prepare_cancel() > > only after the fuse_uring_ent_avail() call, but I think this > > underscores a bit that explicitly checking against torn down entries > > is more robust when dealing with these races. > > > >> > >> Well, maybe cleaner, I don't have a strong opinion. We could skip the > >> comment and explanation with your approach. > > > > I don't really have a strong opinion on this either, just wanted to > > share my thoughts. If you'd rather go with the existing patch, then we > > should do that. > > > I still think Jians patch works as it is, although there is a bit magic > behind it. The mere fact that we have the discussion above it probably > reason enough to at least try to find a way to make easier to read. > > > Thanks, > Bernd ^ permalink raw reply [flat|nested] 10+ messages in thread
end of thread, other threads:[~2025-09-16 20:12 UTC | newest] Thread overview: 10+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2025-09-11 7:33 [PATCH v2] fs/fuse: fix potential memory leak from fuse_uring_cancel Jian Huang Li 2025-09-11 7:37 ` Jian Huang Li 2025-09-15 18:15 ` Joanne Koong 2025-09-15 20:15 ` Bernd Schubert 2025-09-15 21:23 ` Joanne Koong 2025-09-15 21:46 ` Bernd Schubert 2025-09-15 21:57 ` Bernd Schubert 2025-09-15 23:04 ` Joanne Koong 2025-09-16 9:17 ` Bernd Schubert 2025-09-16 20:12 ` Joanne Koong
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox