[PATCH] drm/lima: fix use-after-free of recover work on device removal

From: Fan Wu

Date: Thu Sep 17 2026 - 05:36:27 EST


A recoverable GP error, reported by the GP or the GP MMU interrupt,
queues the pipe's recover_work on the system workqueue. Nothing drains
it on device removal: the worker can run after the GP task slab has
been destroyed, call drm_sched_fault() on a scheduler that
drm_sched_fini() has already torn down, or outlive the devm allocation
that embeds struct lima_device.

Free the GP and GP MMU interrupts, the only sources of recover_work,
before the GP pipe teardown, and drain the work while the task slab and
the scheduler are still alive. Stop the GP with a final reset after
drm_sched_fini(): nothing can restart the job anymore. Free the error
task list only after both schedulers are fini'd, as the timeout
handler locks it.

This issue was found by an in-house static analysis tool.

Fixes: 2081e8dcf1ee ("drm/lima: recover task by enlarging heap buffer")
Cc: stable@xxxxxxxxxxxxxxx
Assisted-by: Codex:gpt-5.6
Co-developed-by: Song Li <songl@xxxxxxxxxx>
Signed-off-by: Song Li <songl@xxxxxxxxxx>
Signed-off-by: Fan Wu <fanwu01@xxxxxxxxxx>
---
drivers/gpu/drm/lima/lima_device.c | 22 +++++++++++++++++-----
drivers/gpu/drm/lima/lima_gp.c | 7 +++++++
drivers/gpu/drm/lima/lima_gp.h | 1 +
3 files changed, 25 insertions(+), 5 deletions(-)

diff --git a/drivers/gpu/drm/lima/lima_device.c b/drivers/gpu/drm/lima/lima_device.c
index 0bf7105c8748..5f728230fce4 100644
--- a/drivers/gpu/drm/lima/lima_device.c
+++ b/drivers/gpu/drm/lima/lima_device.c
@@ -297,8 +297,14 @@ static void lima_fini_gp_pipe(struct lima_device *dev)
{
struct lima_sched_pipe *pipe = dev->pipe + lima_pipe_gp;

- lima_gp_pipe_fini(dev);
+ cancel_work_sync(&pipe->recover_work);
+
lima_sched_pipe_fini(pipe);
+
+ /* a recovery may have restarted the GP job */
+ lima_gp_stop(dev->ip + lima_ip_gp);
+
+ lima_gp_pipe_fini(dev);
}

static int lima_init_pp_pipe(struct lima_device *dev)
@@ -442,17 +448,23 @@ void lima_device_fini(struct lima_device *ldev)
int i;
struct lima_sched_error_task *et, *tmp;

+ lima_fini_pp_pipe(ldev);
+
+ /* free the IRQ sources of recover_work before the GP pipe drain */
+ lima_fini_ip(ldev, lima_ip_gp);
+ lima_fini_ip(ldev, lima_ip_gpmmu);
+ lima_fini_gp_pipe(ldev);
+
+ /* the timeout handlers lock it: free after both schedulers */
list_for_each_entry_safe(et, tmp, &ldev->error_task_list, list) {
list_del(&et->list);
kvfree(et);
}
mutex_destroy(&ldev->error_task_list_lock);

- lima_fini_pp_pipe(ldev);
- lima_fini_gp_pipe(ldev);
-
for (i = lima_ip_num - 1; i >= 0; i--)
- lima_fini_ip(ldev, i);
+ if (i != lima_ip_gp && i != lima_ip_gpmmu)
+ lima_fini_ip(ldev, i);

if (ldev->dlbu_cpu)
dma_free_wc(ldev->dev, LIMA_PAGE_SIZE,
diff --git a/drivers/gpu/drm/lima/lima_gp.c b/drivers/gpu/drm/lima/lima_gp.c
index 3282997a0358..7287af7829f8 100644
--- a/drivers/gpu/drm/lima/lima_gp.c
+++ b/drivers/gpu/drm/lima/lima_gp.c
@@ -350,6 +350,13 @@ void lima_gp_fini(struct lima_ip *ip)
devm_free_irq(dev->dev, ip->irq, ip);
}

+/* keep the irq masked: hard_reset() re-enables it */
+void lima_gp_stop(struct lima_ip *ip)
+{
+ lima_gp_hard_reset(ip);
+ gp_write(LIMA_GP_INT_MASK, 0);
+}
+
int lima_gp_pipe_init(struct lima_device *dev)
{
int frame_size = sizeof(struct drm_lima_gp_frame);
diff --git a/drivers/gpu/drm/lima/lima_gp.h b/drivers/gpu/drm/lima/lima_gp.h
index 02ec9af78a51..c2a1e54ea567 100644
--- a/drivers/gpu/drm/lima/lima_gp.h
+++ b/drivers/gpu/drm/lima/lima_gp.h
@@ -11,6 +11,7 @@ int lima_gp_resume(struct lima_ip *ip);
void lima_gp_suspend(struct lima_ip *ip);
int lima_gp_init(struct lima_ip *ip);
void lima_gp_fini(struct lima_ip *ip);
+void lima_gp_stop(struct lima_ip *ip);

int lima_gp_pipe_init(struct lima_device *dev);
void lima_gp_pipe_fini(struct lima_device *dev);