From: sashiko-bot@kernel.org
To: "Jing Lin" <kayano050408@gmail.com>
Cc: linux-usb@vger.kernel.org
Subject: Re: [PATCH v3 1/2] usb: gadget: gadgetfs: fix ep0 request lifetime on disconnect
Date: Sat, 07 Nov 2026 10:14:31 +0000 [thread overview]
Message-ID: <sashiko-outbox-202813@kernel.org> (raw)
In-Reply-To: <20261107-b4-gadgetfs-ep0-v3-1-62089dc0ec07@gmail.com>
Thank you for your contribution! Sashiko AI review found 2 potential
issues to consider:
- [High] usb: gadget: gadgetfs: unchecked reuse of dev->req clears
ep0_req_queued
- [High] usb: gadget: gadgetfs: race on dev->req->buf frees descriptor
in clean_req()
--
commit 6cf737aca44046a58d9c7ba5151c4864f9f42306
Author: Jing Lin <kayano050408@gmail.com>
usb: gadget: gadgetfs: fix ep0 request lifetime on disconnect
This commit buffers delegated control OUT payloads in a separate
setup_buf array so dev->req can be reused before ep0_read() consumes the
data, and tracks whether dev->req is queued so gadgetfs_unbind() can
dequeue it before freeing.
> diff --git a/drivers/usb/gadget/legacy/inode.c b/drivers/usb/gadget/legacy/inode.c
> index 7f6d8460152f9..87aff568aa596 100644
> --- a/drivers/usb/gadget/legacy/inode.c
> +++ b/drivers/usb/gadget/legacy/inode.c
[ ... ]
> @@ -984,12 +987,23 @@ static inline void ep0_readable (struct dev_data *dev)
> kill_fasync (&dev->fasync, SIGIO, POLL_IN);
> }
>
> +static void dev_req_complete(struct usb_ep *ep, struct usb_request *req)
> +{
> + struct dev_data *dev = ep->driver_data;
> + unsigned long flags;
> +
> + spin_lock_irqsave(&dev->lock, flags);
> + dev->ep0_req_queued = false;
> + spin_unlock_irqrestore(&dev->lock, flags);
> +}
> +
> static void clean_req (struct usb_ep *ep, struct usb_request *req)
> {
> struct dev_data *dev = ep->driver_data;
>
> if (req->buf != dev->rbuf) {
> - kfree(req->buf);
> + if (req->buf != dev->setup_buf)
> + kfree(req->buf);
> req->buf = dev->rbuf;
> }
> req->complete = epio_complete;
[ ... ]
> @@ -1074,10 +1093,15 @@ ep0_read (struct file *fd, char __user *buf, size_t len, loff_t *ptr)
>
> if ((retval = setup_req (ep, req, 0)) == 0) {
> ++dev->udc_usage;
> + dev->ep0_req_queued = true;
> spin_unlock_irq (&dev->lock);
> retval = usb_ep_queue (ep, req, GFP_KERNEL);
> spin_lock_irq (&dev->lock);
> --dev->udc_usage;
> + if (retval < 0) {
> + dev->ep0_req_queued = false;
> + clean_req(ep, req);
> + }
> }
> dev->state = STATE_DEV_CONNECTED;
>
[Severity: High]
Can reusing dev->req without checking dev->ep0_req_queued clear
dev->ep0_req_queued while dev->req is still queued on gadget->ep0?
When gadgetfs_setup() delegates a control OUT transfer with w_length > 0,
it queues dev->req on gadget->ep0, sets dev->ep0_req_queued = true, and
queues a GADGETFS_SETUP event. Once userspace reads that event (entering
STATE_DEV_SETUP with dev->setup_in == 0), a call to ep0_read() with
len == 0 enters this branch while the OUT data stage request is still in
flight, overwrites dev->req in setup_req(), and calls usb_ep_queue().
The UDC rejects the already-queued request with -EINVAL, and the error
path sets dev->ep0_req_queued = false even though dev->req remains on the
endpoint queue. The same false clearing happens in ep0_write() and
gadgetfs_setup() if a previous ep0 request is still queued when a new
transfer starts, or if a second ep0_read() / ep0_write() races while
dev->lock is dropped before dev->state is updated.
When gadgetfs_unbind() later runs, it sees dev->ep0_req_queued == false,
skips usb_ep_dequeue(), and frees the active request, triggering the
WARN_ON(!list_empty(&req->queue)) in dummy_free_request() and a
use-after-free when dummy_udc_stop() drains the queue.
[ ... ]
> @@ -1271,6 +1300,7 @@ ep0_write (struct file *fd, const char __user *buf, size_t len, loff_t *ptr)
> if (retval == 0) {
> dev->state = STATE_DEV_CONNECTED;
> ++dev->udc_usage;
> + dev->ep0_req_queued = true;
> spin_unlock_irq (&dev->lock);
> if (copy_from_user (dev->req->buf, buf, len))
> retval = -EFAULT;
> @@ -1284,6 +1314,7 @@ ep0_write (struct file *fd, const char __user *buf, size_t len, loff_t *ptr)
> spin_lock_irq(&dev->lock);
> --dev->udc_usage;
> if (retval < 0) {
> + dev->ep0_req_queued = false;
> clean_req (dev->gadget->ep0, dev->req);
> } else
> retval = len;
[Severity: High]
Can a concurrent USB_REQ_GET_DESCRIPTOR setup request while dev->lock is
dropped here cause copy_from_user() to overwrite descriptor memory or
clean_req() to kfree() it?
ep0_write() sets dev->state = STATE_DEV_CONNECTED and drops dev->lock
before copy_from_user() and usb_ep_queue(). If a USB_REQ_GET_DESCRIPTOR
request arrives in gadgetfs_setup() during that window, it sees
dev->state != STATE_DEV_SETUP and points dev->req->buf at dev->dev,
dev->config, or dev->hs_config (all inside dev->buf), and
dev_req_complete() leaves dev->req->buf pointing there:
CPU0 CPU1
ep0_write()
setup_req()
dev->req->buf = dev->rbuf
dev->state = STATE_DEV_CONNECTED
spin_unlock_irq(&dev->lock)
gadgetfs_setup()
case USB_DT_CONFIG:
config_buf()
dev->req->buf = dev->config
usb_ep_queue(..., dev->req, ...)
copy_from_user(dev->req->buf, ...)
--> overwrites dev->config or
returns -EFAULT
spin_lock_irq(&dev->lock)
clean_req()
kfree(req->buf) <-- frees dev->config (dev->buf)
If copy_from_user() succeeds, up to RBUF_SIZE (256) bytes are written into
the descriptor buffer, overflowing the 18-byte dev->dev at the end of
dev->buf or corrupting dev->config. If copy_from_user() or usb_ep_queue()
fails (here or in the new clean_req() error path in ep0_read()),
clean_req() sees req->buf is neither dev->rbuf nor dev->setup_buf and
calls kfree(req->buf), either freeing dev->buf while the gadget is still
active (leading to use-after-free and double free in dev_release()) or
passing an interior slab pointer (dev->hs_config or dev->dev) to kfree().
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261107-b4-gadgetfs-ep0-v3-1-62089dc0ec07@gmail.com?part=1
next prev parent reply other threads:[~2026-11-07 10:14 UTC|newest]
Thread overview: 5+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-07 9:56 [PATCH v3 0/2] usb: gadget: gadgetfs: fix ep0 request lifetime and event counter Jing Lin via B4 Relay
2026-11-07 9:56 ` [PATCH v3 1/2] usb: gadget: gadgetfs: fix ep0 request lifetime on disconnect Jing Lin via B4 Relay
2026-11-07 10:14 ` sashiko-bot [this message]
2026-11-07 9:56 ` [PATCH v3 2/2] usb: gadget: gadgetfs: prevent ep0 event counter underflow Jing Lin via B4 Relay
2026-11-07 10:16 ` sashiko-bot
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=sashiko-outbox-202813@kernel.org \
--to=sashiko-bot@kernel.org \
--cc=kayano050408@gmail.com \
--cc=linux-usb@vger.kernel.org \
--cc=sashiko-reviews@lists.linux.dev \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox