* [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®