]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
usb: gadget: udc: bdc: free IRQ and drain func_wake_notify before teardown
authorFan Wu <fanwu01@zju.edu.cn>
Thu, 9 Jul 2026 02:09:04 +0000 (02:09 +0000)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Mon, 13 Jul 2026 05:10:11 +0000 (07:10 +0200)
The Broadcom BDC UDC driver registers its IRQ handler with
devm_request_irq() in bdc_udc_init(), so the IRQ is released by devm
only after bdc_remove() returns.  devm releases resources in reverse
LIFO order, but bdc_remove() runs bdc_udc_exit() and bdc_hw_exit() ->
bdc_mem_free() manually before returning: bdc_udc_exit() tears down
individual endpoint objects via bdc_free_ep(), while bdc_hw_exit() ->
bdc_mem_free() frees and NULLs the DMA-coherent status-report ring
(bdc->srr.sr_bds) and kfree()s bdc->bdc_ep_array.  Both happen while
the IRQ handler (bdc_udc_interrupt, requested with IRQF_SHARED)
remains deliverable in the window up to the post-remove devm
free_irq().

On receipt of a shared interrupt in that window, bdc_udc_interrupt()
dereferences bdc->srr.sr_bds[bdc->srr.dqp_index] (NULL or freed DMA)
and dispatches sr_handler callbacks that index into bdc_ep_array,
causing a NULL-deref or use-after-free.

The same window affects the delayed_work bdc->func_wake_notify, which is
armed from the IRQ handler via bdc_sr_uspc() -> handle_link_state_change()
-> schedule_delayed_work() and may self-rearm from its own callback
bdc_func_wake_timer().  No cancel exists anywhere in the driver, so a
queued work item that fires after bdc_remove() returns and the bdc
structure is devm-freed dereferences freed memory.

Replace devm_request_irq() with request_irq() and add an explicit
free_irq(bdc->irq, bdc) in bdc_remove().  Clear BDC_GIE before
free_irq() to stop the device from asserting interrupts, then
free_irq() drains any in-flight handler, then cancel_delayed_work_sync()
drains the func_wake_notify delayed work.  This ordering ensures the
IRQ handler and delayed work cannot interfere with the subsequent
endpoint and DMA teardown in bdc_udc_exit() and bdc_hw_exit().  Wire the
matching free_irq() into the bdc_udc_init() error path so the IRQ is
released on probe failure, and route the bdc_init_ep() failure through
err0 instead of returning directly.

This issue was found by an in-house static analysis tool.

Fixes: efed421a94e6 ("usb: gadget: Add UDC driver for Broadcom USB3.0 device controller IP BDC")
Cc: stable <stable@kernel.org>
Assisted-by: Codex:gpt-5.5
Signed-off-by: Fan Wu <fanwu01@zju.edu.cn>
Link: https://patch.msgid.link/20260709020904.502611-1-fanwu01@zju.edu.cn
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/usb/gadget/udc/bdc/bdc_core.c
drivers/usb/gadget/udc/bdc/bdc_udc.c

index 438201dc96ca7d8eec1ab3cff6e6a07574b60058..a8dbaef54abaef899a512ea700e81954f4e8a97a 100644 (file)
@@ -585,9 +585,29 @@ disable_clk:
 static void bdc_remove(struct platform_device *pdev)
 {
        struct bdc *bdc;
+       unsigned long flags;
+       u32 temp;
 
        bdc  = platform_get_drvdata(pdev);
        dev_dbg(bdc->dev, "%s ()\n", __func__);
+       /*
+        * Disable the device interrupt source before freeing the IRQ:
+        * clear BDC_GIE so the controller stops asserting interrupts,
+        * then free_irq drains any in-flight handler.
+        */
+       spin_lock_irqsave(&bdc->lock, flags);
+       temp = bdc_readl(bdc->regs, BDC_BDCSC);
+       temp &= ~BDC_GIE;
+       bdc_writel(bdc->regs, BDC_BDCSC, temp);
+       spin_unlock_irqrestore(&bdc->lock, flags);
+       free_irq(bdc->irq, bdc);
+       /*
+        * Drain func_wake_notify after free_irq: the IRQ handler arms this
+        * delayed_work via bdc_sr_uspc -> handle_link_state_change ->
+        * schedule_delayed_work (self-rearmed in bdc_func_wake_timer), so
+        * the IRQ must be released first to prevent re-arm after cancel.
+        */
+       cancel_delayed_work_sync(&bdc->func_wake_notify);
        bdc_udc_exit(bdc);
        bdc_hw_exit(bdc);
        bdc_phy_exit(bdc);
index 23826fd7a8e6938c053dc64e1468d95bbdba2161..7a12219edac6fda928f35649d99fa836699bea8d 100644 (file)
@@ -530,8 +530,8 @@ int bdc_udc_init(struct bdc *bdc)
 
 
        bdc->gadget.name = BRCM_BDC_NAME;
-       ret = devm_request_irq(bdc->dev, bdc->irq, bdc_udc_interrupt,
-                               IRQF_SHARED, BRCM_BDC_NAME, bdc);
+       ret = request_irq(bdc->irq, bdc_udc_interrupt, IRQF_SHARED,
+                         BRCM_BDC_NAME, bdc);
        if (ret) {
                dev_err(bdc->dev,
                        "failed to request irq #%d %d\n",
@@ -542,7 +542,7 @@ int bdc_udc_init(struct bdc *bdc)
        ret = bdc_init_ep(bdc);
        if (ret) {
                dev_err(bdc->dev, "bdc init ep fail: %d\n", ret);
-               return ret;
+               goto err0;
        }
 
        ret = usb_add_gadget_udc(bdc->dev, &bdc->gadget);
@@ -571,6 +571,7 @@ int bdc_udc_init(struct bdc *bdc)
 err1:
        usb_del_gadget_udc(&bdc->gadget);
 err0:
+       free_irq(bdc->irq, bdc);
        bdc_free_ep(bdc);
 
        return ret;