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

Fan Wu posted 1 patch 1 week ago
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(-)
[PATCH] drm/lima: fix use-after-free of recover work on device removal
Posted by Fan Wu 1 week ago
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@vger.kernel.org
Assisted-by: Codex:gpt-5.6
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/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);