[PATCH v2] drm/bridge: ti-sn65dsi83: Cancel reset_work on remove to avoid use-after-free
From: Fan Wu
Date: Wed Aug 05 2026 - 07:59:23 EST
The error recovery code queues reset_work from the threaded IRQ handler
and polling monitor_work. Neither the remove path nor the probe failure
path after the IRQ is registered drains that work before devres releases
the bridge allocation.
Use drm_bridge_unplug() before stopping the work. It prevents a concurrent
atomic commit from entering the bridge and re-arming monitor_work through
monitor_start(), and waits for in-flight bridge critical sections to
finish. Then disable the IRQ and drain monitor_work and reset_work.
Use the same shutdown sequence when attaching the DSI host fails. This
prevents error recovery work queued by an early IRQ from accessing the
devm-managed bridge after the failed probe returns.
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: stable@xxxxxxxxxxxxxxx
Assisted-by: Codex:gpt-5.6
Signed-off-by: Fan Wu <fanwu01@xxxxxxxxxx>
---
Changes since v1:
- Move drm_bridge_unplug() ahead of the work cancellation so a concurrent
atomic commit cannot re-arm monitor_work via monitor_start() after the
cancel.
- Drain from the probe-error path (err_remove_bridge) too, closing a UAF
where a spurious IRQ during probe queues reset_work and a later
-EPROBE_DEFER frees ctx.
- Factor the sequence into sn65dsi83_stop_error_recovery() shared by
both paths.
drivers/gpu/drm/bridge/ti-sn65dsi83.c | 14 ++++++++++++--
1 file changed, 12 insertions(+), 2 deletions(-)
diff --git a/drivers/gpu/drm/bridge/ti-sn65dsi83.c b/drivers/gpu/drm/bridge/ti-sn65dsi83.c
index 42b451432bbb..5f1a96856eac 100644
--- a/drivers/gpu/drm/bridge/ti-sn65dsi83.c
+++ b/drivers/gpu/drm/bridge/ti-sn65dsi83.c
@@ -997,6 +997,18 @@ static irqreturn_t sn65dsi83_irq(int irq, void *data)
return IRQ_HANDLED;
}
+static void sn65dsi83_stop_error_recovery(struct sn65dsi83 *ctx)
+{
+ /* Block new bridge users and wait for existing critical sections. */
+ drm_bridge_unplug(&ctx->bridge);
+
+ if (ctx->irq)
+ disable_irq(ctx->irq);
+
+ 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);
@@ -1061,7 +1073,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 +1081,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