From: sashiko-bot@kernel.org
To: "Jing Lin" <kayano050408@gmail.com>
Cc: linux-usb@vger.kernel.org
Subject: Re: [PATCH v5 1/3] usb: gadget: gadgetfs: fix ep0 request lifetime on disconnect
Date: Mon, 09 Nov 2026 06:16:38 +0000 [thread overview]
Message-ID: <sashiko-outbox-204492@kernel.org> (raw)
In-Reply-To: <20261109-b4-gadgetfs-ep0-v5-1-f16d0b648bc2@gmail.com>
Thank you for your contribution! Sashiko AI review found 4 potential
issues to consider:
- [High] usb: gadget: gadgetfs: invalid kfree of descriptors in
gadgetfs_unbind()
- [Medium] usb: gadget: gadgetfs: premature state change in ep0_write()
hides abort
- [Medium] usb: gadget: gadgetfs: incomplete setup_abort handling in
ep0_read()
- [Medium] usb: gadget: gadgetfs: setup_abort in ep0_read() clobbers
unbound state
--
commit 3d2d9c4891365d79ece2e8243c8a799fb412498e
Author: Jing Lin <kayano050408@gmail.com>
usb: gadget: gadgetfs: fix ep0 request lifetime on disconnect
This commit tracks whether the preallocated ep0 request is queued, stages
delegated control payloads through private buffers so copy_to_user() and
copy_from_user() do not race with gadgetfs_setup() on dev->req->buf, and
dequeues dev->req in gadgetfs_unbind() before freeing it.
> diff --git a/drivers/usb/gadget/legacy/inode.c b/drivers/usb/gadget/legacy/inode.c
> index 7f6d8460152f9..e9380538e0b98 100644
> --- a/drivers/usb/gadget/legacy/inode.c
> +++ b/drivers/usb/gadget/legacy/inode.c
[ ... ]
> @@ -1109,25 +1134,37 @@ ep0_read (struct file *fd, char __user *buf, size_t len, loff_t *ptr)
> if (retval)
> goto done;
>
> - if (dev->state != STATE_DEV_SETUP) {
> + if (dev->setup_abort) {
> + dev->setup_abort = 0;
> + dev->state = STATE_DEV_CONNECTED;
> + retval = -EIDRM;
> + goto done;
> + }
[Severity: Medium]
Can ep0_read() ever reach this dev->setup_abort check when a delegated
control OUT transfer is aborted before its data stage completes?
Immediately above this hunk, ep0_read() sleeps in:
drivers/usb/gadget/legacy/inode.c:ep0_read() {
...
retval = wait_event_interruptible (dev->wait,
dev->setup_out_ready != 0);
...
}
When a new SETUP packet aborts the pending OUT transfer in
gadgetfs_setup(), dev->setup_abort is set to 1 while dev->setup_out_ready
remains 0 (or is reset to 0 for a new delegated setup), so the wait
condition is still false and ep0_read() stays asleep instead of returning
-EIDRM. If the earlier OUT transfer had already set dev->setup_out_ready
before an in-kernel setup aborted it, this branch also returns -EIDRM
without clearing dev->setup_out_ready, dev->setup_out_error, or
dev->setup_out_len.
In addition, if the abort arrives before userspace calls ep0_read(),
ep0_write(), or ep0_poll(), the entry checks in those functions clear
dev->setup_abort = 0 without resetting dev->state from STATE_DEV_SETUP to
STATE_DEV_CONNECTED, so the next event read is still misdirected into the
control data stage path.
> + if (dev->state != STATE_DEV_SETUP ||
> + dev->setup_in || !dev->setup_out_ready) {
> retval = -ECANCELED;
> goto done;
> }
[Severity: Medium]
Does handling dev->setup_abort before checking dev->state overwrite
STATE_DEV_UNBOUND if gadgetfs_unbind() ran while ep0_read() was sleeping?
CPU0 CPU1
ep0_read()
spin_unlock_irq(&dev->lock)
wait_event_interruptible(...)
gadgetfs_setup()
dev->setup_abort = 1
...
ep0_complete()
dev->setup_out_ready = 1
ep0_readable(dev)
gadgetfs_unbind()
dev->state = STATE_DEV_UNBOUND
usb_ep_free_request(..., dev->req)
dev->req = NULL
gadget->ep0->driver_data = NULL
spin_lock_irq(&dev->lock)
if (dev->setup_abort)
dev->state = STATE_DEV_CONNECTED <-- clobbers STATE_DEV_UNBOUND
Because wait_event_interruptible() runs with dev->lock dropped and without
incrementing dev->udc_usage, gadgetfs_unbind() can finish tearing down
dev->req and gadget->ep0->driver_data before ep0_read() reacquires
dev->lock. Setting dev->state = STATE_DEV_CONNECTED before verifying that
dev->state is still STATE_DEV_SETUP allows subsequent ep0_read() and
ep0_write() calls to pass the dev->state <= STATE_DEV_OPENED check and
dereference NULL ep->driver_data and dev->req in setup_req().
[ ... ]
> @@ -1267,29 +1304,41 @@ ep0_write (struct file *fd, const char __user *buf, size_t len, loff_t *ptr)
>
> len = min_t(size_t, len, dev->setup_wLength);
> if (dev->setup_in) {
> - retval = setup_req (dev->gadget->ep0, dev->req, len);
> + u8 tmp[RBUF_SIZE];
> + bool zero = len < dev->setup_wLength;
> +
> + dev->state = STATE_DEV_CONNECTED;
> + spin_unlock_irq(&dev->lock);
> + if (copy_from_user(tmp, buf, len)) {
> + spin_lock_irq(&dev->lock);
> + return -EFAULT;
> + }
> + spin_lock_irq(&dev->lock);
> + if (dev->setup_abort) {
> + dev->setup_abort = 0;
> + return -EIDRM;
> + }
> + if (dev->state != STATE_DEV_CONNECTED)
> + return -ECANCELED;
> + retval = setup_req(dev->gadget->ep0, dev->req, len);
[Severity: Medium]
Does setting dev->state = STATE_DEV_CONNECTED before dropping dev->lock
prevent ep0_write() from detecting an abort during copy_from_user()?
CPU0 CPU1
ep0_write()
dev->state = STATE_DEV_CONNECTED
spin_unlock_irq(&dev->lock)
copy_from_user(tmp, buf, len)
gadgetfs_setup()
dev->setup_abort = 0
if (dev->state == STATE_DEV_SETUP)
dev->setup_abort = 1 <-- skipped
/* new delegated setup queued */
spin_lock_irq(&dev->lock)
if (dev->setup_abort) <-- false
if (dev->state != STATE_DEV_CONNECTED) <-- false
setup_req(dev->gadget->ep0, dev->req, len)
usb_ep_queue(dev->gadget->ep0, dev->req, ...) <-- queues stale response
gadgetfs_setup() and next_event() only set dev->setup_abort = 1 when
dev->state == STATE_DEV_SETUP, and gadgetfs_setup() clears
dev->setup_abort = 0 at entry. Because ep0_write() has already changed
dev->state to STATE_DEV_CONNECTED before sleeping in copy_from_user(), a
new SETUP packet arriving in that window leaves dev->setup_abort == 0 and
dev->state == STATE_DEV_CONNECTED, so ep0_write() queues the stale IN
payload onto ep0 for the new control transfer (and leaves dev->state as
STATE_DEV_CONNECTED even if copy_from_user() fails with -EFAULT).
[ ... ]
> @@ -1781,15 +1843,21 @@ gadgetfs_unbind (struct usb_gadget *gadget)
> usleep_range(1000, 2000);
> spin_lock_irq(&dev->lock);
> }
> + queued = dev->ep0_req_queued;
> spin_unlock_irq (&dev->lock);
>
> + if (dev->req) {
> + if (queued)
> + usb_ep_dequeue(gadget->ep0, dev->req);
> + clean_req(gadget->ep0, dev->req);
> + usb_ep_free_request(gadget->ep0, dev->req);
> + dev->req = NULL;
> + }
[Severity: High]
Can clean_req() here call kfree() on dev->config, dev->hs_config, or
dev->dev when an in-kernel GET_DESCRIPTOR request is active during unbind?
In gadgetfs_setup(), GET_DESCRIPTOR points dev->req->buf at dev->dev,
dev->hs_config, or dev->config (which all live inside the single dev->buf
allocation freed in dev_release()), while clean_req() frees any buffer
other than dev->rbuf:
drivers/usb/gadget/legacy/inode.c:clean_req() {
...
if (req->buf != dev->rbuf) {
kfree(req->buf);
req->buf = dev->rbuf;
}
...
}
If usb_ep_dequeue() on ep0 returns an error without invoking
dev_req_complete() (for example on UDCs whose ep0 does not support
dequeue, or when the controller is already completing the request on
another CPU) or completes asynchronously, dev->req->buf is not reset to
dev->rbuf before clean_req() runs. clean_req() then passes dev->config
(causing a double free when dev_release() frees dev->buf) or interior
pointers dev->hs_config / dev->dev to kfree(), and usb_ep_free_request()
frees dev->req while the UDC may still complete it.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261109-b4-gadgetfs-ep0-v5-0-f16d0b648bc2@gmail.com?part=1
next prev parent reply other threads:[~2026-11-09 6:16 UTC|newest]
Thread overview: 11+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-09 5:55 [PATCH v5 0/3] usb: gadget: gadgetfs: fix ep0 request lifetime and event counter Jing Lin
2026-11-09 5:55 ` Jing Lin via B4 Relay
2026-11-09 5:55 ` [PATCH v5 1/3] usb: gadget: gadgetfs: fix ep0 request lifetime on disconnect Jing Lin
2026-11-09 5:55 ` Jing Lin via B4 Relay
2026-11-09 6:16 ` sashiko-bot [this message]
2026-11-09 5:55 ` [PATCH v5 2/3] usb: gadget: gadgetfs: prevent ep0 event counter underflow Jing Lin
2026-11-09 5:55 ` Jing Lin via B4 Relay
2026-11-09 6:08 ` sashiko-bot
2026-11-09 5:55 ` [PATCH v5 3/3] usb: gadget: gadgetfs: avoid writing through the const setup pointer Jing Lin
2026-11-09 5:55 ` Jing Lin via B4 Relay
2026-11-09 6:00 ` 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-204492@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.