fuse: fix potential memory leak from fuse_uring_cancel
If umount or fuse daemon quits at early stage, could happen all ring queues
have already stopped and later some FUSE_IO_URING_CMD_REGISTER commands get
canceled, that leaves ring entities in ent_in_userspace list and will not
be freed by fuse_uring_destruct.
Move such ring entities to ent_canceled list and ensure fuse_uring_destruct
frees these ring entities.
Fixes: b6236c8407cb ("fuse: {io-uring} Prevent mount point hang on
fuse-server termination")
Signed-off-by: Jian Huang Li <ali@xxxxxxx>
---
fs/fuse/dev_uring.c | 13 +++++++++++--
fs/fuse/dev_uring_i.h | 6 ++++++
2 files changed, 17 insertions(+), 2 deletions(-)
diff --git a/fs/fuse/dev_uring.c b/fs/fuse/dev_uring.c
index 249b210becb1..db35797853c1 100644
--- a/fs/fuse/dev_uring.c
+++ b/fs/fuse/dev_uring.c
@@ -203,6 +203,12 @@ void fuse_uring_destruct(struct fuse_conn *fc)
WARN_ON(!list_empty(&queue->ent_commit_queue));
WARN_ON(!list_empty(&queue->ent_in_userspace));
+ list_for_each_entry_safe(ent, next, &queue->ent_canceled,
+ list) {
+ list_del_init(&ent->list);
+ kfree(ent);
+ }
+
list_for_each_entry_safe(ent, next, &queue->ent_released,
list) {
list_del_init(&ent->list);
@@ -291,6 +297,7 @@ static struct fuse_ring_queue
*fuse_uring_create_queue(struct fuse_ring *ring,
INIT_LIST_HEAD(&queue->ent_in_userspace);
INIT_LIST_HEAD(&queue->fuse_req_queue);
INIT_LIST_HEAD(&queue->fuse_req_bg_queue);
+ INIT_LIST_HEAD(&queue->ent_canceled);
INIT_LIST_HEAD(&queue->ent_released);
queue->fpq.processing = pq;
@@ -391,6 +398,8 @@ 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_canceled, queue,
+ FRRS_CANCELED);
fuse_uring_stop_list_entries(&queue->ent_avail_queue, queue,
FRRS_AVAILABLE);
}
@@ -509,8 +518,8 @@ static void fuse_uring_cancel(struct io_uring_cmd *cmd,
queue = ent->queue;
spin_lock(&queue->lock);
if (ent->state == FRRS_AVAILABLE) {
- ent->state = FRRS_USERSPACE;
- list_move_tail(&ent->list, &queue->ent_in_userspace);
+ ent->state = FRRS_CANCELED;
+ list_move_tail(&ent->list, &queue->ent_canceled);
need_cmd_done = true;
ent->cmd = NULL;
}
diff --git a/fs/fuse/dev_uring_i.h b/fs/fuse/dev_uring_i.h
index 51a563922ce1..e62bd705e4f5 100644
--- a/fs/fuse/dev_uring_i.h
+++ b/fs/fuse/dev_uring_i.h
@@ -32,6 +32,9 @@ enum fuse_ring_req_state {
/* The ring entry is in teardown */
FRRS_TEARDOWN,
+ /* The ring entry is canceled */
+ FRRS_CANCELED,
+
/* The ring entry is released, but not freed yet */
FRRS_RELEASED,
};
@@ -85,6 +88,9 @@ struct fuse_ring_queue {
/* entries in userspace */
struct list_head ent_in_userspace;
+ /* entries that are canceled */
+ struct list_head ent_canceled;
+
/* entries that are released */
struct list_head ent_released;