[PATCH] usb: cdns2: fix use-after-free in cdns2_gadget_giveback

From: 齐柯宇

Date: Wed Jan 14 2026 - 15:32:32 EST


This fix addresses a use-after-free vulnerability discovered through
static code analysis of the cdns2_gadget_giveback() function.

The vulnerability exists because after usb_gadget_giveback_request()
is called, the code continues to access request->buf. However,
usb_gadget_giveback_request() invokes the request's complete callback,
and certain gadget function drivers (such as FunctionFS with DMABUF)
may directly free the request within this callback.

Call flow leading to use-after-free:

cdns2_gadget_giveback()
-> usb_gadget_giveback_request()
-> request->complete() [e.g., ffs_epfile_dmabuf_io_complete]
-> usb_ep_free_request() // request is freed here
-> if (request->buf == pdev->zlp_buf) // UAF: accessing freed memory
-> cdns2_gadget_ep_free_request() // potential double-free

Data flow analysis shows that this vulnerability can be triggered when:
1. A user application uses FunctionFS with DMABUF transfer capability
2. The user attaches a DMABUF via FUNCTIONFS_DMABUF_ATTACH ioctl
3. The user initiates a transfer via FUNCTIONFS_DMABUF_TRANSFER ioctl
4. Upon transfer completion, ffs_epfile_dmabuf_io_complete() is called
as the complete callback, which frees the request
5. cdns2_gadget_giveback() then accesses the freed request->buf field

Evidence that complete callback can free the request (f_fs.c):

static void ffs_epfile_dmabuf_io_complete(struct usb_ep *ep,
struct usb_request *req)
{
ffs_dmabuf_signal_done(req->context, req->status);
usb_ep_free_request(ep, req); // frees the request directly
}

The fix saves the ZLP check result before calling the complete callback
and uses mutually exclusive logic: requests with complete callbacks are
owned by the gadget function driver, while only ZLP requests without
complete callbacks are freed by the UDC driver.

Fixes: 3eb1f1efe204 ("usb: cdns2: Add main part of Cadence USBHS driver")
Signed-off-by: Kery Qi <qikeyu2017@xxxxxxxxx>
---
drivers/usb/gadget/udc/cdns2/cdns2-gadget.c | 11 ++++++++---
1 file changed, 8 insertions(+), 3 deletions(-)

diff --git a/drivers/usb/gadget/udc/cdns2/cdns2-gadget.c
b/drivers/usb/gadget/udc/cdns2/cdns2-gadget.c
index 9b53daf76583..8997623cca5a 100644
--- a/drivers/usb/gadget/udc/cdns2/cdns2-gadget.c
+++ b/drivers/usb/gadget/udc/cdns2/cdns2-gadget.c
@@ -240,6 +240,7 @@ void cdns2_gadget_giveback(struct cdns2_endpoint *pep,
{
struct usb_request *request = &preq->request;
struct cdns2_device *pdev = pep->pdev;
+ bool is_zlp = (request->buf == pdev->zlp_buf);

list_del_init(&preq->list);

@@ -257,10 +258,14 @@ void cdns2_gadget_giveback(struct cdns2_endpoint *pep,
spin_unlock(&pdev->lock);
usb_gadget_giveback_request(&pep->endpoint, request);
spin_lock(&pdev->lock);
- }
-
- if (request->buf == pdev->zlp_buf)
+ } else if (is_zlp) {
+ /*
+ * Only ZLP requests without a complete callback are freed
+ * by the driver. Requests with complete callbacks are
+ * owned by the gadget function driver.
+ */
cdns2_gadget_ep_free_request(&pep->endpoint, request);
+ }
}

static void cdns2_wa1_restore_cycle_bit(struct cdns2_endpoint *pep)
--
2.34.1