mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH] usb: typec: tcpci: free IRQ before unregistering TCPM port
@ 2026-11-04  9:51 Yaohan Li
  2026-11-06  2:16 ` Xu Yang
  0 siblings, 1 reply; 4+ messages in thread
From: Yaohan Li @ 2026-11-04  9:51 UTC (permalink / raw)
  To: badhri, heikki.krogerus, gregkh; +Cc: linux, linux-usb, linux-kernel

tcpci_remove() unregisters the TCPM port before the managed threaded
IRQ has been released. The IRQ handler can still queue work on the
TCPM port worker after unregister has destroyed that worker, causing
a use-after-free.

Free and synchronize the managed IRQ before calling
tcpci_unregister_port(). This ensures no new alert callback can
queue work after the TCPM worker has been torn down.

A KASAN test using a simulated I2C TCPCI device reproduced the
race while unregistering the port. The corresponding teardown
sequence produced no report after the change.

Fixes: ae8a2ca8a2215 ("usb: typec: Group all TCPCI/TCPM code together")
Assisted-by: ZCode:GLM-4
Signed-off-by: Yaohan Li <shunsheng1218@gmail.com>
---
 drivers/usb/typec/tcpm/tcpci.c | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/drivers/usb/typec/tcpm/tcpci.c b/drivers/usb/typec/tcpm/tcpci.c
index 6717ac914c6a6..580dd5256d0a3 100644
--- a/drivers/usb/typec/tcpm/tcpci.c
+++ b/drivers/usb/typec/tcpm/tcpci.c
@@ -993,6 +993,15 @@ static void tcpci_remove(struct i2c_client *client)
 	if (err < 0)
 		dev_warn(&client->dev, "Failed to disable irqs (%pe)\n", ERR_PTR(err));
 
+	/*
+	 * The port's kthread worker is freed by tcpci_unregister_port()
+	 * below, and the threaded IRQ handler queues work on it without
+	 * any port-registered check. Free the IRQ (which also waits for
+	 * an in-flight handler) before the port is unregistered: the devm
+	 * cleanup would only run after .remove() has returned.
+	 */
+	devm_free_irq(&client->dev, client->irq, chip);
+
 	tcpci_unregister_port(chip->tcpci);
 }
 
-- 
2.34.1


^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: [PATCH] usb: typec: tcpci: free IRQ before unregistering TCPM port
  2026-11-04  9:51 [PATCH] usb: typec: tcpci: free IRQ before unregistering TCPM port Yaohan Li
@ 2026-11-06  2:16 ` Xu Yang
  2026-11-06  4:45   ` zhang san
  0 siblings, 1 reply; 4+ messages in thread
From: Xu Yang @ 2026-11-06  2:16 UTC (permalink / raw)
  To: Yaohan Li; +Cc: badhri, heikki.krogerus, gregkh, linux, linux-usb, linux-kernel

On Wed, Nov 04, 2026 at 05:51:32PM +0800, Yaohan Li wrote:
> tcpci_remove() unregisters the TCPM port before the managed threaded
> IRQ has been released. The IRQ handler can still queue work on the
> TCPM port worker after unregister has destroyed that worker, causing
> a use-after-free.

The ALERT_MASK is set to 0 and chip interrupts are masked before unregister
the port. I assume no IRQ should be fired again after port is unregisterd.

> 
> Free and synchronize the managed IRQ before calling
> tcpci_unregister_port(). This ensures no new alert callback can
> queue work after the TCPM worker has been torn down.
> 
> A KASAN test using a simulated I2C TCPCI device reproduced the
> race while unregistering the port. The corresponding teardown
> sequence produced no report after the change.

Can you describe how the race happens?

Thanks,
Xu Yang

> 
> Fixes: ae8a2ca8a2215 ("usb: typec: Group all TCPCI/TCPM code together")
> Assisted-by: ZCode:GLM-4
> Signed-off-by: Yaohan Li <shunsheng1218@gmail.com>
> ---
>  drivers/usb/typec/tcpm/tcpci.c | 9 +++++++++
>  1 file changed, 9 insertions(+)
> 
> diff --git a/drivers/usb/typec/tcpm/tcpci.c b/drivers/usb/typec/tcpm/tcpci.c
> index 6717ac914c6a6..580dd5256d0a3 100644
> --- a/drivers/usb/typec/tcpm/tcpci.c
> +++ b/drivers/usb/typec/tcpm/tcpci.c
> @@ -993,6 +993,15 @@ static void tcpci_remove(struct i2c_client *client)
>  	if (err < 0)
>  		dev_warn(&client->dev, "Failed to disable irqs (%pe)\n", ERR_PTR(err));
>  
> +	/*
> +	 * The port's kthread worker is freed by tcpci_unregister_port()
> +	 * below, and the threaded IRQ handler queues work on it without
> +	 * any port-registered check. Free the IRQ (which also waits for
> +	 * an in-flight handler) before the port is unregistered: the devm
> +	 * cleanup would only run after .remove() has returned.
> +	 */
> +	devm_free_irq(&client->dev, client->irq, chip);
> +
>  	tcpci_unregister_port(chip->tcpci);
>  }
>  
> -- 
> 2.34.1
> 

^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: [PATCH] usb: typec: tcpci: free IRQ before unregistering TCPM port
  2026-11-06  2:16 ` Xu Yang
