[PATCH v3] drm/bridge: ti-sn65dsi83: Cancel reset_work on remove to avoid use-after-free
Fan Wu <[email protected]> Wed, 5 Aug 2026 14:18:16 +0000
| Newsgroups | gmane.linux.kernel.stable,gmane.comp.video.dri.devel,gmane.linux.kernel |
|---|---|
| Message-ID | <[email protected]> |
The error recovery code queues reset_work from the threaded IRQ handler
and polling monitor_work. Neither device removal nor a probe failure after
requesting the IRQ drains that work before devres releases the bridge
allocation.
Keep the IRQ disabled after it is requested and enable it only after the
bridge has attached successfully. This prevents an IRQ during probe from
queueing reset_work before bridge->encoder has been initialized.
On remove and the post-IRQ probe failure path, synchronously disable the
IRQ before declaring the bridge unplugged. This prevents an asserted IRQ
from repeatedly invoking a handler which can no longer acknowledge the
device. drm_bridge_unplug() then blocks new DRM entry points and waits for
existing bridge critical sections. Finally drain monitor_work and
reset_work before devres can free the bridge allocation.
This issue was found by an in-house static analysis tool.
Compile-tested only; runtime testing is appreciated.
Fixes: ad5c6ecef27e ("drm: bridge: ti-sn65dsi83: Add error recovery mechanism")
Cc: [email protected]
Assisted-by: Codex:gpt-5.6
Signed-off-by: Fan Wu <[email protected]>
---
Changes since v2:
- Request the IRQ with IRQF_NO_AUTOEN and enable it only after the bridge
attaches, so it cannot fire during probe and queue reset_work before
bridge->encoder is initialized.
- Disable the IRQ before drm_bridge_unplug() in the teardown helper, so
an asserted level IRQ is acknowledged by the handler while it can still
enter the bridge instead of re-firing once the unplug barrier rejects
it.
drivers/gpu/drm/bridge/ti-sn65dsi83.c | 28 +++++++++++++++++++++++-----
1 file changed, 23 insertions(+), 5 deletions(-)
diff --git a/drivers/gpu/drm/bridge/ti-sn65dsi83.c b/drivers/gpu/drm/bridge/ti-sn65dsi83.c
index 42b451432bbb..d7648b5d5636 100644
--- a/drivers/gpu/drm/bridge/ti-sn65dsi83.c
+++ b/drivers/gpu/drm/bridge/ti-sn65dsi83.c
@@ -296,9 +296,14 @@ static int sn65dsi83_attach(struct drm_bridge *bridge,
enum drm_bridge_attach_flags flags)
{
struct sn65dsi83 *ctx = bridge_to_sn65dsi83(bridge);
+ int ret;
- return drm_bridge_attach(encoder, ctx->panel_bridge,
- &ctx->bridge, flags);
+ ret = drm_bridge_attach(encoder, ctx->panel_bridge, &ctx->bridge,
+ flags);
+ if (!ret && ctx->irq)
+ enable_irq(ctx->irq);
+
+ return ret;
}
static void sn65dsi83_detach(struct drm_bridge *bridge)
@@ -997,6 +1004,19 @@ static irqreturn_t sn65dsi83_irq(int irq, void *data)
return IRQ_HANDLED;
}
+static void sn65dsi83_stop_error_recovery(struct sn65dsi83 *ctx)
+{
+ /* Stop the IRQ before the unplug barrier rejects its handler. */
+ if (ctx->irq)
+ disable_irq(ctx->irq);
+
+ /* Block new bridge users and wait for existing critical sections. */
+ drm_bridge_unplug(&ctx->bridge);
+
+ cancel_delayed_work_sync(&ctx->monitor_work);
+ cancel_work_sync(&ctx->reset_work);
+}
+
static int sn65dsi83_probe(struct i2c_client *client)
{
const struct i2c_device_id *id = i2c_client_get_device_id(client);
@@ -1039,7 +1056,8 @@ static int sn65dsi83_probe(struct i2c_client *client)
if (client->irq) {
ctx->irq = client->irq;
ret = devm_request_threaded_irq(ctx->dev, ctx->irq, NULL, sn65dsi83_irq,
- IRQF_ONESHOT, dev_name(ctx->dev), ctx);
+ IRQF_ONESHOT | IRQF_NO_AUTOEN,
+ dev_name(ctx->dev), ctx);
if (ret)
return dev_err_probe(dev, ret, "failed to request irq\n");
}
@@ -1061,7 +1078,7 @@ static int sn65dsi83_probe(struct i2c_client *client)
return 0;
err_remove_bridge:
- drm_bridge_remove(&ctx->bridge);
+ sn65dsi83_stop_error_recovery(ctx);
return ret;
}
@@ -1069,6 +1086,6 @@ static void sn65dsi83_remove(struct i2c_client *client)
{
struct sn65dsi83 *ctx = i2c_get_clientdata(client);
- drm_bridge_unplug(&ctx->bridge);
+ sn65dsi83_stop_error_recovery(ctx);
}
--
2.51.0