drivers/usb/renesas_usbhs/common.c | 4 ++++ 1 file changed, 4 insertions(+)
From: Fan Wu <fanwu01@zju.edu.cn>
In usbhs_remove(), the pipe array info->pipe is freed by calling
usbhs_pipe_remove(priv). However, the interrupt handler usbhs_interrupt()
is registered using devm_request_irq(). The devres cleanup, which
includes freeing the IRQ, happens after usbhs_remove() returns.
If a hardware interrupt fires or a pending ISR executes after
usbhs_pipe_remove() but before devres cleanup, the ISR will access the
freed info->pipe array via the usbhs_for_each_pipe_with_dcp() macro,
leading to a slab-use-after-free.
The race window can be illustrated as:
CPU 0 (remove path) CPU 1 (ISR context)
--------------------- ---------------------
usbhs_remove()
usbhs_pipe_remove()
kfree(info->pipe) <--+
|
| usbhs_interrupt()
| usbhsf_irq_empty()
| usbhs_for_each_pipe_with_dcp()
| access pipe->pipe_type <-- UAF!
...
return
(devres cleanup frees IRQ too late)
Fix this by explicitly calling disable_irq() before freeing the pipe
array in usbhs_remove(), ensuring that no ISR can run and access the
freed memory.
KASAN report:
BUG: KASAN: slab-use-after-free in usbhsf_irq_empty+0x1c4/0x210 [renesas_usbhs]
Read of size 4 at addr ffff88800d3d3d00 by task swapper/0/0
Call Trace:
<IRQ>
dump_stack_lvl+0x68/0xa0
kasan_report+0xca/0x100
usbhs_for_each_pipe_with_dcp+... [renesas_usbhs]
usbhsf_irq_empty+... [renesas_usbhs]
usbhs_interrupt+... [renesas_usbhs]
</IRQ>
Allocated by task 70:
kcalloc+0x52/0xb0
usbhs_pipe_probe+0xed/0x4f0 [renesas_usbhs]
usbhs_probe+0xa3e/0xf40 [renesas_usbhs]
...
Freed by task 70:
kfree+0x12c/0x350
usbhs_pipe_remove+0x41/0x60 [renesas_usbhs]
usbhs_remove+0x1a1/0x1f0 [renesas_usbhs]
device_release_driver_internal+0x371/0x540
...
Fixes: f1407d5c6624 ("usb: renesas_usbhs: Add Renesas USBHS common code")
Signed-off-by: Fan Wu <fanwu01@zju.edu.cn>
---
Note for reviewers:
Due to the difficulty of precisely controlling hardware interrupt timing
for physical hot-unplug, I verified this race condition dynamically.
I wrote a software harness that injects a delay in usbhs_remove() and
simulates the ISR accessing the pipe array via a high-frequency timer
in QEMU. This reliably triggers the KASAN Use-After-Free trace shown
in the commit message, confirming the vulnerability.
drivers/usb/renesas_usbhs/common.c | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/drivers/usb/renesas_usbhs/common.c b/drivers/usb/renesas_usbhs/common.c
index cf4a0367d..404220ceb 100644
--- a/drivers/usb/renesas_usbhs/common.c
+++ b/drivers/usb/renesas_usbhs/common.c
@@ -815,6 +815,10 @@ static void usbhs_remove(struct platform_device *pdev)
usbhs_platform_call(priv, hardware_exit, pdev);
reset_control_assert(priv->rsts);
+
+ /* Disable IRQ before freeing resources to prevent UAF in ISR */
+ disable_irq(priv->irq);
+
usbhs_mod_remove(priv);
usbhs_fifo_remove(priv);
usbhs_pipe_remove(priv);
--
2.34.1
On Tue, Mar 03, 2026 at 12:45:50AM +0000, UAF Researcher wrote: > From: Fan Wu <fanwu01@zju.edu.cn> > > In usbhs_remove(), the pipe array info->pipe is freed by calling > usbhs_pipe_remove(priv). However, the interrupt handler usbhs_interrupt() > is registered using devm_request_irq(). The devres cleanup, which > includes freeing the IRQ, happens after usbhs_remove() returns. > > If a hardware interrupt fires or a pending ISR executes after > usbhs_pipe_remove() but before devres cleanup, the ISR will access the > freed info->pipe array via the usbhs_for_each_pipe_with_dcp() macro, > leading to a slab-use-after-free. ... > drivers/usb/renesas_usbhs/common.c | 4 ++++ > 1 file changed, 4 insertions(+) > > diff --git a/drivers/usb/renesas_usbhs/common.c b/drivers/usb/renesas_usbhs/common.c > index cf4a0367d..404220ceb 100644 > --- a/drivers/usb/renesas_usbhs/common.c > +++ b/drivers/usb/renesas_usbhs/common.c > @@ -815,6 +815,10 @@ static void usbhs_remove(struct platform_device *pdev) > > usbhs_platform_call(priv, hardware_exit, pdev); > reset_control_assert(priv->rsts); > + > + /* Disable IRQ before freeing resources to prevent UAF in ISR */ > + disable_irq(priv->irq); Don't you also need to call synchronize_irq(priv->irq) here? Otherwise there still could be pending interrupt requests that will be handled later, causing the use-after-free problem. Alan Stern
In usbhs_remove(), the driver frees resources (including the pipe array)
while the interrupt handler (usbhs_interrupt) is still registered. If an
interrupt fires after usbhs_pipe_remove() but before the driver is fully
unbound, the ISR may access freed memory, causing a use-after-free.
Fix this by calling devm_free_irq() before freeing resources. This ensures
the interrupt handler is both disabled and synchronized (waits for any
running ISR to complete) before usbhs_pipe_remove() is called.
Fixes: f1407d5c6624 ("usb: renesas_usbhs: Add Renesas USBHS common code")
Suggested-by: Alan Stern <stern@rowland.harvard.edu>
Signed-off-by: Fan Wu <fanwu01@zju.edu.cn>
---
v2:
- Replace disable_irq() with devm_free_irq() as suggested by
Alan Stern. While disable_irq() only masks the interrupt line,
devm_free_irq() (via free_irq()) implicitly calls synchronize_irq()
to wait for any in-progress handler to complete, and also ensures
the interrupt is fully unregistered. This prevents both future
interrupts and pending handlers from accessing freed resources.
- Verified the fix in a QEMU environment with KASAN enabled.
By injecting a delay in usbhs_remove() and using a high-frequency
timer to simulate concurrent ISR execution, the original UAF
report was reliably reproduced and is now confirmed to be
eliminated with this patch.
drivers/usb/renesas_usbhs/common.c | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/drivers/usb/renesas_usbhs/common.c b/drivers/usb/renesas_usbhs/common.c
index cf4a0367d6d6..8c93bde4b816 100644
--- a/drivers/usb/renesas_usbhs/common.c
+++ b/drivers/usb/renesas_usbhs/common.c
@@ -815,6 +815,15 @@ static void usbhs_remove(struct platform_device *pdev)
usbhs_platform_call(priv, hardware_exit, pdev);
reset_control_assert(priv->rsts);
+
+ /*
+ * Explicitly free the IRQ to ensure the interrupt handler is
+ * disabled and synchronized before freeing resources.
+ * devm_free_irq() calls free_irq() which waits for any running
+ * ISR to complete, preventing UAF.
+ */
+ devm_free_irq(&pdev->dev, priv->irq, priv);
+
usbhs_mod_remove(priv);
usbhs_fifo_remove(priv);
usbhs_pipe_remove(priv);
--
2.34.1
© 2016 - 2026 Red Hat, Inc.