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: 8+ 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
2026-11-07 9:56 ` 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
2026-11-07 9:56 ` 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
2026-11-07 9:56 ` 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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.