[PATCH] gpio: mpsse: fix race when arming the IRQ poll worker

Fan Wu posted 1 patch 1 day, 10 hours ago
drivers/gpio/gpio-mpsse.c | 14 ++++++++++++--
1 file changed, 12 insertions(+), 2 deletions(-)
[PATCH] gpio: mpsse: fix race when arming the IRQ poll worker
Posted by Fan Wu 1 day, 10 hours ago
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
Re: [PATCH] gpio: mpsse: fix race when arming the IRQ poll worker
Posted by Bartosz Golaszewski 1 day, 5 hours ago
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
Re: [PATCH] gpio: mpsse: fix race when arming the IRQ poll worker
Posted by Fan Wu 1 day, 3 hours ago
> 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