drivers/gpu/drm/bridge/ti-sn65dsi83.c | 6 ++++++ 1 file changed, 6 insertions(+)
The error recovery code queues ctx->reset_work from the threaded IRQ
handler and the polling monitor_work, but nothing ever cancels it.
sn65dsi83_remove() only unplugs the bridge, and the
sn65dsi83_release_resources devm action disables the IRQ at the chip and
stops monitor_work but does not cancel reset_work. The IRQ is
devm-managed, so a reset_work already queued survives detach and the devm
IRQ free and runs after ctx is freed, recovering it via container_of() and
leading to a use-after-free. drm_bridge_enter()/SRCU guards a running work
against bridge removal but cannot stop a queued work running after ctx is
gone.
Stop the producers before unplug, then drain: disable_irq so the threaded
handler can no longer queue reset_work, cancel monitor_work, then
cancel_work_sync(&ctx->reset_work). disable_irq keeps the IRQ registered
so a racing reset_work's trailing enable_irq() stays valid, while its
disable depth stops the handler from re-arming before ctx is freed.
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@vger.kernel.org
Assisted-by: Codex:gpt-5.6
Signed-off-by: Fan Wu <fanwu01@zju.edu.cn>
---
drivers/gpu/drm/bridge/ti-sn65dsi83.c | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/drivers/gpu/drm/bridge/ti-sn65dsi83.c b/drivers/gpu/drm/bridge/ti-sn65dsi83.c
index 42b451432bbb..d334b2898e25 100644
--- a/drivers/gpu/drm/bridge/ti-sn65dsi83.c
+++ b/drivers/gpu/drm/bridge/ti-sn65dsi83.c
@@ -1069,6 +1069,12 @@ static void sn65dsi83_remove(struct i2c_client *client)
{
struct sn65dsi83 *ctx = i2c_get_clientdata(client);
+ /* Stop the reset_work producers, then drain, before unplug. */
+ if (ctx->irq)
+ disable_irq(ctx->irq);
+ cancel_delayed_work_sync(&ctx->monitor_work);
+ cancel_work_sync(&ctx->reset_work);
+
drm_bridge_unplug(&ctx->bridge);
}
--
2.39.5
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@vger.kernel.org
Assisted-by: Codex:gpt-5.6
Signed-off-by: Fan Wu <fanwu01@zju.edu.cn>
---
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
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: stable@vger.kernel.org
Assisted-by: Codex:gpt-5.6
Signed-off-by: Fan Wu <fanwu01@zju.edu.cn>
---
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
© 2016 - 2026 Red Hat, Inc.