[PATCH] usb: renesas_usbhs: fix race between device remove and ISR

UAF Researcher posted 1 patch 1 month, 1 week ago
drivers/usb/renesas_usbhs/common.c | 4 ++++
1 file changed, 4 insertions(+)
[PATCH] usb: renesas_usbhs: fix race between device remove and ISR
Posted by UAF Researcher 1 month, 1 week ago
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
Re: [PATCH] usb: renesas_usbhs: fix race between device remove and ISR
Posted by Alan Stern 1 month, 1 week ago
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
[PATCH v2] usb: renesas_usbhs: fix use-after-free in ISR during device removal
Posted by Fan Wu 1 month, 1 week ago
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