@ 2026-11-06  4:45   ` zhang san
  2026-11-06  5:59     ` Xu Yang
  0 siblings, 1 reply; 4+ messages in thread
From: zhang san @ 2026-11-06  4:45 UTC (permalink / raw)
  To: Xu Yang; +Cc: badhri, heikki.krogerus, gregkh, linux, linux-usb, linux-kernel

Hi Xu,

Thanks for the review.

You are right that tcpci_remove() clears TCPC_ALERT_MASK before
unregistering the TCPM port. However, this prevents subsequent chip
alerts; it does not synchronize with a threaded IRQ handler that was
already woken or running before the mask write.

In particular, tcpci_irq() can call tcpm_cc_change(), which queues
event_work on port->wq. Meanwhile, tcpm_unregister_port() destroys that
worker. An in-flight IRQ thread can therefore reach
kthread_queue_work() after the worker has been destroyed.

We reproduced this ordering on a KASAN-enabled 7.4-rc1 kernel using a
simulated I2C TCPCI device. A kprobe paused the real threaded handler
immediately before kthread_queue_work() while another CPU unbound the
device.

Without the patch, TCPM worker teardown completed before the handler
resumed, and KASAN reported a slab-use-after-free in
kthread_queue_work().

With the patch, devm_free_irq() waited for the handler to finish before
tcpci_unregister_port() destroyed the worker, and the same test produced
no KASAN report.

This is an injection-assisted reproduction, not a measurement of how
often the race occurs on physical hardware. It demonstrates that
masking the chip alert does not by itself exclude an already in-flight
threaded handler.

I would be happy to provide further details of the interleaving or test
setup if useful.

Best regards,
Yaohan Li


