drivers/gpio/gpio-mpsse.c | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-)
gpio_mpsse_irq_enable() arms the poll worker before publishing it:
schedule_work() runs before the worker is added to priv->workers. If
gpio_mpsse_disconnect() walks the list in that window it misses the
worker, and once disconnect returns, the USB core frees mpsse_priv
while the orphaned gpio_mpsse_poll() work keeps accessing it, causing
a use-after-free.
Fix this by publishing and arming the worker in one irq_spin
critical section. Teardown walks the same list under irq_spin, so a
worker found on the list is guaranteed to be armed, and
cancel_work_sync() handles it whether it is queued or running.
A worker armed after the disconnect walk would still be missed, so
also set a new priv->dying flag under irq_spin before the teardown
walk, and check it in the same critical section, freeing the worker
instead when the device is going away. schedule_work() is safe to
call with irq_spin held, and the next probe gets a fresh mpsse_priv,
so the flag never needs to be cleared.
This issue was found by an in-house static analysis tool.
Fixes: 179ef1127d7a ("gpio: mpsse: ensure worker is torn down")
Cc: stable@vger.kernel.org
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>
---
drivers/gpio/gpio-mpsse.c | 14 ++++++++++++--
1 file changed, 12 insertions(+), 2 deletions(-)
diff --git a/drivers/gpio/gpio-mpsse.c b/drivers/gpio/gpio-mpsse.c
index 12191aeb6566..9efa7dfc9332 100644
--- a/drivers/gpio/gpio-mpsse.c
+++ b/drivers/gpio/gpio-mpsse.c
@@ -24,6 +24,7 @@
raw_spinlock_t irq_spin; /* protects worker list */
atomic_t irq_type[16]; /* pin -> edge detection type */
atomic_t irq_enabled;
+ atomic_t dying; /* no new workers after disconnect */
int id;
u8 gpio_outputs[2]; /* Output states for GPIOs [L, H] */
@@ -525,10 +526,16 @@
worker->priv = priv;
INIT_LIST_HEAD(&worker->list);
INIT_WORK(&worker->work, gpio_mpsse_poll);
- schedule_work(&worker->work);
- scoped_guard(raw_spinlock_irqsave, &priv->irq_spin)
+ scoped_guard(raw_spinlock_irqsave, &priv->irq_spin) {
+ if (atomic_read(&priv->dying)) {
+ kfree(worker);
+ return;
+ }
+
list_add(&worker->list, &priv->workers);
+ schedule_work(&worker->work);
+ }
}
}
@@ -715,6 +722,9 @@
{
struct mpsse_priv *priv = usb_get_intfdata(intf);
+ scoped_guard(raw_spinlock_irqsave, &priv->irq_spin)
+ atomic_set(&priv->dying, 1);
+
/*
* Lock prevents double-free of worker from here and the teardown
* step at the beginning of gpio_mpsse_poll
On Wed, 23 Sep 2026 05:08:55 +0200, Fan Wu <fanwu01@zju.edu.cn> said:
> gpio_mpsse_irq_enable() arms the poll worker before publishing it:
> schedule_work() runs before the worker is added to priv->workers. If
> gpio_mpsse_disconnect() walks the list in that window it misses the
> worker, and once disconnect returns, the USB core frees mpsse_priv
> while the orphaned gpio_mpsse_poll() work keeps accessing it, causing
> a use-after-free.
>
> Fix this by publishing and arming the worker in one irq_spin
> critical section. Teardown walks the same list under irq_spin, so a
> worker found on the list is guaranteed to be armed, and
> cancel_work_sync() handles it whether it is queued or running.
>
> A worker armed after the disconnect walk would still be missed, so
> also set a new priv->dying flag under irq_spin before the teardown
> walk, and check it in the same critical section, freeing the worker
> instead when the device is going away. schedule_work() is safe to
> call with irq_spin held, and the next probe gets a fresh mpsse_priv,
> so the flag never needs to be cleared.
>
> This issue was found by an in-house static analysis tool.
>
> Fixes: 179ef1127d7a ("gpio: mpsse: ensure worker is torn down")
> Cc: stable@vger.kernel.org
> 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>
> ---
I probably wouldn't have noticed but Sashiko found an issue on PREEMPT_RT
kernels[1].
Bart
[1] https://sashiko.dev/#/patchset/20260923030855.410109-1-fanwu01%40zju.edu.cn?part=1
> On Sep 23, 2026, at 16:15, Bartosz Golaszewski <brgl@kernel.org> wrote: > > I probably wouldn't have noticed but Sashiko found an issue on PREEMPT_RT > kernels[1]. > > Bart > > [1] https://sashiko.dev/#/patchset/20260923030855.410109-1-fanwu01%40zju.edu.cn?part=1 Hi Bart, You're right, thank you for catching this. The new kfree() is called while holding priv->irq_spin. More importantly, moving it just after that scoped guard would not be sufficient: irq_enable() is invoked with the IRQ descriptor raw spinlock held by the IRQ core. Thus, a normal kfree() cannot be used on this path on PREEMPT_RT. The worker must instead be disposed of from a safe context, while keeping its list publication and schedule_work() together under irq_spin so that the disconnect race is not reintroduced. I will rework the cleanup path accordingly and also recheck the pre-existing GFP_NOWAIT allocation in this irqchip callback for PREEMPT_RT constraints. Thanks, Fan
© 2016 - 2026 Red Hat, Inc.