mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH v1 0/2] drm/sched: fix a use-after-free in get_timeline_name()
@ 2026-08-28 14:57 Jonghyuk Kim(MalHyuk)
  2026-08-28 14:57 ` [PATCH v1 1/2] drm/sched: cache the timeline name to fix a use-after-free Jonghyuk Kim(MalHyuk)
  2026-08-28 14:57 ` [PATCH v1 2/2] drm/sched/tests: add a UAF regression test for get_timeline_name() Jonghyuk Kim(MalHyuk)
  0 siblings, 2 replies; 3+ messages in thread
From: Jonghyuk Kim(MalHyuk) @ 2026-08-28 14:57 UTC (permalink / raw)
  To: Matthew Brost, Danilo Krummrich, Philipp Stanner
  Cc: Christian König, dri-devel, linux-kernel, Jonghyuk Kim(MalHyuk)

This fixes an unprivileged use-after-free (read) in the DRM GPU scheduler
core, present in current mainline (v7.2-rc5) and reachable through at least
three in-tree drivers: amdxdna, nouveau and msm (VM_BIND).

drm_sched_fence_get_timeline_name() dereferences fence->sched->name, and
the fence is not ops-detached on signalling (the ops carry a .release
callback), so a userspace-held finished fence can outlive a per-context
drm_gpu_scheduler that a driver frees on context/fd teardown.
get_timeline_name() is reachable unprivileged via SYNC_IOC_FILE_INFO on an
exported sync_file, so this is a deterministic UAF read of the freed
scheduler - a bounded arbitrary kernel read once the slab is reclaimed
(there is no write primitive on this path).

It's the same bug class as CVE-2025-38703 (drm/xe) and CVE-2025-71302
(drm/panthor), which were fixed per-driver; the drivers above never got
the equivalent fix. Patch 1 fixes it in the core (cache the persistent
timeline-name pointer at fence init) so any per-context-scheduler driver is
covered. Patch 2 adds a KUnit regression test on the existing drm_sched
mock harness that catches the UAF under KASAN with no hardware.

Since the bug class is already public (the xe/panthor CVEs), I'm sending
this to the list directly rather than through the security process. It
looks like a candidate for stable backport.

The KUnit test was run with:

    ./tools/testing/kunit/kunit.py run --arch=x86_64 \
        --kunitconfig=<KUNIT + KASAN + DRM_SCHED_KUNIT_TEST> \
        'drm_sched_fence_uaf_tests*'

- without patch 1: KASAN slab-use-after-free in
  drm_sched_fence_get_timeline_name
- with patch 1: test passes, no KASAN report

Jonghyuk Kim(MalHyuk) (2):
  drm/sched: cache the timeline name to fix a use-after-free
  drm/sched/tests: add a UAF regression test for get_timeline_name()

 drivers/gpu/drm/scheduler/sched_fence.c       | 16 ++++-
 drivers/gpu/drm/scheduler/tests/tests_basic.c | 65 ++++++++++++++++++-
 include/drm/gpu_scheduler.h                   | 11 ++++
 3 files changed, 90 insertions(+), 2 deletions(-)

-- 
2.43.0


^ permalink raw reply	[flat|nested] 3+ messages in thread

* [PATCH v1 1/2] drm/sched: cache the timeline name to fix a use-after-free
  2026-08-28 14:57 [PATCH v1 0/2] drm/sched: fix a use-after-free in get_timeline_name() Jonghyuk Kim(MalHyuk)
@ 2026-08-28 14:57 ` Jonghyuk Kim(MalHyuk)
  2026-08-28 14:57 ` [PATCH v1 2/2] drm/sched/tests: add a UAF regression test for get_timeline_name() Jonghyuk Kim(MalHyuk)
  1 sibling, 0 replies; 3+ messages in thread
From: Jonghyuk Kim(MalHyuk) @ 2026-08-28 14:57 UTC (permalink / raw)
  To: Matthew Brost, Danilo Krummrich, Philipp Stanner
  Cc: Christian König, dri-devel, linux-kernel, Jonghyuk Kim(MalHyuk)

drm_sched_fence_get_timeline_name() returns fence->sched->name, and the
drm_sched_fence ops keep a .release callback, so the fence is not
ops-detached on signalling (dma_fence_signal_timestamp_locked() only
clears ->ops for fences without .release/.wait). The callback therefore
stays reachable on a long-signalled, userspace-held finished fence and
unconditionally dereferences fence->sched.

A driver that allocates a drm_gpu_scheduler at per-context/per-queue/
per-VM granularity and frees it on an unprivileged context/fd close, while
exporting the resulting finished fence to userspace (drm_syncobj /
sync_file / dma_resv), leaves fence->sched dangling after the free. A
subsequent SYNC_IOC_FILE_INFO ioctl (which calls get_timeline_name())
then reads the freed scheduler:

  BUG: KASAN: slab-use-after-free in drm_sched_fence_get_timeline_name

