208 lines
6.0 KiB
Diff
208 lines
6.0 KiB
Diff
From 6e7ac63c4c4a8fe7c66f856f4091d9b20899f167 Mon Sep 17 00:00:00 2001
|
|
From: Bernd Schubert <bschubert@ddn.com>
|
|
Date: Tue, 25 Mar 2025 18:29:31 +0100
|
|
Subject: fuse: {io-uring} Fix a possible req cancellation race
|
|
|
|
task-A (application) might be in request_wait_answer and
|
|
try to remove the request when it has FR_PENDING set.
|
|
|
|
task-B (a fuse-server io-uring task) might handle this
|
|
request with FUSE_IO_URING_CMD_COMMIT_AND_FETCH, when
|
|
fetching the next request and accessed the req from
|
|
the pending list in fuse_uring_ent_assign_req().
|
|
That code path was not protected by fiq->lock and so
|
|
might race with task-A.
|
|
|
|
For scaling reasons we better don't use fiq->lock, but
|
|
add a handler to remove canceled requests from the queue.
|
|
|
|
This also removes usage of fiq->lock from
|
|
fuse_uring_add_req_to_ring_ent() altogether, as it was
|
|
there just to protect against this race and incomplete.
|
|
|
|
Also added is a comment why FR_PENDING is not cleared.
|
|
|
|
Fixes: c090c8abae4b ("fuse: Add io-uring sqe commit and fetch support")
|
|
Cc: <stable@vger.kernel.org> # v6.14
|
|
Reported-by: Joanne Koong <joannelkoong@gmail.com>
|
|
Closes: https://lore.kernel.org/all/CAJnrk1ZgHNb78dz-yfNTpxmW7wtT88A=m-zF0ZoLXKLUHRjNTw@mail.gmail.com/
|
|
Signed-off-by: Bernd Schubert <bschubert@ddn.com>
|
|
Reviewed-by: Joanne Koong <joannelkoong@gmail.com>
|
|
Signed-off-by: Miklos Szeredi <mszeredi@redhat.com>
|
|
---
|
|
fs/fuse/dev.c | 34 +++++++++++++++++++++++++---------
|
|
fs/fuse/dev_uring.c | 15 +++++++++++----
|
|
fs/fuse/dev_uring_i.h | 6 ++++++
|
|
fs/fuse/fuse_dev_i.h | 1 +
|
|
fs/fuse/fuse_i.h | 3 +++
|
|
5 files changed, 46 insertions(+), 13 deletions(-)
|
|
|
|
--- a/fs/fuse/dev.c
|
|
+++ b/fs/fuse/dev.c
|
|
@@ -407,6 +407,24 @@ static int queue_interrupt(struct fuse_r
|
|
return 0;
|
|
}
|
|
|
|
+bool fuse_remove_pending_req(struct fuse_req *req, spinlock_t *lock)
|
|
+{
|
|
+ spin_lock(lock);
|
|
+ if (test_bit(FR_PENDING, &req->flags)) {
|
|
+ /*
|
|
+ * FR_PENDING does not get cleared as the request will end
|
|
+ * up in destruction anyway.
|
|
+ */
|
|
+ list_del(&req->list);
|
|
+ spin_unlock(lock);
|
|
+ __fuse_put_request(req);
|
|
+ req->out.h.error = -EINTR;
|
|
+ return true;
|
|
+ }
|
|
+ spin_unlock(lock);
|
|
+ return false;
|
|
+}
|
|
+
|
|
static void request_wait_answer(struct fuse_req *req)
|
|
{
|
|
struct fuse_conn *fc = req->fm->fc;
|
|
@@ -428,22 +446,20 @@ static void request_wait_answer(struct f
|
|
}
|
|
|
|
if (!test_bit(FR_FORCE, &req->flags)) {
|
|
+ bool removed;
|
|
+
|
|
/* Only fatal signals may interrupt this */
|
|
err = wait_event_killable(req->waitq,
|
|
test_bit(FR_FINISHED, &req->flags));
|
|
if (!err)
|
|
return;
|
|
|
|
- spin_lock(&fiq->lock);
|
|
- /* Request is not yet in userspace, bail out */
|
|
- if (test_bit(FR_PENDING, &req->flags)) {
|
|
- list_del(&req->list);
|
|
- spin_unlock(&fiq->lock);
|
|
- __fuse_put_request(req);
|
|
- req->out.h.error = -EINTR;
|
|
+ if (test_bit(FR_URING, &req->flags))
|
|
+ removed = fuse_uring_remove_pending_req(req);
|
|
+ else
|
|
+ removed = fuse_remove_pending_req(req, &fiq->lock);
|
|
+ if (removed)
|
|
return;
|
|
- }
|
|
- spin_unlock(&fiq->lock);
|
|
}
|
|
|
|
/*
|
|
--- a/fs/fuse/dev_uring.c
|
|
+++ b/fs/fuse/dev_uring.c
|
|
@@ -726,8 +726,6 @@ static void fuse_uring_add_req_to_ring_e
|
|
struct fuse_req *req)
|
|
{
|
|
struct fuse_ring_queue *queue = ent->queue;
|
|
- struct fuse_conn *fc = req->fm->fc;
|
|
- struct fuse_iqueue *fiq = &fc->iq;
|
|
|
|
lockdep_assert_held(&queue->lock);
|
|
|
|
@@ -737,9 +735,7 @@ static void fuse_uring_add_req_to_ring_e
|
|
ent->state);
|
|
}
|
|
|
|
- spin_lock(&fiq->lock);
|
|
clear_bit(FR_PENDING, &req->flags);
|
|
- spin_unlock(&fiq->lock);
|
|
ent->fuse_req = req;
|
|
ent->state = FRRS_FUSE_REQ;
|
|
list_move(&ent->list, &queue->ent_w_req_queue);
|
|
@@ -1238,6 +1234,8 @@ void fuse_uring_queue_fuse_req(struct fu
|
|
if (unlikely(queue->stopped))
|
|
goto err_unlock;
|
|
|
|
+ set_bit(FR_URING, &req->flags);
|
|
+ req->ring_queue = queue;
|
|
ent = list_first_entry_or_null(&queue->ent_avail_queue,
|
|
struct fuse_ring_ent, list);
|
|
if (ent)
|
|
@@ -1276,6 +1274,8 @@ bool fuse_uring_queue_bq_req(struct fuse
|
|
return false;
|
|
}
|
|
|
|
+ set_bit(FR_URING, &req->flags);
|
|
+ req->ring_queue = queue;
|
|
list_add_tail(&req->list, &queue->fuse_req_bg_queue);
|
|
|
|
ent = list_first_entry_or_null(&queue->ent_avail_queue,
|
|
@@ -1306,6 +1306,13 @@ bool fuse_uring_queue_bq_req(struct fuse
|
|
return true;
|
|
}
|
|
|
|
+bool fuse_uring_remove_pending_req(struct fuse_req *req)
|
|
+{
|
|
+ struct fuse_ring_queue *queue = req->ring_queue;
|
|
+
|
|
+ return fuse_remove_pending_req(req, &queue->lock);
|
|
+}
|
|
+
|
|
static const struct fuse_iqueue_ops fuse_io_uring_ops = {
|
|
/* should be send over io-uring as enhancement */
|
|
.send_forget = fuse_dev_queue_forget,
|
|
--- a/fs/fuse/dev_uring_i.h
|
|
+++ b/fs/fuse/dev_uring_i.h
|
|
@@ -142,6 +142,7 @@ void fuse_uring_abort_end_requests(struc
|
|
int fuse_uring_cmd(struct io_uring_cmd *cmd, unsigned int issue_flags);
|
|
void fuse_uring_queue_fuse_req(struct fuse_iqueue *fiq, struct fuse_req *req);
|
|
bool fuse_uring_queue_bq_req(struct fuse_req *req);
|
|
+bool fuse_uring_remove_pending_req(struct fuse_req *req);
|
|
|
|
static inline void fuse_uring_abort(struct fuse_conn *fc)
|
|
{
|
|
@@ -199,6 +200,11 @@ static inline bool fuse_uring_ready(stru
|
|
{
|
|
return false;
|
|
}
|
|
+
|
|
+static inline bool fuse_uring_remove_pending_req(struct fuse_req *req)
|
|
+{
|
|
+ return false;
|
|
+}
|
|
|
|
#endif /* CONFIG_FUSE_IO_URING */
|
|
|
|
--- a/fs/fuse/fuse_dev_i.h
|
|
+++ b/fs/fuse/fuse_dev_i.h
|
|
@@ -61,6 +61,7 @@ int fuse_copy_out_args(struct fuse_copy_
|
|
void fuse_dev_queue_forget(struct fuse_iqueue *fiq,
|
|
struct fuse_forget_link *forget);
|
|
void fuse_dev_queue_interrupt(struct fuse_iqueue *fiq, struct fuse_req *req);
|
|
+bool fuse_remove_pending_req(struct fuse_req *req, spinlock_t *lock);
|
|
|
|
#endif
|
|
|
|
--- a/fs/fuse/fuse_i.h
|
|
+++ b/fs/fuse/fuse_i.h
|
|
@@ -378,6 +378,7 @@ struct fuse_io_priv {
|
|
* FR_FINISHED: request is finished
|
|
* FR_PRIVATE: request is on private list
|
|
* FR_ASYNC: request is asynchronous
|
|
+ * FR_URING: request is handled through fuse-io-uring
|
|
*/
|
|
enum fuse_req_flag {
|
|
FR_ISREPLY,
|
|
@@ -392,6 +393,7 @@ enum fuse_req_flag {
|
|
FR_FINISHED,
|
|
FR_PRIVATE,
|
|
FR_ASYNC,
|
|
+ FR_URING,
|
|
};
|
|
|
|
/**
|
|
@@ -441,6 +443,7 @@ struct fuse_req {
|
|
|
|
#ifdef CONFIG_FUSE_IO_URING
|
|
void *ring_entry;
|
|
+ void *ring_queue;
|
|
#endif
|
|
};
|
|
|