[PATCH] usb: gadget: f_fs: only dequeue ep0req when it is queued
From: Cole Munz
Date: Tue Sep 08 2026 - 13:02:20 EST
Commit ce405d561b02 ("usb: gadget: f_fs: Ensure ep0req is dequeued
before free_request") made functionfs_unbind() call usb_ep_dequeue()
on ep0req unconditionally before freeing it. That closed a real
use-after-free: a reader or writer parked in __ffs_ep0_queue_wait()
holds ffs->mutex and waits on ep0req_completion while the request is
in flight, and unbind has to force it to complete so that thread wakes
up, returns, and drops the mutex before the request is freed.
Problem is, unbind now dequeues every single time it runs, and the
parked-reader case is rare. Far more often ep0req already completed
on its own, or it was never queued in the first place because bind
failed before any control transfer reached us. dwc3 is not quiet
about the second case. dwc3_gadget_ep_dequeue() logs "request %p was
not queued to %s" whenever the request isn't sitting on one of the
endpoint's lists, so ordinary gadget teardown, and any boot where the
host never finished enumeration before the driver unbound, prints
that error right before the "failed to start ...: -19" that follows.
The fix is to track whether ep0req is actually queued on ep0, and
only call usb_ep_dequeue() in functionfs_unbind() when it is.
ep0req_queued gets set right before usb_ep_queue() in
__ffs_ep0_queue_wait(), cleared again if that call fails, and cleared
in ffs_ep0_complete() once the request comes back. The parked-reader
case still works the same way it always did: the flag stays true for
as long as the thread sits blocked in
wait_for_completion_interruptible(), so unbind still finds it queued
and still dequeues. In the other two cases the flag reads false and
unbind skips a call that was doing nothing anyway.
Fixes: ce405d561b02 ("usb: gadget: f_fs: Ensure ep0req is dequeued before free_request")
Assisted-by: LLM sparse
Signed-off-by: Cole Munz <Munzzyy1@xxxxxxxxx>
---
This is the f_fs-side fix Thinh suggested on the dwc3 thread rather than
changing dwc3_gadget_ep_dequeue()'s return, and it also quiets the boot
case that the dwc3 change did not:
https://lore.kernel.org/linux-usb/aptrIbp-p6RNGd9X@vbox/
Built for arm64, f_fs.o compiles with no new warnings, sparse clean,
checkpatch --strict reports nothing. I can't flash a gadget here, so the
runtime side is reasoned from source, not tested on hardware.
drivers/usb/gadget/function/f_fs.c | 11 ++++++++---
drivers/usb/gadget/function/u_fs.h | 1 +
2 files changed, 9 insertions(+), 3 deletions(-)
diff --git a/drivers/usb/gadget/function/f_fs.c b/drivers/usb/gadget/function/f_fs.c
index 43962e05eacf..a2308ad7d3db 100644
--- a/drivers/usb/gadget/function/f_fs.c
+++ b/drivers/usb/gadget/function/f_fs.c
@@ -307,6 +307,7 @@ static void ffs_ep0_complete(struct usb_ep *ep, struct usb_request *req)
{
struct ffs_data *ffs = req->context;
+ ffs->ep0req_queued = false;
complete(&ffs->ep0req_completion);
}
@@ -338,9 +339,12 @@ static int __ffs_ep0_queue_wait(struct ffs_data *ffs, char *data, size_t len)
reinit_completion(&ffs->ep0req_completion);
+ ffs->ep0req_queued = true;
ret = usb_ep_queue(ffs->gadget->ep0, req, GFP_ATOMIC);
- if (ret < 0)
+ if (ret < 0) {
+ ffs->ep0req_queued = false;
return ret;
+ }
ret = wait_for_completion_interruptible(&ffs->ep0req_completion);
if (ret) {
@@ -2389,8 +2393,9 @@ static int functionfs_bind(struct ffs_data *ffs, struct usb_composite_dev *cdev)
static void functionfs_unbind(struct ffs_data *ffs)
{
if (!WARN_ON(!ffs->gadget)) {
- /* dequeue before freeing ep0req */
- usb_ep_dequeue(ffs->gadget->ep0, ffs->ep0req);
+ /* dequeue before freeing ep0req, but only if it's actually queued */
+ if (ffs->ep0req_queued)
+ usb_ep_dequeue(ffs->gadget->ep0, ffs->ep0req);
mutex_lock(&ffs->mutex);
usb_ep_free_request(ffs->gadget->ep0, ffs->ep0req);
ffs->ep0req = NULL;
diff --git a/drivers/usb/gadget/function/u_fs.h b/drivers/usb/gadget/function/u_fs.h
index c280c495fbd2..be9cfc8f43a5 100644
--- a/drivers/usb/gadget/function/u_fs.h
+++ b/drivers/usb/gadget/function/u_fs.h
@@ -172,6 +172,7 @@ struct ffs_data {
*/
struct usb_request *ep0req; /* P: mutex */
struct completion ep0req_completion; /* P: mutex */
+ bool ep0req_queued; /* P: mutex */
/* reference counter */
refcount_t ref;
--
2.55.0