On Wed, Sep 9, 2026 at 12:20 PM Joanne Koong <[email protected]> wrote: > > On Sat, Sep 5, 2026 at 6:29 PM Xiang Mei <[email protected]> wrote: > > > > The fuse-io-uring copy paths do not check that the data they copy fits > > in the ring entry's payload buffer. fuse_uring_copy_from_ring() passes > > the server-supplied payload_sz straight to fuse_copy_out_args(), and > > fuse_uring_args_to_ring() copies the request in-args without comparing > > them against the buffer. Either can exhaust the imported iterator and > > hit fuse_copy_fill()'s BUG_ON(!err). > > > > An unprivileged user can trigger the request side by mounting a FUSE > > filesystem that registers small payload buffers and then issuing a > > setxattr() whose value exceeds them: > > > > kernel BUG at fs/fuse/dev.c:1064! > > Oops: invalid opcode: 0000 [#1] SMP KASAN NOPTI > > CPU: 1 UID: 0 PID: 148 Comm: exploit Not tainted 7.3.0-rc1 #2 > > RIP: 0010:fuse_copy_fill (fs/fuse/dev.c:1033) > > Call Trace: > > <TASK> > > fuse_copy_args (fs/fuse/dev.c:1354 fs/fuse/dev.c:1380) > > fuse_uring_args_to_ring (fs/fuse/dev_uring.c:891) > > fuse_uring_prepare_send (fs/fuse/dev_uring.c:940 > > fs/fuse/dev_uring.c:1057) > > fuse_uring_send_in_task (fs/fuse/dev_uring.c:1745) > > tctx_task_work_run (io_uring/tw.c:96) > > tctx_task_work (io_uring/tw.c:133) > > task_work_run (kernel/task_work.c:233) > > io_run_task_work (io_uring/tw.h:84) > > io_cqring_wait (io_uring/wait.c:278) > > __do_sys_io_uring_enter (io_uring/io_uring.c:2676) > > do_syscall_64 (arch/x86/entry/syscall_64.c:61) > > entry_SYSCALL_64_after_hwframe (arch/x86/entry/entry_64.S:121) > > </TASK> > > > > Bound both directions against ent->payload.iov_len, the length the > > server registered, so an oversized reply is rejected with -EINVAL and an > > oversized request with -E2BIG/-EIO, matching fuse_dev_do_read(). > > > > On a zero-copied request the last page-backed argument is handed to the > > server as registered pages rather than copied into the payload buffer, > > so exclude it from the request-side sum to avoid rejecting valid > > zero-copy reads and writes. > > > > 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]> > > --- > > v2: add: Cc stable and Reviewed-by tags > > v3: propose the patch fixing another issue found by Bernd by Joanne > > suggested way > > v4: no context change as v3; add Reviewed-by: Joanne Koong ... > > v5: no change > > v6: rebase on fuse.git#for-next. The ring entry already keeps the payload > > iovec and imports it at iov_len, so only the two bounds checks are left. > > Skip the page-backed arg on a zero-copied request, it does not consume > > the payload buffer. > > > > fs/fuse/dev_uring.c | 14 ++++++++++++++ > > 1 file changed, 14 insertions(+) > > > > diff --git a/fs/fuse/dev_uring.c b/fs/fuse/dev_uring.c > > index c6dd420c4034..981b7c295e5e 100644 > > --- a/fs/fuse/dev_uring.c > > +++ b/fs/fuse/dev_uring.c > > @@ -775,6 +775,9 @@ static int fuse_uring_copy_from_ring(struct fuse_req > > *req, > > if (err) > > return err; > > > > + if (ring_in_out.payload_sz > ent->payload.iov_len) > > + return -EINVAL; > > + > > This logic will break fuse zero-copy reads (ent->payload.iov_len is zero). >
You're right. This check wrongly rejects every zero-copy read reply. > imo I think the cleanest fix for this bug in general would be to just > remove the BUG_ON in fuse_copy_fill() and return back the error, eg > > --- a/fs/fuse/dev.c > +++ b/fs/fuse/dev.c > @@ static int fuse_copy_fill(struct fuse_copy_state *cs) > err = iov_iter_get_pages2(cs->iter, &page, PAGE_SIZE, 1, &off); > if (err < 0) > return err; > - BUG_ON(!err); > + if (!err) > + return -EIO; > cs->len = err; > Agreed, and it's a much better fix than bounding the copies. > This would cover both the request and reply side and get rid of the > special logic here and below. It'd also make backporting to stable a > lot easier. I think it would still be worth having the FUSE_SETXATTR > errno fix (eg returning -E2BIG instead of -EIO) > > if (fuse_len_args(num_args, (struct fuse_arg *)in_args) > > ent->payload.iov_len) > return args->opcode == FUSE_SETXATTR ? -E2BIG : -EIO; > > as its own patch, where that check is only about picking the right > errno, not about preventing the BUG. > > WDYT? > Thanks for your suggestions; this patch is better, and I have sent and tested v7: https://lore.kernel.org/fuse-devel/[email protected]/T/#t Xiang > Thanks, > Joanne > > > err = setup_fuse_copy_state(&cs, req, ent, ITER_SOURCE, &iter, > > issue_flags); > > if (err) > > @@ -854,6 +857,7 @@ static int fuse_uring_args_to_ring(struct fuse_req *req, > > int num_args = args->in_numargs; > > int err; > > struct iov_iter iter; > > + size_t copy_size; > > struct fuse_uring_ent_in_out ent_in_out = { > > .flags = 0, > > .commit_id = req->in.h.unique, > > @@ -887,6 +891,16 @@ static int fuse_uring_args_to_ring(struct fuse_req > > *req, > > num_args--; > > } > > > > + copy_size = fuse_len_args(num_args, (struct fuse_arg *)in_args); > > + /* a zero-copied page arg does not consume the payload buffer */ > > + if (cs.skip_folio_copy && args->in_pages && num_args) > > + copy_size -= in_args[num_args - 1].size; > > + > > + if (copy_size > ent->payload.iov_len) { > > + fuse_copy_finish(&cs); > > + 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); > > -- > > 2.43.0 > >