Xu Yang <xu.yang_2@oss.nxp.com> 于2026年11月6日周五 10:19写道:
>
> On Wed, Nov 04, 2026 at 05:51:32PM +0800, Yaohan Li wrote:
> > tcpci_remove() unregisters the TCPM port before the managed threaded
> > IRQ has been released. The IRQ handler can still queue work on the
> > TCPM port worker after unregister has destroyed that worker, causing
> > a use-after-free.
>
> The ALERT_MASK is set to 0 and chip interrupts are masked before unregister
> the port. I assume no IRQ should be fired again after port is unregisterd.
>
> >
> > Free and synchronize the managed IRQ before calling
> > tcpci_unregister_port(). This ensures no new alert callback can
> > queue work after the TCPM worker has been torn down.
> >
> > A KASAN test using a simulated I2C TCPCI device reproduced the
> > race while unregistering the port. The corresponding teardown
> > sequence produced no report after the change.
>
> Can you describe how the race happens?
>
> Thanks,
> Xu Yang
>
> >
> > Fixes: ae8a2ca8a2215 ("usb: typec: Group all TCPCI/TCPM code together")
> > Assisted-by: ZCode:GLM-4
> > Signed-off-by: Yaohan Li <shunsheng1218@gmail.com>
> > ---
> >  drivers/usb/typec/tcpm/tcpci.c | 9 +++++++++
> >  1 file changed, 9 insertions(+)
> >
> > diff --git a/drivers/usb/typec/tcpm/tcpci.c b/drivers/usb/typec/tcpm/tcpci.c
> > index 6717ac914c6a6..580dd5256d0a3 100644
> > --- a/drivers/usb/typec/tcpm/tcpci.c
> > +++ b/drivers/usb/typec/tcpm/tcpci.c
> > @@ -993,6 +993,15 @@ static void tcpci_remove(struct i2c_client *client)
> >       if (err < 0)
> >               dev_warn(&client->dev, "Failed to disable irqs (%pe)\n", ERR_PTR(err));
> >
> > +     /*
> > +      * The port's kthread worker is freed by tcpci_unregister_port()
> > +      * below, and the threaded IRQ handler queues work on it without
> > +      * any port-registered check. Free the IRQ (which also waits for
> > +      * an in-flight handler) before the port is unregistered: the devm
> > +      * cleanup would only run after .remove() has returned.
> > +      */
> > +     devm_free_irq(&client->dev, client->irq, chip);
> > +
> >       tcpci_unregister_port(chip->tcpci);
> >  }
> >
> > --
> > 2.34.1
> >

^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: [PATCH] usb: typec: tcpci: free IRQ before unregistering TCPM port
  2026-11-06  4:45   ` zhang san
@ 2026-11-06  5:59     ` Xu Yang
  0 siblings, 0 replies; 4+ messages in thread
From: Xu Yang @ 2026-11-06  5:59 UTC (permalink / raw)
  To: zhang san; +Cc: badhri, heikki.krogerus, gregkh, linux, linux-usb, linux-kernel

On Fri, Nov 06, 2026 at 12:45:48PM +0800, zhang san wrote:
> Hi Xu,
> 
> Thanks for the review.
> 
> You are right that tcpci_remove() clears TCPC_ALERT_MASK before
> unregistering the TCPM port. However, this prevents subsequent chip
> alerts; it does not synchronize with a threaded IRQ handler that was
> already woken or running before the mask write.
> 
> In particular, tcpci_irq() can call tcpm_cc_change(), which queues
> event_work on port->wq. Meanwhile, tcpm_unregister_port() destroys that
> worker. An in-flight IRQ thread can therefore reach
> kthread_queue_work() after the worker has been destroyed.
> 
> We reproduced this ordering on a KASAN-enabled 7.4-rc1 kernel using a
> simulated I2C TCPCI device. A kprobe paused the real threaded handler
> immediately before kthread_queue_work() while another CPU unbound the
> device.
> 
> Without the patch, TCPM worker teardown completed before the handler
> resumed, and KASAN reported a slab-use-after-free in
> kthread_queue_work().
> 
> With the patch, devm_free_irq() waited for the handler to finish before
> tcpci_unregister_port() destroyed the worker, and the same test produced
> no KASAN report.
> 
> This is an injection-assisted reproduction, not a measurement of how
> often the race occurs on physical hardware. It demonstrates that
> masking the chip alert does not by itself exclude an already in-flight
> threaded handler.
> 
> I would be happy to provide further details of the interleaving or test
> setup if useful.

OK. 

I think synchronize_irq() would be more suitable for this case. Otherwise,
using devm_* would lose its purpose.

Thanks,
Xu Yang

^ permalink raw reply	[flat|nested] 4+ messages in thread

end of thread, other threads:[~2026-11-06  6:02 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-11-04  9:51 [PATCH] usb: typec: tcpci: free IRQ before unregistering TCPM port Yaohan Li
2026-11-06  2:16 ` Xu Yang
2026-11-06  4:45   ` zhang san
2026-11-06  5:59     ` Xu Yang

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®