mirror of
https://github.com/torvalds/linux.git
synced 2026-11-07 06:17:02 +01:00
usb: gadget: aspeed-vhub: cancel wake work on device removal
wake_work is armed from the gadget .wakeup callback to resume suspended
downstream ports, and it is never cancelled in ast_vhub_remove(), so a
work item queued before or during removal can run after devm has freed
vhub and its ports, a use-after-free.
Cancelling the work alone is not sufficient: usb_gadget_wakeup() takes
no lock and the unbind path never clears wakeup_en, so a remote-wakeup
request in flight on another CPU can re-arm the work after the cancel
and before vhub is freed.
ast_vhub_del_dev() already clears d->registered under vhub->lock before
unregistering the gadget. Refuse the wakeup in ast_vhub_udc_wakeup()
once d->registered is clear: the check and the schedule_work() are then
atomic against the unbind, so a wakeup that passed before the flag was
cleared is drained by the cancel_work_sync() after the del_dev loop,
and one that arrives later returns without arming. Also move INIT_WORK()
to the top of ast_vhub_probe(), since the probe error path reuses
ast_vhub_remove() and would otherwise cancel a never-initialized work
item.
This issue was found by an in-house static analysis tool.
Fixes: 7ecca2a408 ("usb/gadget: Add driver for Aspeed SoC virtual hub")
Cc: stable@kernel.org
Assisted-by: Codex:gpt-5.6
Co-developed-by: Song Li <songl@zju.edu.cn>
Signed-off-by: Song Li <songl@zju.edu.cn>
Signed-off-by: Fan Wu <fanwu01@zju.edu.cn>
Reviewed-by: Benjamin Herrenschmidt <benh@kernel.crashing.org>
Link: https://patch.msgid.link/20260908041740.613713-1-fanwu01@zju.edu.cn
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
This commit is contained in:
parent
abc36cbda2
commit
20388c8d1d
|
|
@ -267,6 +267,9 @@ static void ast_vhub_remove(struct platform_device *pdev)
|
|||
for (i = 0; i < vhub->max_ports; i++)
|
||||
ast_vhub_del_dev(&vhub->ports[i].dev);
|
||||
|
||||
/* Final drain; the worker takes vhub->lock, so stay outside of it */
|
||||
cancel_work_sync(&vhub->wake_work);
|
||||
|
||||
spin_lock_irqsave(&vhub->lock, flags);
|
||||
|
||||
/* Mask & ack all interrupts */
|
||||
|
|
@ -328,6 +331,7 @@ static int ast_vhub_probe(struct platform_device *pdev)
|
|||
return -ENOMEM;
|
||||
|
||||
spin_lock_init(&vhub->lock);
|
||||
INIT_WORK(&vhub->wake_work, ast_vhub_wake_work);
|
||||
vhub->pdev = pdev;
|
||||
vhub->port_irq_mask = GENMASK(VHUB_IRQ_DEV1_BIT + vhub->max_ports - 1,
|
||||
VHUB_IRQ_DEV1_BIT);
|
||||
|
|
|
|||
|
|
@ -280,7 +280,7 @@ static int ast_vhub_udc_wakeup(struct usb_gadget* gadget)
|
|||
int rc = -EINVAL;
|
||||
|
||||
spin_lock_irqsave(&d->vhub->lock, flags);
|
||||
if (!d->wakeup_en)
|
||||
if (!d->wakeup_en || !d->registered)
|
||||
goto err;
|
||||
|
||||
DDBG(d, "Device initiated wakeup\n");
|
||||
|
|
|
|||
|
|
@ -558,7 +558,7 @@ void ast_vhub_device_connect(struct ast_vhub *vhub,
|
|||
ast_vhub_send_host_wakeup(vhub);
|
||||
}
|
||||
|
||||
static void ast_vhub_wake_work(struct work_struct *work)
|
||||
void ast_vhub_wake_work(struct work_struct *work)
|
||||
{
|
||||
struct ast_vhub *vhub = container_of(work,
|
||||
struct ast_vhub,
|
||||
|
|
@ -588,6 +588,8 @@ static void ast_vhub_wake_work(struct work_struct *work)
|
|||
|
||||
void ast_vhub_hub_wake_all(struct ast_vhub *vhub)
|
||||
{
|
||||
lockdep_assert_held(&vhub->lock);
|
||||
|
||||
/*
|
||||
* A device is trying to wake the world, because this
|
||||
* can recurse into the device, we break the call chain
|
||||
|
|
@ -1076,7 +1078,6 @@ static int ast_vhub_init_desc(struct ast_vhub *vhub)
|
|||
int ast_vhub_init_hub(struct ast_vhub *vhub)
|
||||
{
|
||||
vhub->speed = USB_SPEED_UNKNOWN;
|
||||
INIT_WORK(&vhub->wake_work, ast_vhub_wake_work);
|
||||
|
||||
return ast_vhub_init_desc(vhub);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -547,6 +547,7 @@ void ast_vhub_hub_suspend(struct ast_vhub *vhub);
|
|||
void ast_vhub_hub_resume(struct ast_vhub *vhub);
|
||||
void ast_vhub_hub_reset(struct ast_vhub *vhub);
|
||||
void ast_vhub_hub_wake_all(struct ast_vhub *vhub);
|
||||
void ast_vhub_wake_work(struct work_struct *work);
|
||||
|
||||
/* dev.c */
|
||||
int ast_vhub_init_dev(struct ast_vhub *vhub, unsigned int idx);
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user