* [PATCH v4 2/3] fuse: bound io-uring payload copies to the registered buffer size
2026-07-14 23:54 [PATCH v4 1/3] fuse: whitelist the request headers for usercopy Xiang Mei
@ 2026-07-14 23:54 ` Xiang Mei
2026-07-14 23:54 ` [PATCH v4 3/3] fuse: deduplicate the oversized-request error selection Xiang Mei
` (2 subsequent siblings)
3 siblings, 0 replies; 8+ messages in thread
From: Xiang Mei @ 2026-07-14 23:54 UTC (permalink / raw)
To: Joanne Koong, Bernd Schubert, Baokun Li, Miklos Szeredi,
Kees Cook, Gustavo A . R . Silva
Cc: fuse-devel, linux-hardening, linux-kernel, stable,
Luis Henriques, Pavel Begunkov, bestswngs, Xiang Mei
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: stable@vger.kernel.org
Reported-by: Weiming Shi <bestswngs@gmail.com>
Assisted-by: Claude:claude-opus-4-8
Signed-off-by: Xiang Mei <xmei5@asu.edu>
Reviewed-by: Joanne Koong <joannelkoong@gmail.com>
---
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 ...
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 77c8cec43d9c..4529505b2bca 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);
@@ -1159,6 +1165,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
^ permalink raw reply [flat|nested] 8+ messages in thread* [PATCH v4 3/3] fuse: deduplicate the oversized-request error selection
2026-07-14 23:54 [PATCH v4 1/3] fuse: whitelist the request headers for usercopy Xiang Mei
2026-07-14 23:54 ` [PATCH v4 2/3] fuse: bound io-uring payload copies to the registered buffer size Xiang Mei
@ 2026-07-14 23:54 ` Xiang Mei
2026-07-15 18:30 ` Joanne Koong
2026-07-15 2:17 ` [PATCH v4 1/3] fuse: whitelist the request headers for usercopy Baokun Li
2026-07-15 18:13 ` Joanne Koong
3 siblings, 1 reply; 8+ messages in thread
From: Xiang Mei @ 2026-07-14 23:54 UTC (permalink / raw)
To: Joanne Koong, Bernd Schubert, Baokun Li, Miklos Szeredi,
Kees Cook, Gustavo A . R . Silva
Cc: fuse-devel, linux-hardening, linux-kernel, stable,
Luis Henriques, Pavel Begunkov, bestswngs, Xiang Mei
fuse_dev_do_read() and fuse_uring_args_to_ring() both pick the error for
a request that does not fit the server's buffer, and both special-case
FUSE_SETXATTR. Move that choice into a helper so the two transports
cannot drift apart.
No functional change.
Signed-off-by: Xiang Mei <xmei5@asu.edu>
---
v4: introduce fuse_req_too_large_error as a helper
fs/fuse/dev.c | 5 +----
fs/fuse/dev_uring.c | 2 +-
fs/fuse/fuse_dev_i.h | 7 +++++++
3 files changed, 9 insertions(+), 5 deletions(-)
diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c
index b8e43e374b35..56c38aca7389 100644
--- a/fs/fuse/dev.c
+++ b/fs/fuse/dev.c
@@ -1584,10 +1584,7 @@ static ssize_t fuse_dev_do_read(struct fuse_dev *fud, struct file *file,
/* If request is too large, reply with an error and restart the read */
if (nbytes < reqsize) {
- req->out.h.error = -EIO;
- /* SETXATTR is special, since it may contain too large data */
- if (args->opcode == FUSE_SETXATTR)
- req->out.h.error = -E2BIG;
+ req->out.h.error = fuse_req_too_large_error(args);
fuse_request_end(req);
goto restart;
}
diff --git a/fs/fuse/dev_uring.c b/fs/fuse/dev_uring.c
index 4529505b2bca..cebd8f871627 100644
--- a/fs/fuse/dev_uring.c
+++ b/fs/fuse/dev_uring.c
@@ -729,7 +729,7 @@ static int fuse_uring_args_to_ring(struct fuse_ring *ring, struct fuse_req *req,
}
if (fuse_len_args(num_args, (struct fuse_arg *)in_args) > ent->payload_sz)
- return args->opcode == FUSE_SETXATTR ? -E2BIG : -EIO;
+ return fuse_req_too_large_error(args);
/* copy the payload */
err = fuse_copy_args(&cs, num_args, args->in_pages,
diff --git a/fs/fuse/fuse_dev_i.h b/fs/fuse/fuse_dev_i.h
index b511aaab6bfc..4958158c0f02 100644
--- a/fs/fuse/fuse_dev_i.h
+++ b/fs/fuse/fuse_dev_i.h
@@ -13,6 +13,8 @@
#include <linux/workqueue.h>
#include <linux/fs.h>
+#include "args.h"
+
/* Ordinary requests have even IDs, while interrupts IDs are odd */
#define FUSE_INT_REQ_BIT (1ULL << 0)
#define FUSE_REQ_ID_STEP (1ULL << 1)
@@ -367,6 +369,11 @@ static inline struct fuse_dev *__fuse_get_dev(struct file *file)
return fud;
}
+static inline int fuse_req_too_large_error(struct fuse_args *args)
+{
+ return args->opcode == FUSE_SETXATTR ? -E2BIG : -EIO;
+}
+
void fuse_iqueue_init(struct fuse_iqueue *fiq, const struct fuse_iqueue_ops *ops, void *priv);
struct fuse_dev *fuse_get_dev(struct file *file);
--
2.43.0
^ permalink raw reply [flat|nested] 8+ messages in thread* Re: [PATCH v4 3/3] fuse: deduplicate the oversized-request error selection
2026-07-14 23:54 ` [PATCH v4 3/3] fuse: deduplicate the oversized-request error selection Xiang Mei
@ 2026-07-15 18:30 ` Joanne Koong
0 siblings, 0 replies; 8+ messages in thread
From: Joanne Koong @ 2026-07-15 18:30 UTC (permalink / raw)
To: Xiang Mei
Cc: Bernd Schubert, Baokun Li, Miklos Szeredi, Kees Cook,
Gustavo A . R . Silva, fuse-devel, linux-hardening, linux-kernel,
stable, Luis Henriques, Pavel Begunkov, bestswngs
On Tue, Jul 14, 2026 at 4:54 PM Xiang Mei <xmei5@asu.edu> wrote:
>
> fuse_dev_do_read() and fuse_uring_args_to_ring() both pick the error for
> a request that does not fit the server's buffer, and both special-case
> FUSE_SETXATTR. Move that choice into a helper so the two transports
> cannot drift apart.
>
> No functional change.
>
> Signed-off-by: Xiang Mei <xmei5@asu.edu>
> ---
> v4: introduce fuse_req_too_large_error as a helper
>
> fs/fuse/dev.c | 5 +----
> fs/fuse/dev_uring.c | 2 +-
> fs/fuse/fuse_dev_i.h | 7 +++++++
> 3 files changed, 9 insertions(+), 5 deletions(-)
>
> diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c
> index b8e43e374b35..56c38aca7389 100644
> --- a/fs/fuse/dev.c
> +++ b/fs/fuse/dev.c
> @@ -1584,10 +1584,7 @@ static ssize_t fuse_dev_do_read(struct fuse_dev *fud, struct file *file,
>
> /* If request is too large, reply with an error and restart the read */
> if (nbytes < reqsize) {
> - req->out.h.error = -EIO;
> - /* SETXATTR is special, since it may contain too large data */
> - if (args->opcode == FUSE_SETXATTR)
> - req->out.h.error = -E2BIG;
> + req->out.h.error = fuse_req_too_large_error(args);
> fuse_request_end(req);
> goto restart;
> }
> diff --git a/fs/fuse/dev_uring.c b/fs/fuse/dev_uring.c
> index 4529505b2bca..cebd8f871627 100644
> --- a/fs/fuse/dev_uring.c
> +++ b/fs/fuse/dev_uring.c
> @@ -729,7 +729,7 @@ static int fuse_uring_args_to_ring(struct fuse_ring *ring, struct fuse_req *req,
> }
>
> if (fuse_len_args(num_args, (struct fuse_arg *)in_args) > ent->payload_sz)
> - return args->opcode == FUSE_SETXATTR ? -E2BIG : -EIO;
> + return fuse_req_too_large_error(args);
>
> /* copy the payload */
> err = fuse_copy_args(&cs, num_args, args->in_pages,
> diff --git a/fs/fuse/fuse_dev_i.h b/fs/fuse/fuse_dev_i.h
> index b511aaab6bfc..4958158c0f02 100644
> --- a/fs/fuse/fuse_dev_i.h
> +++ b/fs/fuse/fuse_dev_i.h
> @@ -13,6 +13,8 @@
> #include <linux/workqueue.h>
> #include <linux/fs.h>
>
> +#include "args.h"
> +
> /* Ordinary requests have even IDs, while interrupts IDs are odd */
> #define FUSE_INT_REQ_BIT (1ULL << 0)
> #define FUSE_REQ_ID_STEP (1ULL << 1)
> @@ -367,6 +369,11 @@ static inline struct fuse_dev *__fuse_get_dev(struct file *file)
> return fud;
> }
>
> +static inline int fuse_req_too_large_error(struct fuse_args *args)
> +{
> + return args->opcode == FUSE_SETXATTR ? -E2BIG : -EIO;
> +}
Could you add a comment to the top of this function? Maybe something
like "A request whose payload size exceeds the transport buffer size
is rejected with -EIO. The exception is FUSE_SETXATTR whose value may
legitimately be oversized and is rejected with -E2BIG, matching the
vfs setxattr path".
Reviewed-by: Joanne Koong <joannelkoong@gmail.com>
Thanks,
Joanne
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH v4 1/3] fuse: whitelist the request headers for usercopy
2026-07-14 23:54 [PATCH v4 1/3] fuse: whitelist the request headers for usercopy Xiang Mei
2026-07-14 23:54 ` [PATCH v4 2/3] fuse: bound io-uring payload copies to the registered buffer size Xiang Mei
2026-07-14 23:54 ` [PATCH v4 3/3] fuse: deduplicate the oversized-request error selection Xiang Mei
@ 2026-07-15 2:17 ` Baokun Li
2026-07-15 18:13 ` Joanne Koong
3 siblings, 0 replies; 8+ messages in thread
From: Baokun Li @ 2026-07-15 2:17 UTC (permalink / raw)
To: Xiang Mei
Cc: fuse-devel, linux-hardening, linux-kernel, stable,
Luis Henriques, Pavel Begunkov, bestswngs, Joanne Koong,
Bernd Schubert, Miklos Szeredi, Gustavo A . R . Silva, Kees Cook
On 2026/7/15 07:54, Xiang Mei 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: stable@vger.kernel.org
> Reported-by: Weiming Shi <bestswngs@gmail.com>
> Suggested-by: Baokun Li <libaokun@linux.alibaba.com>
> Assisted-by: Claude:claude-opus-4-8
> Signed-off-by: Xiang Mei <xmei5@asu.edu>
Looks good, feel free to add:
Reviewed-by: Baokun Li <libaokun@linux.alibaba.com>
> ---
> 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 */
^ permalink raw reply [flat|nested] 8+ messages in thread* Re: [PATCH v4 1/3] fuse: whitelist the request headers for usercopy
2026-07-14 23:54 [PATCH v4 1/3] fuse: whitelist the request headers for usercopy Xiang Mei
` (2 preceding siblings ...)
2026-07-15 2:17 ` [PATCH v4 1/3] fuse: whitelist the request headers for usercopy Baokun Li
@ 2026-07-15 18:13 ` Joanne Koong
2026-07-15 21:59 ` Bernd Schubert
3 siblings, 1 reply; 8+ messages in thread
From: Joanne Koong @ 2026-07-15 18:13 UTC (permalink / raw)
To: Xiang Mei
Cc: Bernd Schubert, Baokun Li, Miklos Szeredi, Kees Cook,
Gustavo A . R . Silva, fuse-devel, linux-hardening, linux-kernel,
stable, Luis Henriques, Pavel Begunkov, bestswngs
On Tue, Jul 14, 2026 at 4:54 PM Xiang Mei <xmei5@asu.edu> 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: stable@vger.kernel.org
> Reported-by: Weiming Shi <bestswngs@gmail.com>
> Suggested-by: Baokun Li <libaokun@linux.alibaba.com>
> Assisted-by: Claude:claude-opus-4-8
> Signed-off-by: Xiang Mei <xmei5@asu.edu>
> ---
> 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
^ permalink raw reply [flat|nested] 8+ messages in thread* Re: [PATCH v4 1/3] fuse: whitelist the request headers for usercopy
2026-07-15 18:13 ` Joanne Koong
@ 2026-07-15 21:59 ` Bernd Schubert
2026-07-27 23:39 ` Xiang Mei
0 siblings, 1 reply; 8+ messages in thread
From: Bernd Schubert @ 2026-07-15 21:59 UTC (permalink / raw)
To: Joanne Koong, Xiang Mei
Cc: Baokun Li, Miklos Szeredi, Kees Cook, Gustavo A . R . Silva,
fuse-devel, linux-hardening, linux-kernel, stable,
Luis Henriques, Pavel Begunkov, bestswngs
On 7/15/26 20:13, Joanne Koong wrote:
> On Tue, Jul 14, 2026 at 4:54 PM Xiang Mei <xmei5@asu.edu> 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: stable@vger.kernel.org
>> Reported-by: Weiming Shi <bestswngs@gmail.com>
>> Suggested-by: Baokun Li <libaokun@linux.alibaba.com>
>> Assisted-by: Claude:claude-opus-4-8
>> Signed-off-by: Xiang Mei <xmei5@asu.edu>
>> ---
>> 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.
Sorry for late replies, currently on vacation and reviews only when
everyone else is asleep.
I don't have a strong opinion, I wondered the same if the stack copy was
avoidable, but then came to the same conclusion as Joanne - negligible
overhead.
Thanks,
Bernd
^ permalink raw reply [flat|nested] 8+ messages in thread* Re: [PATCH v4 1/3] fuse: whitelist the request headers for usercopy
2026-07-15 21:59 ` Bernd Schubert
@ 2026-07-27 23:39 ` Xiang Mei
0 siblings, 0 replies; 8+ messages in thread
From: Xiang Mei @ 2026-07-27 23:39 UTC (permalink / raw)
To: Bernd Schubert, Joanne Koong
Cc: Baokun Li, Miklos Szeredi, Kees Cook, Gustavo A . R . Silva,
fuse-devel, linux-hardening, linux-kernel, stable,
Luis Henriques, Pavel Begunkov, bestswngs
On Wed, Jul 15, 2026 at 2:59 PM Bernd Schubert <bernd@bsbernd.com> wrote:
>
>
>
> On 7/15/26 20:13, Joanne Koong wrote:
> > On Tue, Jul 14, 2026 at 4:54 PM Xiang Mei <xmei5@asu.edu> 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: stable@vger.kernel.org
> >> Reported-by: Weiming Shi <bestswngs@gmail.com>
> >> Suggested-by: Baokun Li <libaokun@linux.alibaba.com>
> >> Assisted-by: Claude:claude-opus-4-8
> >> Signed-off-by: Xiang Mei <xmei5@asu.edu>
> >> ---
> >> 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.
>
> Sorry for late replies, currently on vacation and reviews only when
> everyone else is asleep.
>
> I don't have a strong opinion, I wondered the same if the stack copy was
> avoidable, but then came to the same conclusion as Joanne - negligible
> overhead.
>
>
> Thanks,
> Bernd
Sorry for the late reply. I just got some free time to continue the
patch. Thanks for all of your review and advice. I'll keep the code
simple and just use the stack var to fix it for 1/3 and add Joanne's
suggested comment to 3/3.
The v5 has been sent:
https://lore.kernel.org/fuse-devel/20260727233706.218446-1-xmei5@asu.edu/T/#t
Thanks so much!
Xiang
Xiang
^ permalink raw reply [flat|nested] 8+ messages in thread