* [PATCH v2 1/2] fuse: copy request headers via a stack buffer for io-uring
@ 2026-07-07 18:44 Xiang Mei
2026-07-07 18:44 ` [PATCH v2 2/2] fuse: reject oversized payload_sz in fuse_uring_copy_from_ring() Xiang Mei
2026-07-08 20:21 ` [PATCH v2 1/2] fuse: copy request headers via a stack buffer for io-uring Bernd Schubert
0 siblings, 2 replies; 6+ messages in thread
From: Xiang Mei @ 2026-07-07 18:44 UTC (permalink / raw)
To: Joanne Koong, djwong, Bernd Schubert, Miklos Szeredi, Kees Cook,
Gustavo A . R . Silva
Cc: stable, fuse-devel, linux-kernel, Pavel Begunkov, Luis Henriques,
Weiming Shi, Xiang Mei
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!
RIP: 0010:usercopy_abort+0x6c/0x80
Call Trace:
__check_heap_object
__check_object_size
copy_header_to_ring fs/fuse/dev_uring.c:618
fuse_uring_prepare_send
fuse_uring_send_in_task
...
__do_sys_io_uring_enter
entry_SYSCALL_64_after_hwframe
Bounce both headers through an on-stack copy so the usercopy touches
stack memory, not the slab object.
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>
---
v2: add: Cc stable and Reviewed-by tags
fs/fuse/dev_uring.c | 12 ++++++++----
1 file changed, 8 insertions(+), 4 deletions(-)
diff --git a/fs/fuse/dev_uring.c b/fs/fuse/dev_uring.c
index 77c8cec43d9c..0814681eb04b 100644
--- a/fs/fuse/dev_uring.c
+++ b/fs/fuse/dev_uring.c
@@ -744,6 +744,7 @@ static int fuse_uring_copy_to_ring(struct fuse_ring_ent *ent,
{
struct fuse_ring_queue *queue = ent->queue;
struct fuse_ring *ring = queue->ring;
+ struct fuse_in_header in_header;
int err;
err = -EIO;
@@ -765,8 +766,9 @@ static int fuse_uring_copy_to_ring(struct fuse_ring_ent *ent,
}
/* copy fuse_in_header */
- return copy_header_to_ring(ent, FUSE_URING_HEADER_IN_OUT, &req->in.h,
- sizeof(req->in.h));
+ in_header = req->in.h;
+ return copy_header_to_ring(ent, FUSE_URING_HEADER_IN_OUT, &in_header,
+ sizeof(in_header));
}
static int fuse_uring_prepare_send(struct fuse_ring_ent *ent,
@@ -871,11 +873,13 @@ static void fuse_uring_commit(struct fuse_ring_ent *ent, struct fuse_req *req,
unsigned int issue_flags)
{
struct fuse_ring *ring = ent->queue->ring;
+ struct fuse_out_header out_header;
ssize_t err = -EFAULT;
- if (copy_header_from_ring(ent, FUSE_URING_HEADER_IN_OUT, &req->out.h,
- sizeof(req->out.h)))
+ if (copy_header_from_ring(ent, FUSE_URING_HEADER_IN_OUT, &out_header,
+ sizeof(out_header)))
goto out;
+ req->out.h = out_header;
err = fuse_uring_out_header_has_err(&req->out.h, req);
if (err) {
--
2.43.0
^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH v2 2/2] fuse: reject oversized payload_sz in fuse_uring_copy_from_ring()
2026-07-07 18:44 [PATCH v2 1/2] fuse: copy request headers via a stack buffer for io-uring Xiang Mei
@ 2026-07-07 18:44 ` Xiang Mei
2026-07-08 19:22 ` Bernd Schubert
2026-07-08 20:21 ` [PATCH v2 1/2] fuse: copy request headers via a stack buffer for io-uring Bernd Schubert
1 sibling, 1 reply; 6+ messages in thread
From: Xiang Mei @ 2026-07-07 18:44 UTC (permalink / raw)
To: Joanne Koong, djwong, Bernd Schubert, Miklos Szeredi, Kees Cook,
Gustavo A . R . Silva
Cc: stable, fuse-devel, linux-kernel, Pavel Begunkov, Luis Henriques,
Weiming Shi, Xiang Mei
fuse_uring_copy_from_ring() imports the payload buffer with length
ring->max_payload_sz but passes the server-controlled payload_sz to
fuse_copy_out_args() unchecked. A larger payload_sz drains the iterator
to exhaustion and fuse_copy_fill() hits BUG_ON(!err), panicking the
kernel. Reject replies whose payload_sz exceeds the imported buffer.
kernel BUG at fs/fuse/dev.c:1053!
RIP: 0010:fuse_copy_fill+0x6c6/0x7e0
Call Trace:
fuse_copy_args
fuse_uring_copy_from_ring fs/fuse/dev_uring.c:686
fuse_uring_cmd
io_uring_cmd
__io_issue_sqe
io_submit_sqes
__do_sys_io_uring_enter
entry_SYSCALL_64_after_hwframe
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>
---
v2: add: Cc stable and Reviewed-by tags
fs/fuse/dev_uring.c | 3 +++
1 file changed, 3 insertions(+)
diff --git a/fs/fuse/dev_uring.c b/fs/fuse/dev_uring.c
index 0814681eb04b..f6127c230dd9 100644
--- a/fs/fuse/dev_uring.c
+++ b/fs/fuse/dev_uring.c
@@ -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 > ring->max_payload_sz)
+ return -EINVAL;
+
err = setup_fuse_copy_state(&cs, ring, req, ent, ITER_SOURCE, &iter);
if (err)
return err;
--
2.43.0
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH v2 2/2] fuse: reject oversized payload_sz in fuse_uring_copy_from_ring()
2026-07-07 18:44 ` [PATCH v2 2/2] fuse: reject oversized payload_sz in fuse_uring_copy_from_ring() Xiang Mei
@ 2026-07-08 19:22 ` Bernd Schubert
2026-07-08 23:08 ` Joanne Koong
0 siblings, 1 reply; 6+ messages in thread
From: Bernd Schubert @ 2026-07-08 19:22 UTC (permalink / raw)
To: Xiang Mei, Joanne Koong, djwong, Miklos Szeredi, Kees Cook,
Gustavo A . R . Silva
Cc: stable, fuse-devel, linux-kernel, Pavel Begunkov, Luis Henriques,
Weiming Shi
Hi Xiang,
On 7/7/26 20:44, Xiang Mei wrote:
> fuse_uring_copy_from_ring() imports the payload buffer with length
> ring->max_payload_sz but passes the server-controlled payload_sz to
> fuse_copy_out_args() unchecked. A larger payload_sz drains the iterator
> to exhaustion and fuse_copy_fill() hits BUG_ON(!err), panicking the
> kernel. Reject replies whose payload_sz exceeds the imported buffer.
>
> kernel BUG at fs/fuse/dev.c:1053!
> RIP: 0010:fuse_copy_fill+0x6c6/0x7e0
> Call Trace:
> fuse_copy_args
> fuse_uring_copy_from_ring fs/fuse/dev_uring.c:686
> fuse_uring_cmd
> io_uring_cmd
> __io_issue_sqe
> io_submit_sqes
> __do_sys_io_uring_enter
> entry_SYSCALL_64_after_hwframe
>
> 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>
> ---
> v2: add: Cc stable and Reviewed-by tags
>
> fs/fuse/dev_uring.c | 3 +++
> 1 file changed, 3 insertions(+)
>
> diff --git a/fs/fuse/dev_uring.c b/fs/fuse/dev_uring.c
> index 0814681eb04b..f6127c230dd9 100644
> --- a/fs/fuse/dev_uring.c
> +++ b/fs/fuse/dev_uring.c
> @@ -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 > ring->max_payload_sz)
> + return -EINVAL;
> +
> err = setup_fuse_copy_state(&cs, ring, req, ent, ITER_SOURCE, &iter);
> if (err)
> return err;
Good catch and sorry for lare review! Hrmm, it just gives me a bit headache,
because idea for max_payload_size in fuse_uring_create() that it prevents
exactly that.
After tracing through the code, I think we have two cases where max_payload calculation
in fuse_uring_create() is not enough for xattr and ioctl
For xattr we have an additional in addition to the patch above - it sends unchecked
against max_pages and fuse_dev_do_read() has an additional op code protection
that I had missed
/* 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;
fuse_request_end(req);
goto restart;
}
And I think with the current patch is incomplete and missing something like this
diff --git a/fs/fuse/dev_uring.c b/fs/fuse/dev_uring.c
index 77c8cec43d9c..449b84ac24e7 100644
--- a/fs/fuse/dev_uring.c
+++ b/fs/fuse/dev_uring.c
@@ -725,6 +725,14 @@ static int fuse_uring_args_to_ring(struct fuse_ring *ring, struct fuse_req *req,
num_args--;
}
+ /*
+ * A FUSE_SETXATTR value may exceed the ring buffer; match
+ * fuse_dev_do_read() instead of overrunning the payload iterator.
+ */
+ if (fuse_len_args(num_args, (struct fuse_arg *)in_args) >
+ ring->max_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);
And a generic patch, but that has the potential to break existing userspace is
diff --git a/fs/fuse/dev_uring.c b/fs/fuse/dev_uring.c
index 449b84ac24e7..d25d7922bbdd 100644
--- a/fs/fuse/dev_uring.c
+++ b/fs/fuse/dev_uring.c
@@ -251,7 +251,14 @@ static struct fuse_ring *fuse_uring_create(struct fuse_chan *fch)
goto out_err;
max_payload_size = max(FUSE_MIN_READ_BUFFER, fch->max_write);
- max_payload_size = max(max_payload_size, fch->max_pages * PAGE_SIZE);
+ /*
+ * A max_pages-sized paged reply may be preceded by a fixed op reply
+ * header (e.g. FUSE_IOCTL); reserve a page of header room generically.
+ */
+ max_payload_size = max(max_payload_size,
+ fch->max_pages * PAGE_SIZE + PAGE_SIZE);
+ /* getxattr/listxattr values are bounded only by XATTR_SIZE_MAX */
+ max_payload_size = max(max_payload_size, (size_t)XATTR_SIZE_MAX);
spin_lock(&fch->lock);
if (!fch->connected) {
Question is how we could add it in, maybe with a feature flag?
Thanks,
Bernd
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH v2 1/2] fuse: copy request headers via a stack buffer for io-uring
2026-07-07 18:44 [PATCH v2 1/2] fuse: copy request headers via a stack buffer for io-uring Xiang Mei
2026-07-07 18:44 ` [PATCH v2 2/2] fuse: reject oversized payload_sz in fuse_uring_copy_from_ring() Xiang Mei
@ 2026-07-08 20:21 ` Bernd Schubert
1 sibling, 0 replies; 6+ messages in thread
From: Bernd Schubert @ 2026-07-08 20:21 UTC (permalink / raw)
To: Xiang Mei, Joanne Koong, djwong, Miklos Szeredi, Kees Cook,
Gustavo A . R . Silva
Cc: stable, fuse-devel, linux-kernel, Pavel Begunkov, Luis Henriques,
Weiming Shi
On 7/7/26 20:44, 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!
> RIP: 0010:usercopy_abort+0x6c/0x80
> Call Trace:
> __check_heap_object
> __check_object_size
> copy_header_to_ring fs/fuse/dev_uring.c:618
> fuse_uring_prepare_send
> fuse_uring_send_in_task
> ...
> __do_sys_io_uring_enter
> entry_SYSCALL_64_after_hwframe
>
> Bounce both headers through an on-stack copy so the usercopy touches
> stack memory, not the slab object.
>
> 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>
> ---
> v2: add: Cc stable and Reviewed-by tags
>
> fs/fuse/dev_uring.c | 12 ++++++++----
> 1 file changed, 8 insertions(+), 4 deletions(-)
>
> diff --git a/fs/fuse/dev_uring.c b/fs/fuse/dev_uring.c
> index 77c8cec43d9c..0814681eb04b 100644
> --- a/fs/fuse/dev_uring.c
> +++ b/fs/fuse/dev_uring.c
> @@ -744,6 +744,7 @@ static int fuse_uring_copy_to_ring(struct fuse_ring_ent *ent,
> {
> struct fuse_ring_queue *queue = ent->queue;
> struct fuse_ring *ring = queue->ring;
> + struct fuse_in_header in_header;
> int err;
>
> err = -EIO;
> @@ -765,8 +766,9 @@ static int fuse_uring_copy_to_ring(struct fuse_ring_ent *ent,
> }
>
> /* copy fuse_in_header */
> - return copy_header_to_ring(ent, FUSE_URING_HEADER_IN_OUT, &req->in.h,
> - sizeof(req->in.h));
> + in_header = req->in.h;
> + return copy_header_to_ring(ent, FUSE_URING_HEADER_IN_OUT, &in_header,
> + sizeof(in_header));
> }
>
> static int fuse_uring_prepare_send(struct fuse_ring_ent *ent,
> @@ -871,11 +873,13 @@ static void fuse_uring_commit(struct fuse_ring_ent *ent, struct fuse_req *req,
> unsigned int issue_flags)
> {
> struct fuse_ring *ring = ent->queue->ring;
> + struct fuse_out_header out_header;
> ssize_t err = -EFAULT;
>
> - if (copy_header_from_ring(ent, FUSE_URING_HEADER_IN_OUT, &req->out.h,
> - sizeof(req->out.h)))
> + if (copy_header_from_ring(ent, FUSE_URING_HEADER_IN_OUT, &out_header,
> + sizeof(out_header)))
> goto out;
> + req->out.h = out_header;
>
> err = fuse_uring_out_header_has_err(&req->out.h, req);
> if (err) {
Reviewed-by: Bernd Schubert <bernd@bsbernd.com>
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH v2 2/2] fuse: reject oversized payload_sz in fuse_uring_copy_from_ring()
2026-07-08 19:22 ` Bernd Schubert
@ 2026-07-08 23:08 ` Joanne Koong
2026-07-09 21:16 ` Xiang Mei
0 siblings, 1 reply; 6+ messages in thread
From: Joanne Koong @ 2026-07-08 23:08 UTC (permalink / raw)
To: Bernd Schubert
Cc: Xiang Mei, djwong, Miklos Szeredi, Kees Cook,
Gustavo A . R . Silva, stable, fuse-devel, linux-kernel,
Pavel Begunkov, Luis Henriques, Weiming Shi
On Wed, Jul 8, 2026 at 12:22 PM Bernd Schubert <bernd@bsbernd.com> wrote:
>
> Hi Xiang,
>
> On 7/7/26 20:44, Xiang Mei wrote:
> > fuse_uring_copy_from_ring() imports the payload buffer with length
> > ring->max_payload_sz but passes the server-controlled payload_sz to
> > fuse_copy_out_args() unchecked. A larger payload_sz drains the iterator
> > to exhaustion and fuse_copy_fill() hits BUG_ON(!err), panicking the
> > kernel. Reject replies whose payload_sz exceeds the imported buffer.
> >
> > kernel BUG at fs/fuse/dev.c:1053!
> > RIP: 0010:fuse_copy_fill+0x6c6/0x7e0
> > Call Trace:
> > fuse_copy_args
> > fuse_uring_copy_from_ring fs/fuse/dev_uring.c:686
> > fuse_uring_cmd
> > io_uring_cmd
> > __io_issue_sqe
> > io_submit_sqes
> > __do_sys_io_uring_enter
> > entry_SYSCALL_64_after_hwframe
> >
> > 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>
> > ---
> > v2: add: Cc stable and Reviewed-by tags
> >
> > fs/fuse/dev_uring.c | 3 +++
> > 1 file changed, 3 insertions(+)
> >
> > diff --git a/fs/fuse/dev_uring.c b/fs/fuse/dev_uring.c
> > index 0814681eb04b..f6127c230dd9 100644
> > --- a/fs/fuse/dev_uring.c
> > +++ b/fs/fuse/dev_uring.c
> > @@ -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 > ring->max_payload_sz)
> > + return -EINVAL;
> > +
> > err = setup_fuse_copy_state(&cs, ring, req, ent, ITER_SOURCE, &iter);
> > if (err)
> > return err;
>
> Good catch and sorry for lare review! Hrmm, it just gives me a bit headache,
> because idea for max_payload_size in fuse_uring_create() that it prevents
> exactly that.
I don't think this is related to fuse_uring_create()'s
max_payload_size. The problem this fix addresses is that the
ring_ent_in_out.payload_sz header value returned by userspace can be
whatever arbitrary big value userspace wants to set.
Maybe I'm misunderstanding what you're saying, but i think this fix is
orthogonal to the setxattr issue you mention below? afaict, that one
has a different root cause (unbounded in_args copy coming from the
kernel side when sending requests vs. trying to copy in an unbounded
server-set payload size when handling a server's reply).
>
> After tracing through the code, I think we have two cases where max_payload calculation
> in fuse_uring_create() is not enough for xattr and ioctl
>
> For xattr we have an additional in addition to the patch above - it sends unchecked
> against max_pages and fuse_dev_do_read() has an additional op code protection
> that I had missed
Nice find, I didn't realize setxattr had a special error value either
>
> /* 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;
> fuse_request_end(req);
> goto restart;
> }
>
>
>
> And I think with the current patch is incomplete and missing something like this
>
> diff --git a/fs/fuse/dev_uring.c b/fs/fuse/dev_uring.c
> index 77c8cec43d9c..449b84ac24e7 100644
> --- a/fs/fuse/dev_uring.c
> +++ b/fs/fuse/dev_uring.c
> @@ -725,6 +725,14 @@ static int fuse_uring_args_to_ring(struct fuse_ring *ring, struct fuse_req *req,
> num_args--;
> }
>
> + /*
> + * A FUSE_SETXATTR value may exceed the ring buffer; match
> + * fuse_dev_do_read() instead of overrunning the payload iterator.
> + */
> + if (fuse_len_args(num_args, (struct fuse_arg *)in_args) >
> + ring->max_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);
>
>
>
> And a generic patch, but that has the potential to break existing userspace is
>
> diff --git a/fs/fuse/dev_uring.c b/fs/fuse/dev_uring.c
> index 449b84ac24e7..d25d7922bbdd 100644
> --- a/fs/fuse/dev_uring.c
> +++ b/fs/fuse/dev_uring.c
> @@ -251,7 +251,14 @@ static struct fuse_ring *fuse_uring_create(struct fuse_chan *fch)
> goto out_err;
>
> max_payload_size = max(FUSE_MIN_READ_BUFFER, fch->max_write);
> - max_payload_size = max(max_payload_size, fch->max_pages * PAGE_SIZE);
> + /*
> + * A max_pages-sized paged reply may be preceded by a fixed op reply
> + * header (e.g. FUSE_IOCTL); reserve a page of header room generically.
> + */
> + max_payload_size = max(max_payload_size,
> + fch->max_pages * PAGE_SIZE + PAGE_SIZE);
I hope we don't have to do this because imo even with a feature flag,
it gets confusing/cluttered. What about just having it be the
responsibility of userspace/libfuse to allocate a big enough buffer to
hold ioctl reply headers if they want to handle ioctls that have
max_payload_size amount of data? On the kernel side we'd just need to
store the buf_size value the user already passes in at registration
time and use that value for the import and payload-size bounds check
instead of max_payload_size which seems like a pretty minimal change.
If the kernel insists on the + PAGE_SIZE headroom, across lots of
queues that have lots of buffers each, I think that memory waste may
add up as well :(
I think with bufferpools being the primary interface going forward
(not sure if you agree with this, but with zero-copy and other
optimizations being gated on it, I view it as that in my mind), the
ioctl issue won't be a problem since the kernel can allocate more
memory than max_payload_size to that particular request from the pool.
Since they have alternative solutions (libfuse or buffer pools), maybe
we don't need to bake this in here?
> + /* getxattr/listxattr values are bounded only by XATTR_SIZE_MAX */
> + max_payload_size = max(max_payload_size, (size_t)XATTR_SIZE_MAX);
afaict XATTR_SIZE_MAX is 64k, so if a server deliberately sets max
pages to a very small value because they don't have enough memory, it
seems counter-intuitive to force all their buffers to be >= 64k. As I
understand it, the vast majority of xattrs are small (tens of bytes),
so maybe better to just let them proceed and return an error on an
oversized xattr than pay 64k/entry in every queue? If they really want
to support 64k xattrs, then imo it should be their responsibility to
allocate a big enough buffer size for that.
Thanks,
Joanne
>
> spin_lock(&fch->lock);
> if (!fch->connected) {
>
>
> Question is how we could add it in, maybe with a feature flag?
>
>
> Thanks,
> Bernd
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH v2 2/2] fuse: reject oversized payload_sz in fuse_uring_copy_from_ring()
2026-07-08 23:08 ` Joanne Koong
@ 2026-07-09 21:16 ` Xiang Mei
0 siblings, 0 replies; 6+ messages in thread
From: Xiang Mei @ 2026-07-09 21:16 UTC (permalink / raw)
To: Joanne Koong
Cc: Bernd Schubert, djwong, Miklos Szeredi, Kees Cook,
Gustavo A . R . Silva, stable, fuse-devel, linux-kernel,
Pavel Begunkov, Luis Henriques, Weiming Shi
On Wed, Jul 8, 2026 at 4:08 PM Joanne Koong <joannelkoong@gmail.com> wrote:
>
> On Wed, Jul 8, 2026 at 12:22 PM Bernd Schubert <bernd@bsbernd.com> wrote:
> >
> > Hi Xiang,
> >
> > On 7/7/26 20:44, Xiang Mei wrote:
> > > fuse_uring_copy_from_ring() imports the payload buffer with length
> > > ring->max_payload_sz but passes the server-controlled payload_sz to
> > > fuse_copy_out_args() unchecked. A larger payload_sz drains the iterator
> > > to exhaustion and fuse_copy_fill() hits BUG_ON(!err), panicking the
> > > kernel. Reject replies whose payload_sz exceeds the imported buffer.
> > >
> > > kernel BUG at fs/fuse/dev.c:1053!
> > > RIP: 0010:fuse_copy_fill+0x6c6/0x7e0
> > > Call Trace:
> > > fuse_copy_args
> > > fuse_uring_copy_from_ring fs/fuse/dev_uring.c:686
> > > fuse_uring_cmd
> > > io_uring_cmd
> > > __io_issue_sqe
> > > io_submit_sqes
> > > __do_sys_io_uring_enter
> > > entry_SYSCALL_64_after_hwframe
> > >
> > > 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>
> > > ---
> > > v2: add: Cc stable and Reviewed-by tags
> > >
> > > fs/fuse/dev_uring.c | 3 +++
> > > 1 file changed, 3 insertions(+)
> > >
> > > diff --git a/fs/fuse/dev_uring.c b/fs/fuse/dev_uring.c
> > > index 0814681eb04b..f6127c230dd9 100644
> > > --- a/fs/fuse/dev_uring.c
> > > +++ b/fs/fuse/dev_uring.c
> > > @@ -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 > ring->max_payload_sz)
> > > + return -EINVAL;
> > > +
> > > err = setup_fuse_copy_state(&cs, ring, req, ent, ITER_SOURCE, &iter);
> > > if (err)
> > > return err;
> >
> > Good catch and sorry for lare review! Hrmm, it just gives me a bit headache,
> > because idea for max_payload_size in fuse_uring_create() that it prevents
> > exactly that.
>
> I don't think this is related to fuse_uring_create()'s
> max_payload_size. The problem this fix addresses is that the
> ring_ent_in_out.payload_sz header value returned by userspace can be
> whatever arbitrary big value userspace wants to set.
>
> Maybe I'm misunderstanding what you're saying, but i think this fix is
> orthogonal to the setxattr issue you mention below? afaict, that one
> has a different root cause (unbounded in_args copy coming from the
> kernel side when sending requests vs. trying to copy in an unbounded
> server-set payload size when handling a server's reply).
>
> >
> > After tracing through the code, I think we have two cases where max_payload calculation
> > in fuse_uring_create() is not enough for xattr and ioctl
> >
> > For xattr we have an additional in addition to the patch above - it sends unchecked
> > against max_pages and fuse_dev_do_read() has an additional op code protection
> > that I had missed
>
> Nice find, I didn't realize setxattr had a special error value either
>
Good catch, that's a real issue, and we triggered that by modifying our PoC.
> >
> > /* 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;
> > fuse_request_end(req);
> > goto restart;
> > }
> >
> >
> >
> > And I think with the current patch is incomplete and missing something like this
> >
> > diff --git a/fs/fuse/dev_uring.c b/fs/fuse/dev_uring.c
> > index 77c8cec43d9c..449b84ac24e7 100644
> > --- a/fs/fuse/dev_uring.c
> > +++ b/fs/fuse/dev_uring.c
> > @@ -725,6 +725,14 @@ static int fuse_uring_args_to_ring(struct fuse_ring *ring, struct fuse_req *req,
> > num_args--;
> > }
> >
> > + /*
> > + * A FUSE_SETXATTR value may exceed the ring buffer; match
> > + * fuse_dev_do_read() instead of overrunning the payload iterator.
> > + */
> > + if (fuse_len_args(num_args, (struct fuse_arg *)in_args) >
> > + ring->max_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);
> >
> >
> >
> > And a generic patch, but that has the potential to break existing userspace is
> >
> > diff --git a/fs/fuse/dev_uring.c b/fs/fuse/dev_uring.c
> > index 449b84ac24e7..d25d7922bbdd 100644
> > --- a/fs/fuse/dev_uring.c
> > +++ b/fs/fuse/dev_uring.c
> > @@ -251,7 +251,14 @@ static struct fuse_ring *fuse_uring_create(struct fuse_chan *fch)
> > goto out_err;
> >
> > max_payload_size = max(FUSE_MIN_READ_BUFFER, fch->max_write);
> > - max_payload_size = max(max_payload_size, fch->max_pages * PAGE_SIZE);
> > + /*
> > + * A max_pages-sized paged reply may be preceded by a fixed op reply
> > + * header (e.g. FUSE_IOCTL); reserve a page of header room generically.
> > + */
> > + max_payload_size = max(max_payload_size,
> > + fch->max_pages * PAGE_SIZE + PAGE_SIZE);
>
> I hope we don't have to do this because imo even with a feature flag,
> it gets confusing/cluttered. What about just having it be the
> responsibility of userspace/libfuse to allocate a big enough buffer to
> hold ioctl reply headers if they want to handle ioctls that have
> max_payload_size amount of data? On the kernel side we'd just need to
> store the buf_size value the user already passes in at registration
> time and use that value for the import and payload-size bounds check
> instead of max_payload_size which seems like a pretty minimal change.
> If the kernel insists on the + PAGE_SIZE headroom, across lots of
> queues that have lots of buffers each, I think that memory waste may
> add up as well :(
>
> I think with bufferpools being the primary interface going forward
> (not sure if you agree with this, but with zero-copy and other
> optimizations being gated on it, I view it as that in my mind), the
> ioctl issue won't be a problem since the kernel can allocate more
> memory than max_payload_size to that particular request from the pool.
>
> Since they have alternative solutions (libfuse or buffer pools), maybe
> we don't need to bake this in here?
>
>
> > + /* getxattr/listxattr values are bounded only by XATTR_SIZE_MAX */
> > + max_payload_size = max(max_payload_size, (size_t)XATTR_SIZE_MAX);
>
> afaict XATTR_SIZE_MAX is 64k, so if a server deliberately sets max
> pages to a very small value because they don't have enough memory, it
> seems counter-intuitive to force all their buffers to be >= 64k. As I
> understand it, the vast majority of xattrs are small (tens of bytes),
> so maybe better to just let them proceed and return an error on an
> oversized xattr than pay 64k/entry in every queue? If they really want
> to support 64k xattrs, then imo it should be their responsibility to
> allocate a big enough buffer size for that.
>
> Thanks,
> Joanne
This seems a good way to patch both paths. We have sent a v3 based on this.
v3-0001 keeps the same as v2.
v3-0002 patches both paths based on your proposal.
Please check, and we'd like to hear your feedback:
https://lore.kernel.org/fuse-devel/20260709211130.543773-2-xmei5@asu.edu/T/#u
Thanks,
Xiang
>
> >
> > spin_lock(&fch->lock);
> > if (!fch->connected) {
> >
> >
> > Question is how we could add it in, maybe with a feature flag?
> >
> >
> > Thanks,
> > Bernd
^ permalink raw reply [flat|nested] 6+ messages in thread
end of thread, other threads:[~2026-07-09 21:16 UTC | newest]
Thread overview: 6+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-07-07 18:44 [PATCH v2 1/2] fuse: copy request headers via a stack buffer for io-uring Xiang Mei
2026-07-07 18:44 ` [PATCH v2 2/2] fuse: reject oversized payload_sz in fuse_uring_copy_from_ring() Xiang Mei
2026-07-08 19:22 ` Bernd Schubert
2026-07-08 23:08 ` Joanne Koong
2026-07-09 21:16 ` Xiang Mei
2026-07-08 20:21 ` [PATCH v2 1/2] fuse: copy request headers via a stack buffer for io-uring Bernd Schubert
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox