On Tue, Jul 14, 2026 at 4:54 PM Xiang Mei <[email protected]> wrote: > > The fuse-io-uring transport copies req->in.h out to the ring in > fuse_uring_copy_to_ring() and req->out.h back in fuse_uring_commit(). > Both headers live inside the fuse_request slab object, whose cache > (fuse_req_cachep) is created without a usercopy whitelist, so copying > them directly to/from userspace trips CONFIG_HARDENED_USERCOPY and > panics: > > usercopy: Kernel memory exposure attempt detected from SLUB object > 'fuse_request' (offset 56, size 40)! > kernel BUG at mm/usercopy.c:102! > Oops: invalid opcode: 0000 [#1] SMP KASAN NOPTI > RIP: 0010:usercopy_abort (mm/usercopy.c:90) > Call Trace: > __check_heap_object (mm/slub.c:8268) > __check_object_size (mm/usercopy.c:197 mm/usercopy.c:258 mm/usercopy.c:223) > copy_header_to_ring (fs/fuse/dev_uring.c:618) > fuse_uring_prepare_send (fs/fuse/dev_uring.c:776 fs/fuse/dev_uring.c:785) > fuse_uring_send_in_task (fs/fuse/dev_uring.c:1306) > tctx_task_work_run (io_uring/tw.c:96) > 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:2685) > entry_SYSCALL_64_after_hwframe (arch/x86/entry/entry_64.S:121) > > in.h and out.h are adjacent in struct fuse_req, so a single usercopy > region starting at in.h covers both and nothing else. Create the cache > with that region whitelisted. > > Fixes: c090c8abae4b ("fuse: Add io-uring sqe commit and fetch support") > Cc: [email protected] > Reported-by: Weiming Shi <[email protected]> > Suggested-by: Baokun Li <[email protected]> > Assisted-by: Claude:claude-opus-4-8 > Signed-off-by: Xiang Mei <[email protected]> > --- > v3: no context change; add Bernd's Reviewed-by > v4: drop previous tags; use kmem_cache_args to reserve usercopy area > > fs/fuse/dev.c | 9 +++++++-- > fs/fuse/fuse_dev_i.h | 5 +++++ > 2 files changed, 12 insertions(+), 2 deletions(-) > > diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c > index 5763a7cd3b37..b8e43e374b35 100644 > --- a/fs/fuse/dev.c > +++ b/fs/fuse/dev.c > @@ -2404,10 +2404,15 @@ static struct miscdevice fuse_miscdevice = { > > int __init fuse_dev_init(void) > { > + struct kmem_cache_args args = { > + .useroffset = offsetof(struct fuse_req, in.h), > + .usersize = sizeof_field(struct fuse_req, in.h) + > + sizeof_field(struct fuse_req, out.h), > + }; > int err = -ENOMEM; > + > fuse_req_cachep = kmem_cache_create("fuse_request", > - sizeof(struct fuse_req), > - 0, 0, NULL); > + sizeof(struct fuse_req), &args, > 0); > if (!fuse_req_cachep) > goto out; > > diff --git a/fs/fuse/fuse_dev_i.h b/fs/fuse/fuse_dev_i.h > index 668c8391d61c..b511aaab6bfc 100644 > --- a/fs/fuse/fuse_dev_i.h > +++ b/fs/fuse/fuse_dev_i.h > @@ -81,6 +81,11 @@ struct fuse_req { > /** @flags: Request flags, updated with test/set/clear_bit() */ > unsigned long flags; > > + /* > + * @in and @out are the usercopy region of this cache (see > + * fuse_dev_init()); keep them adjacent. > + */ > + > /** @in: The request input header */ > struct { > /** @in.h: The request input header */ > -- > 2.43.0 >
I think this is more a matter of preference as they're both functionally correct but imo the previous approach seemed cleaner, given that only the io-uring path needs this. The extra hop goes through a tmp stack variable whose memory is already in the L1 cache and the memcpys are small (~40 bytes), so I think the cost is essentially negligible. Not sure if Bernd or Miklos or Amir have a preference here. Thanks, Joanne