This is the same class as CVE-2025-38703 (drm/xe) and CVE-2025-71302
(drm/panthor), which were fixed per-driver. amdxdna, nouveau and msm
(VM_BIND) are still affected in mainline, so fix it in the core to cover
any per-context-scheduler driver at once.

Scheduler names are persistent strings (string literals passed to
drm_sched_init()), so cache the name pointer in the fence at init time,
while the scheduler is guaranteed alive, and return the cached value from
get_timeline_name() without dereferencing fence->sched.

Signed-off-by: Jonghyuk Kim(MalHyuk) <malhyuk97@gmail.com>
---
 drivers/gpu/drm/scheduler/sched_fence.c | 16 +++++++++++++++-
 include/drm/gpu_scheduler.h             | 11 +++++++++++
 2 files changed, 26 insertions(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/scheduler/sched_fence.c b/drivers/gpu/drm/scheduler/sched_fence.c
index 85ee3d694dc9..280a2d036ffd 100644
--- a/drivers/gpu/drm/scheduler/sched_fence.c
+++ b/drivers/gpu/drm/scheduler/sched_fence.c
@@ -92,7 +92,13 @@ static const char *drm_sched_fence_get_driver_name(struct dma_fence *fence)
 static const char *drm_sched_fence_get_timeline_name(struct dma_fence *f)
 {
 	struct drm_sched_fence *fence = to_drm_sched_fence(f);
-	return (const char *)fence->sched->name;
+
+	/*
+	 * Do not dereference fence->sched here: a userspace-held finished
+	 * fence can outlive a per-context scheduler. Return the name cached
+	 * in drm_sched_fence_init() instead.
+	 */
+	return fence->sched_name;
 }
 
 static void drm_sched_fence_free_rcu(struct rcu_head *rcu)
@@ -228,6 +234,14 @@ void drm_sched_fence_init(struct drm_sched_fence *fence,
 	unsigned seq;
 
 	fence->sched = container_of(entity->rq, typeof(*fence->sched), rq);
+	/*
+	 * Cache the scheduler's timeline name: the finished fence may be
+	 * exported to userspace and outlive @sched (per-context schedulers
+	 * are freed on context teardown), so get_timeline_name() must not
+	 * dereference @sched. Scheduler names are persistent (string
+	 * literals passed to drm_sched_init()).
+	 */
+	fence->sched_name = fence->sched->name;
 	seq = atomic_inc_return(&entity->fence_seq);
 	dma_fence_init(&fence->scheduled, &drm_sched_fence_ops_scheduled,
 		       &fence->lock, entity->fence_context, seq);
diff --git a/include/drm/gpu_scheduler.h b/include/drm/gpu_scheduler.h
index d61c19e78182..4f780d70acb6 100644
--- a/include/drm/gpu_scheduler.h
+++ b/include/drm/gpu_scheduler.h
@@ -305,6 +305,17 @@ struct drm_sched_fence {
          * belongs to.
          */
 	struct drm_gpu_scheduler	*sched;
+	/**
+	 * @sched_name: the timeline name of @sched, cached at init time.
+	 *
+	 * &drm_sched_fence.finished may be exported to userspace (via a
+	 * sync_file or drm_syncobj) and can outlive @sched: a driver using a
+	 * per-context scheduler frees it on context teardown while a
+	 * userspace-held finished fence still references it. The
+	 * get_timeline_name() callback must therefore not dereference @sched;
+	 * it returns this cached name instead.
+	 */
+	const char			*sched_name;
         /**
          * @lock: the lock used by the scheduled and the finished fences.
          */
-- 
2.43.0


^ permalink raw reply	[flat|nested] 3+ messages in thread

* [PATCH v1 2/2] drm/sched/tests: add a UAF regression test for get_timeline_name()
  2026-08-28 14:57 [PATCH v1 0/2] drm/sched: fix a use-after-free in get_timeline_name() Jonghyuk Kim(MalHyuk)
  2026-08-28 14:57 ` [PATCH v1 1/2] drm/sched: cache the timeline name to fix a use-after-free Jonghyuk Kim(MalHyuk)
@ 2026-08-28 14:57 ` Jonghyuk Kim(MalHyuk)
  1 sibling, 0 replies; 3+ messages in thread
From: Jonghyuk Kim(MalHyuk) @ 2026-08-28 14:57 UTC (permalink / raw)
  To: Matthew Brost, Danilo Krummrich, Philipp Stanner
  Cc: Christian König, dri-devel, linux-kernel, Jonghyuk Kim(MalHyuk)

Add a KUnit test that reproduces the use-after-free fixed by the previous
patch. It holds a reference on a job's finished drm_sched_fence (standing
in for a userspace sync_file), frees the mock scheduler, then calls
get_timeline_name(). Before the fix this triggers a KASAN
slab-use-after-free read of the freed scheduler; after it the test passes.

The test needs no hardware - it exercises the drm_sched core through the
existing mock scheduler under KASAN.

Signed-off-by: Jonghyuk Kim(MalHyuk) <malhyuk97@gmail.com>
---
 drivers/gpu/drm/scheduler/tests/tests_basic.c | 65 ++++++++++++++++++-
 1 file changed, 64 insertions(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/scheduler/tests/tests_basic.c b/drivers/gpu/drm/scheduler/tests/tests_basic.c
index a5a5a35a87b0..2c6744b58f04 100644
--- a/drivers/gpu/drm/scheduler/tests/tests_basic.c
+++ b/drivers/gpu/drm/scheduler/tests/tests_basic.c
@@ -555,9 +555,72 @@ static struct kunit_suite drm_sched_credits = {
 	.test_cases = drm_sched_credits_tests,
 };
 
+/*
+ * Reproduce the drm_sched_fence get_timeline_name() lifetime bug.
+ *
+ * drm_sched_fence_get_timeline_name() dereferences fence->sched->name, and the
+ * drm_sched_fence ops keep .release set, so the fence is NOT ops-detached on
+ * signal (unlike stub fences). A driver that frees a per-context
+ * drm_gpu_scheduler while userspace still holds the exported ->finished fence
+ * (via sync_file / drm_syncobj) leaves fence->sched dangling; reading the
+ * timeline name then touches freed slab memory (arbitrary-read once the slab is
+ * re-sprayed). Confirmed instances: amdxdna, nouveau, msm. Same class as
+ * CVE-2025-38703 (xe) and CVE-2025-71302 (panthor). KASAN reports a
+ * slab-use-after-free READ in drm_sched_fence_get_timeline_name.
+ */
+static void drm_sched_fence_get_timeline_name_uaf(struct kunit *test)
+{
+	struct drm_mock_sched_entity *entity;
+	struct drm_mock_scheduler *sched;
+	struct drm_mock_sched_job *job;
+	struct dma_fence *finished;
+	const char *name;
+	bool done;
+
+	sched = drm_mock_sched_new(test, MAX_SCHEDULE_TIMEOUT);
+	entity = drm_mock_sched_entity_new(test, DRM_SCHED_PRIORITY_NORMAL,
+					   sched);
+	job = drm_mock_sched_job_new(test, entity);
+
+	/* Arm + submit first; the s_fence is only created by drm_sched_job_arm(). */
+	drm_mock_sched_job_submit(job);
+
+	/* Independent reference on the finished fence == userspace sync_file. */
+	finished = dma_fence_get(&job->base.s_fence->finished);
+
+	/* Let the job get picked up (hw_fence created), then signal + finish. */
+	done = drm_mock_sched_job_wait_scheduled(job, HZ);
+	KUNIT_ASSERT_TRUE(test, done);
+	drm_mock_sched_advance(sched, 1);
+	done = drm_mock_sched_job_wait_finished(job, HZ);
+	KUNIT_ASSERT_TRUE(test, done);
+
+	/* Free the per-context scheduler while the finished fence is held. */
+	drm_mock_sched_entity_free(entity);
+	drm_mock_sched_fini(sched);
+	kunit_kfree(test, sched);
+
+	/* UAF read: fence->sched->name is read from the freed scheduler. */
+	name = finished->ops->get_timeline_name(finished);
+	kunit_info(test, "get_timeline_name() on stale fence returned %p\n", name);
+
+	dma_fence_put(finished);
+}
+
+static struct kunit_case drm_sched_fence_uaf_tests[] = {
+	KUNIT_CASE(drm_sched_fence_get_timeline_name_uaf),
+	{}
+};
+
+static struct kunit_suite drm_sched_fence_uaf = {
+	.name = "drm_sched_fence_uaf_tests",
+	.test_cases = drm_sched_fence_uaf_tests,
+};
+
 kunit_test_suites(&drm_sched_basic,
 		  &drm_sched_timeout,
 		  &drm_sched_cancel,
 		  &drm_sched_priority,
 		  &drm_sched_modify_sched,
-		  &drm_sched_credits);
+		  &drm_sched_credits,
+		  &drm_sched_fence_uaf);
-- 
2.43.0


^ permalink raw reply	[flat|nested] 3+ messages in thread

end of thread, other threads:[~2026-08-28 14:58 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-08-28 14:57 [PATCH v1 0/2] drm/sched: fix a use-after-free in get_timeline_name() Jonghyuk Kim(MalHyuk)
2026-08-28 14:57 ` [PATCH v1 1/2] drm/sched: cache the timeline name to fix a use-after-free Jonghyuk Kim(MalHyuk)
2026-08-28 14:57 ` [PATCH v1 2/2] drm/sched/tests: add a UAF regression test for get_timeline_name() Jonghyuk Kim(MalHyuk)

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®