* [PATCH v2 0/2] drm/sched: fix use-after-free of the fence timeline name
@ 2026-09-02 10:58 Jonghyuk Kim(MalHyuk)
2026-09-02 10:58 ` [PATCH v2 1/2] drm/sched: cache the timeline name to fix a use-after-free Jonghyuk Kim(MalHyuk)
2026-09-02 10:58 ` [PATCH v2 2/2] drm/sched/tests: add a UAF regression test for the timeline name Jonghyuk Kim(MalHyuk)
0 siblings, 2 replies; 5+ messages in thread
From: Jonghyuk Kim(MalHyuk) @ 2026-09-02 10:58 UTC (permalink / raw)
To: tursulin, phasta, matthew.brost, dakr
Cc: christian.koenig, dri-devel, linux-kernel
drm_sched_fence_get_timeline_name() dereferences fence->sched, but a
per-context/per-queue/per-VM scheduler can be freed on an unprivileged
context/fd close while userspace still holds the exported ->finished fence
(sync_file / drm_syncobj). A later SYNC_IOC_FILE_INFO 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 patch 1 fixes it in the core for
any per-context-scheduler driver at once.
Patch 1 caches the scheduler name pointer in the fence at init time and
returns it from get_timeline_name() without touching fence->sched, plus
documents in struct drm_sched_init_args that the name must outlive any
exported fence. Patch 2 is a KUnit reproducer exercising the mock scheduler
under KASAN (no hardware needed).
v1 -> v2:
- Keep caching the name pointer and document the lifetime rule, rather than
kstrdup()-ing per fence, to avoid an allocation on the submit path for a
debug-only value (Tvrtko).
- Reworked the test to query through the public dma_fence_timeline_name()
API and moved it to a new tests_integration.c so tests_basic.c stays
focused on core scheduler behaviour (Tvrtko).
Tested with the patch 2 KUnit test under KASAN (kunit.py --arch=x86_64):
- with patch 1: [PASSED] drm-sched-dma-fence-uaf
- without patch 1: [FAILED] - BUG: KASAN: slab-use-after-free in
drm_sched_fence_get_timeline_name+0x9c/0xb0 (read of the freed scheduler)
As discussed, the cleaner long-term fix is to drop drm_sched_fence's
ops->release so dma_fence detaches ->ops on signal and decouples the name
itself (Philipp); that is a larger sched_fence rework and is left as a
follow-up, with this cached-name fix as the immediate, backportable one.
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 the timeline name
drivers/gpu/drm/scheduler/sched_fence.c | 16 +++-
drivers/gpu/drm/scheduler/tests/Makefile | 1 +
.../drm/scheduler/tests/tests_integration.c | 83 +++++++++++++++++++
include/drm/gpu_scheduler.h | 18 +++-
4 files changed, 116 insertions(+), 2 deletions(-)
create mode 100644 drivers/gpu/drm/scheduler/tests/tests_integration.c
--
2.43.0
^ permalink raw reply [flat|nested] 5+ messages in thread* [PATCH v2 1/2] drm/sched: cache the timeline name to fix a use-after-free 2026-09-02 10:58 [PATCH v2 0/2] drm/sched: fix use-after-free of the fence timeline name Jonghyuk Kim(MalHyuk) @ 2026-09-02 10:58 ` Jonghyuk Kim(MalHyuk) 2026-09-02 11:12 ` sashiko-bot 2026-09-02 10:58 ` [PATCH v2 2/2] drm/sched/tests: add a UAF regression test for the timeline name Jonghyuk Kim(MalHyuk) 1 sibling, 1 reply; 5+ messages in thread From: Jonghyuk Kim(MalHyuk) @ 2026-09-02 10:58 UTC (permalink / raw) To: tursulin, phasta, matthew.brost, dakr Cc: christian.koenig, dri-devel, linux-kernel, stable 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. Cache the scheduler's 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. The timeline name is not guaranteed by the contract to outlive the scheduler, so document in struct drm_sched_init_args that the @name passed to drm_sched_init() must follow the dma-fence safe access rules and outlive any exported fence. Every in-tree driver passes a string literal, which satisfies this; drm/xe's 299bc6d50b1b keeps its dynamically-allocated name alive across the RCU grace and can be simplified on top of this. Fixes: 506aa8b02a8d ("dma-fence: Add safe access helpers and document the rules") Cc: stable@vger.kernel.org Signed-off-by: Jonghyuk Kim(MalHyuk) <malhyuk97@gmail.com> --- drivers/gpu/drm/scheduler/sched_fence.c | 16 +++++++++++++++- include/drm/gpu_scheduler.h | 18 +++++++++++++++++- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/scheduler/sched_fence.c b/drivers/gpu/drm/scheduler/sched_fence.c index 096fe28aa9c9..a944eeeb25bd 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 = entity->rq->sched; + /* + * 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. The name is required to outlive any exported + * fence (see @name in struct drm_sched_init_args). + */ + 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 7a64cc11de08..412b8c4643f1 100644 --- a/include/drm/gpu_scheduler.h +++ b/include/drm/gpu_scheduler.h @@ -322,6 +322,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. */ @@ -646,7 +657,12 @@ struct drm_gpu_scheduler { * @timeout: timeout value in jiffies for submitted jobs. * @timeout_wq: workqueue to use for timeout work. If NULL, the system_wq is used. * @score: score atomic shared with other schedulers. May be NULL. - * @name: name (typically the driver's name). Used for debugging + * @name: name (typically the driver's name). Used for debugging, and as the + * dma-fence timeline name of the scheduler's fences. It must follow the + * dma-fence safe access rules: a &drm_sched_fence.finished exported to + * userspace can outlive the scheduler, so @name has to outlive any such + * fence - use a string literal, or free it only after an RCU grace period + * past the last exported fence. See drm_sched_fence_get_timeline_name(). * @dev: associated device. Used for debugging */ struct drm_sched_init_args { -- 2.43.0 ^ permalink raw reply related [flat|nested] 5+ messages in thread
* Re: [PATCH v2 1/2] drm/sched: cache the timeline name to fix a use-after-free 2026-09-02 10:58 ` [PATCH v2 1/2] drm/sched: cache the timeline name to fix a use-after-free Jonghyuk Kim(MalHyuk) @ 2026-09-02 11:12 ` sashiko-bot 0 siblings, 0 replies; 5+ messages in thread From: sashiko-bot @ 2026-09-02 11:12 UTC (permalink / raw) To: Jonghyuk Kim(MalHyuk); +Cc: dri-devel Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider: New issues: - [High] The patch fails to fully fix the Use-After-Free in get_timeline_name() for drivers that dynamically allocate the timeline name (drm/panthor and drm/xe). Caching the pointer merely shifts the UAF to the string's lifetime. Pre-existing issues: - [High] amdgpu_sync_same_dev blindly casts any drm_sched_fence to an amdgpu_ring, causing out-of-bounds reads and potential use-after-free for foreign fences. - [High] pvr_queue_fence_is_native dereferences sched_fence->sched->ops on foreign fences, leading to a Use-After-Free if the foreign scheduler was destroyed. -- commit 5c48f101c7375d5ec48751f42c79a19d2a8e35b2 Author: Jonghyuk Kim(MalHyuk) <malhyuk97@gmail.com> drm/sched: cache the timeline name to fix a use-after-free This commit caches the scheduler's timeline name during fence initialization to prevent a use-after-free when querying the timeline name of a finished fence whose scheduler was destroyed. It updates get_timeline_name to use this cached value instead of dereferencing the scheduler. > 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. [Severity: High] This isn't a bug introduced by this patch, but amdgpu_sync_same_dev blindly casts any drm_sched_fence to an amdgpu_ring, which can cause out-of-bounds reads and potential use-after-free for foreign fences. If a userspace process creates a job on a different DRM driver that uses drm/scheduler, exports the resulting dma_fence to a sync_file, and passes it as a dependency to an amdgpu job, can this cast access out-of-bounds memory? drivers/gpu/drm/amd/amdgpu/amdgpu_sync.c:amdgpu_sync_same_dev() { ... struct amdgpu_ring *ring; ring = container_of(s_fence->sched, struct amdgpu_ring, sched); return ring->adev == adev; ... } Since the foreign scheduler is not embedded in an amdgpu_ring, this accesses memory outside the struct. If the foreign scheduler was per-context and already destroyed by closing the context, could this also result in a use-after-free? [Severity: High] This is a pre-existing issue, but pvr_queue_fence_is_native dereferences sched_fence->sched->ops on foreign fences, which can lead to a use-after-free if the foreign scheduler was destroyed. If a userspace process creates a job on a DRM driver that allocates per-context schedulers, exports the dma_fence, closes the context (which frees the scheduler), and then submits a job to the Imagination PVR driver passing the exported fence as a dependency: drivers/gpu/drm/imagination/pvr_queue.c:pvr_queue_fence_is_native() { ... struct drm_sched_fence *sched_fence = f ? to_drm_sched_fence(f) : NULL; if (sched_fence && sched_fence->sched->ops == &pvr_queue_sched_ops) return true; ... } Because the foreign per-context scheduler was already freed, will accessing sched_fence->sched->ops result in a use-after-free? > diff --git a/drivers/gpu/drm/scheduler/sched_fence.c b/drivers/gpu/drm/scheduler/sched_fence.c > index 096fe28aa9c9e..a944eeeb25bd7 100644 > --- a/drivers/gpu/drm/scheduler/sched_fence.c > +++ b/drivers/gpu/drm/scheduler/sched_fence.c [ ... ] > @@ -228,6 +234,14 @@ void drm_sched_fence_init(struct drm_sched_fence *fence, > unsigned seq; > > fence->sched = entity->rq->sched; > + /* > + * 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. The name is required to outlive any exported > + * fence (see @name in struct drm_sched_init_args). > + */ > + fence->sched_name = fence->sched->name; [Severity: High] Does caching the name pointer fully fix the use-after-free for drivers like drm/panthor and drm/xe that dynamically allocate the timeline name? In drm/panthor, queue->name is dynamically allocated and freed immediately via kfree upon queue teardown: drivers/gpu/drm/panthor/panthor_sched.c:group_create_queue() { ... queue->name = kasprintf(GFP_KERNEL, "panthor-queue-%llu-%u-%u", drm_client_id, gid, qid); ... } If an exported dma_fence held in a sync_file outlives the queue, and userspace later calls SYNC_IOC_FILE_INFO, won't drm_sched_fence_get_timeline_name return this cached pointer which now points to freed memory? -- Sashiko AI review · https://sashiko.dev/#/patchset/20260902105808.1541063-1-malhyuk97@gmail.com?part=1 ^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH v2 2/2] drm/sched/tests: add a UAF regression test for the timeline name 2026-09-02 10:58 [PATCH v2 0/2] drm/sched: fix use-after-free of the fence timeline name Jonghyuk Kim(MalHyuk) 2026-09-02 10:58 ` [PATCH v2 1/2] drm/sched: cache the timeline name to fix a use-after-free Jonghyuk Kim(MalHyuk) @ 2026-09-02 10:58 ` Jonghyuk Kim(MalHyuk) 2026-09-02 11:09 ` sashiko-bot 1 sibling, 1 reply; 5+ messages in thread From: Jonghyuk Kim(MalHyuk) @ 2026-09-02 10:58 UTC (permalink / raw) To: tursulin, phasta, matthew.brost, dakr Cc: christian.koenig, dri-devel, linux-kernel Add a KUnit test that reproduces the drm_sched_fence get_timeline_name() 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 queries the timeline name through the public dma_fence_timeline_name() API - as a SYNC_IOC_FILE_INFO consumer would. Before the fix this triggers a KASAN slab-use-after-free read of the freed scheduler; after it the cached name is returned and the test passes. The test needs no hardware - it exercises the drm_sched core through the existing mock scheduler under KASAN. Put it in a new tests_integration.c so tests_basic.c stays focused on core scheduler behaviour. Signed-off-by: Jonghyuk Kim(MalHyuk) <malhyuk97@gmail.com> --- drivers/gpu/drm/scheduler/tests/Makefile | 1 + .../drm/scheduler/tests/tests_integration.c | 83 +++++++++++++++++++ 2 files changed, 84 insertions(+) create mode 100644 drivers/gpu/drm/scheduler/tests/tests_integration.c diff --git a/drivers/gpu/drm/scheduler/tests/Makefile b/drivers/gpu/drm/scheduler/tests/Makefile index 9ec185fbbc15..10abe07c06d2 100644 --- a/drivers/gpu/drm/scheduler/tests/Makefile +++ b/drivers/gpu/drm/scheduler/tests/Makefile @@ -3,6 +3,7 @@ drm-sched-tests-y := \ mock_scheduler.o \ tests_basic.o \ + tests_integration.o \ tests_scheduler.o obj-$(CONFIG_DRM_SCHED_KUNIT_TEST) += drm-sched-tests.o diff --git a/drivers/gpu/drm/scheduler/tests/tests_integration.c b/drivers/gpu/drm/scheduler/tests/tests_integration.c new file mode 100644 index 000000000000..0572f1e3a80b --- /dev/null +++ b/drivers/gpu/drm/scheduler/tests/tests_integration.c @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (c) 2026 Jonghyuk Kim(MalHyuk) */ + +#include <linux/dma-fence.h> +#include <linux/rcupdate.h> + +#include "sched_tests.h" + +/* + * Integration tests exercising the drm_sched interaction with the wider + * dma-fence infrastructure, e.g. fences exported to userspace outliving the + * objects they were created from. + */ + +/* + * Reproduce the drm_sched_fence get_timeline_name() lifetime bug. + * + * drm_sched_fence_get_timeline_name() reads the scheduler name, and the + * drm_sched_fence ops keep .release set, so the fence is NOT ops-detached on + * signal (dma_fence_signal_timestamp_locked() only clears ->ops for fences + * without .release/.wait). A driver that frees a per-context drm_gpu_scheduler + * while userspace still holds the exported ->finished fence (via sync_file / + * drm_syncobj) leaves the scheduler dangling; querying the timeline name then + * touches freed slab memory. KASAN reports a slab-use-after-free read in + * drm_sched_fence_get_timeline_name(). Confirmed instances: amdxdna, nouveau, + * msm; same class as CVE-2025-38703 (xe) and CVE-2025-71302 (panthor). + */ +static void drm_sched_dma_fence_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; the s_fence is only created by drm_sched_job_arm(). */ + drm_mock_sched_job_submit(job); + + /* Independent reference on the finished fence, as a sync_file would. */ + finished = dma_fence_get(&job->base.s_fence->finished); + + 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); + + /* + * Query the timeline name through the public dma-fence API, as a + * userspace SYNC_IOC_FILE_INFO consumer would. Before the fix this is a + * use-after-free read of the freed scheduler; after it the cached name + * is returned and the test passes. + */ + rcu_read_lock(); + name = (const char *)dma_fence_timeline_name(finished); + rcu_read_unlock(); + kunit_info(test, "get_timeline_name() on stale fence returned %p\n", name); + + dma_fence_put(finished); +} + +static struct kunit_case drm_sched_dma_fence_uaf_tests[] = { + KUNIT_CASE(drm_sched_dma_fence_timeline_name_uaf), + {} +}; + +static struct kunit_suite drm_sched_dma_fence_uaf = { + .name = "drm-sched-dma-fence-uaf", + .test_cases = drm_sched_dma_fence_uaf_tests, +}; + +kunit_test_suite(drm_sched_dma_fence_uaf); -- 2.43.0 ^ permalink raw reply related [flat|nested] 5+ messages in thread
* Re: [PATCH v2 2/2] drm/sched/tests: add a UAF regression test for the timeline name 2026-09-02 10:58 ` [PATCH v2 2/2] drm/sched/tests: add a UAF regression test for the timeline name Jonghyuk Kim(MalHyuk) @ 2026-09-02 11:09 ` sashiko-bot 0 siblings, 0 replies; 5+ messages in thread From: sashiko-bot @ 2026-09-02 11:09 UTC (permalink / raw) To: Jonghyuk Kim(MalHyuk); +Cc: dri-devel Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [Medium] Test aborts skip scheduler cleanup leading to use-after-free crash in workqueue. -- commit a5abd99d79c0acda1fdefe1fe2690846be11bd71 Author: Jonghyuk Kim(MalHyuk) <malhyuk97@gmail.com> drm/sched/tests: add a UAF regression test for the timeline name This commit adds a KUnit integration test to reproduce a use-after-free bug in drm_sched_fence_get_timeline_name(). It tests the scenario where a scheduler is freed while userspace still holds a reference to a finished fence, querying the timeline name via the dma-fence API. > diff --git a/drivers/gpu/drm/scheduler/tests/tests_integration.c b/drivers/gpu/drm/scheduler/tests/tests_integration.c > new file mode 100644 > index 0000000000000..0572f1e3a80b2 > --- /dev/null > +++ b/drivers/gpu/drm/scheduler/tests/tests_integration.c [ ... ] > + /* Independent reference on the finished fence, as a sync_file would. */ > + finished = dma_fence_get(&job->base.s_fence->finished); > + > + done = drm_mock_sched_job_wait_scheduled(job, HZ); > + KUNIT_ASSERT_TRUE(test, done); [Severity: Medium] If this assertion fails, the test aborts immediately, skipping the explicit drm_mock_sched_fini() and drm_mock_sched_entity_free() cleanup calls at the end of the function. Since the sched object is allocated with kunit_kzalloc(), the KUnit framework will automatically free its memory on test abort. However, without calling drm_mock_sched_fini(), the sched->submit_wq workqueue and sched->work_tdr delayed work will remain active. Could this cause a kernel panic due to a use-after-free when the active workqueue tries to execute a work struct from the freed scheduler memory? > + drm_mock_sched_advance(sched, 1); > + done = drm_mock_sched_job_wait_finished(job, HZ); > + KUNIT_ASSERT_TRUE(test, done); [Severity: Medium] Similarly, failing here aborts the test while leaving active workqueues referencing the soon-to-be-freed scheduler. Would it be safer to use KUNIT_EXPECT_TRUE() instead to allow the test to continue to the cleanup phase, or use kunit_add_action() to ensure the scheduler is always properly torn down? > + > + /* Free the per-context scheduler while the finished fence is held. */ > + drm_mock_sched_entity_free(entity); -- Sashiko AI review · https://sashiko.dev/#/patchset/20260902105808.1541063-1-malhyuk97@gmail.com?part=2 ^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-09-03 7:33 UTC | newest] Thread overview: 5+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-09-02 10:58 [PATCH v2 0/2] drm/sched: fix use-after-free of the fence timeline name Jonghyuk Kim(MalHyuk) 2026-09-02 10:58 ` [PATCH v2 1/2] drm/sched: cache the timeline name to fix a use-after-free Jonghyuk Kim(MalHyuk) 2026-09-02 11:12 ` sashiko-bot 2026-09-02 10:58 ` [PATCH v2 2/2] drm/sched/tests: add a UAF regression test for the timeline name Jonghyuk Kim(MalHyuk) 2026-09-02 11:09 ` sashiko-bot
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox