The fuse-io-uring transport imports each ring entry's payload buffer at
ring->max_payload_sz and bounds both copy directions against that value,
ignoring the buffer length the server actually registered. Both the
server-supplied reply payload_sz (fuse_uring_copy_from_ring) and an
oversized request payload such as a large FUSE_SETXATTR value
(fuse_uring_args_to_ring) can then overrun the imported iterator and hit
fuse_copy_fill()'s BUG_ON(!err):
kernel BUG at fs/fuse/dev.c:1053!
Oops: invalid opcode: 0000 [#1] SMP KASAN NOPTI
RIP: 0010:fuse_copy_fill (fs/fuse/dev.c:1022)
Call Trace:
fuse_copy_args (fs/fuse/dev.c:1329 fs/fuse/dev.c:1351)
fuse_uring_copy_from_ring (fs/fuse/dev_uring.c:686)
fuse_uring_cmd (fs/fuse/dev_uring.c:1226)
io_uring_cmd (io_uring/uring_cmd.c:271)
__io_issue_sqe (io_uring/io_uring.c:1395)
io_issue_sqe (io_uring/io_uring.c:1418)
io_submit_sqes (io_uring/io_uring.c:1649 io_uring/io_uring.c:1934
io_uring/io_uring.c:2057)
__do_sys_io_uring_enter (io_uring/io_uring.c:2646)
do_syscall_64 (arch/x86/entry/syscall_64.c:63 arch/x86/entry/syscall_64.c:94)
entry_SYSCALL_64_after_hwframe (arch/x86/entry/entry_64.S:121)
The request path overruns the same way, via fuse_copy_args() ->
fuse_uring_args_to_ring().
Store the registered payload length (payload->iov_len) in the ring entry
and use it for the import and both bounds checks, so the buffer the
server provided is honoured and an oversized reply/request is rejected
(-EINVAL for a reply, and -E2BIG/-EIO for a request, matching
fuse_dev_do_read()) instead of panicking.
Fixes: c090c8abae4b ("fuse: Add io-uring sqe commit and fetch support")
Cc: [email protected]
Reported-by: Weiming Shi <[email protected]>
Assisted-by: Claude:claude-opus-4-8
Signed-off-by: Xiang Mei <[email protected]>
---
v3: propose the patch fixing another issue found by Bernd by Joanne suggested
way
fs/fuse/dev_uring.c | 9 ++++++++-
fs/fuse/dev_uring_i.h | 1 +
2 files changed, 9 insertions(+), 1 deletion(-)
diff --git a/fs/fuse/dev_uring.c b/fs/fuse/dev_uring.c
index 0814681eb04b..248e5a3e340e 100644
--- a/fs/fuse/dev_uring.c
+++ b/fs/fuse/dev_uring.c
@@ -650,7 +650,7 @@ static int setup_fuse_copy_state(struct fuse_copy_state *cs,
{
int err;
- err = import_ubuf(dir, ent->payload, ring->max_payload_sz, iter);
+ err = import_ubuf(dir, ent->payload, ent->payload_sz, iter);
if (err) {
pr_info_ratelimited("fuse: Import of user buffer failed\n");
return err;
@@ -679,6 +679,9 @@ static int fuse_uring_copy_from_ring(struct fuse_ring *ring,
if (err)
return err;
+ if (ring_in_out.payload_sz > ent->payload_sz)
+ return -EINVAL;
+
err = setup_fuse_copy_state(&cs, ring, req, ent, ITER_SOURCE, &iter);
if (err)
return err;
@@ -725,6 +728,9 @@ static int fuse_uring_args_to_ring(struct fuse_ring *ring,
struct fuse_req *req,
num_args--;
}
+ if (fuse_len_args(num_args, (struct fuse_arg *)in_args) >
ent->payload_sz)
+ return args->opcode == FUSE_SETXATTR ? -E2BIG : -EIO;
+
/* copy the payload */
err = fuse_copy_args(&cs, num_args, args->in_pages,
(struct fuse_arg *)in_args, 0);
@@ -1163,6 +1169,7 @@ fuse_uring_create_ring_ent(struct io_uring_cmd *cmd,
ent->queue = queue;
ent->headers = headers->iov_base;
ent->payload = payload->iov_base;
+ ent->payload_sz = payload->iov_len;
atomic_inc(&ring->queue_refs);
return ent;
diff --git a/fs/fuse/dev_uring_i.h b/fs/fuse/dev_uring_i.h
index 55f8d04e4b0b..efa7f034496a 100644
--- a/fs/fuse/dev_uring_i.h
+++ b/fs/fuse/dev_uring_i.h
@@ -41,6 +41,7 @@ struct fuse_ring_ent {
/* userspace buffer */
struct fuse_uring_req_header __user *headers;
void __user *payload;
+ size_t payload_sz;
/* the ring queue that owns the request */
struct fuse_ring_queue *queue;
--
2.43.0