mirror of
https://github.com/torvalds/linux.git
synced 2026-07-27 17:47:41 +02:00
usb: gadget: udc: bdc: free IRQ and drain func_wake_notify before teardown
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: efed421a94 ("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>
This commit is contained in:
parent
7aa7d4bf9d
commit
0583f2fbf8
|
|
@ -585,9 +585,29 @@ static int bdc_probe(struct platform_device *pdev)
|
|||
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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user