Re: [PATCH v2 2/2] fuse: reject oversized payload_sz in fuse_uring_copy_from_ring()

Xiang Mei <[email protected]> Thu, 9 Jul 2026 14:16:07 -0700
Newsgroups dev.linux.lists.fuse-devel,org.kernel.vger.linux-kernel,org.kernel.vger.stable
Message-ID <CAPpSM+TXbaA0YfaxoKjNhxxA3V4oqY3KYJuJpqDr4rSo+z8W4g@mail.gmail.com>
On Wed, Jul 8, 2026 at 4:08 PM Joanne Koong <[email protected]> wrote:
>
> On Wed, Jul 8, 2026 at 12:22 PM Bernd Schubert <[email protected]> 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: [email protected]
> > > Reported-by: Weiming Shi <[email protected]>
> > > Assisted-by: Claude:claude-opus-4-8
> > > Signed-off-by: Xiang Mei <[email protected]>
> > > Reviewed-by: Joanne Koong <[email protected]>
> > > ---
> > > 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/[email protected]/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