drivers/gpu/drm/scheduler/sched_fence.c | 46 +++++----- drivers/gpu/drm/scheduler/sched_main.c | 9 ++ drivers/gpu/drm/scheduler/tests/Makefile | 1 + .../drm/scheduler/tests/tests_integration.c | 92 +++++++++++++++++++ include/drm/gpu_scheduler.h | 22 +++-- 5 files changed, 139 insertions(+), 31 deletions(-) create mode 100644 drivers/gpu/drm/scheduler/tests/tests_integration.c
drm_sched_fence_get_timeline_name() dereferences fence->sched->name. A
driver that allocates a drm_gpu_scheduler per context, queue or VM frees
that scheduler on context teardown, but the finished fence can outlive it:
unprivileged userspace holds the exported fence via a sync_file or
drm_syncobj and later queries its timeline name (e.g. SYNC_IOC_FILE_INFO),
reading the freed scheduler. Same class as CVE-2025-38703 (drm/xe) and
CVE-2025-71302 (drm/panthor); amdxdna, nouveau and msm (VM_BIND) are still
affected in mainline.
This series fixes it in the core rather than per driver.
v1 and v2 took the approach of caching the name at fence init. Review showed
that is the wrong fix:
- Tvrtko pointed out the documented contract does not require the name
passed to drm_sched_init() to outlive the scheduler, so caching the bare
pointer only narrows the window; and
- the sashiko review bot pointed out that caching does not help drivers
whose timeline name is dynamically allocated and freed with the queue
(drm/panthor, drm/xe) - it just moves the UAF to the string's lifetime.
Philipp suggested dropping the finished fence's ->release callback instead.
That is what this series does. dma_fence detaches a fence's ops on signalling
when it has neither .release nor .wait (dma_fence_signal_timestamp_locked()),
and dma_fence_timeline_name() returns a static string once the ops are gone.
So with the callback removed, get_timeline_name() is simply never reached on
a signalled finished fence - no ->sched dereference at all, for static and
dynamically-allocated names alike. The finished fence's only job in that
callback was to drop the scheduled fence's reference, which patch 1 moves
elsewhere.
Link to v2 (name caching):
https://lore.kernel.org/dri-devel/20260902105808.1541063-1-malhyuk97@gmail.com/
Note: detaching the finished fence's ops on signalling also makes
to_drm_sched_fence() return NULL for a signalled finished fence. Callers
already handle NULL (the normal foreign-fence result), a signalled fence is
an already-satisfied dependency so the scheduler's dependency collapsing is
unaffected, and it avoids the container_of() on a possibly-freed foreign
scheduler that amdgpu_sync_same_dev() and pvr_queue_fence_is_native() would
otherwise do. Flagging it explicitly since it touches an exported helper.
I did not add Fixes:/Cc: stable tags: the ->sched->name deref dates back to
1b1f42d8fde4 ("drm: move amd_gpu_scheduler into common location") but only
became reachable once drivers began allocating per-context schedulers, so
the right attribution is unclear to me. This is stable material as the
driver instances are live - happy to add whatever tags you prefer.
Tested with KUnit under KASAN (kunit.py --arch=x86_64), matched pair:
- unfixed (finished fence keeps .release):
[FAILED] drm_sched_dma_fence_uaf
BUG: KASAN: slab-use-after-free in
drm_sched_fence_get_timeline_name+0x9c/0xb0
Read of size 8 ...
- fixed (this series):
[PASSED] drm_sched_dma_fence_uaf
Testing complete. Ran 47 tests: passed: 47
(The whole drm_sched suite passes with the series, no regressions.)
v3:
- Switch from caching the timeline name (v1/v2) to dropping the finished
fence's ->release so the ops are detached on signalling (per Philipp);
also fixes the dynamically-allocated-name drivers caching could not.
- Rework the scheduled/finished fence lifetime: the scheduled fence now
holds a reference on the finished fence, which is released last and freed
from dma_fence_free(); @finished moved to offset 0. drm_sched_job_cleanup()
drops the scheduled fence's initial reference.
- Move the regression test to a new tests_integration.c and query via
dma_fence_timeline_name() (per Tvrtko's review of v2).
Jonghyuk Kim(MalHyuk) (2):
drm/sched: fix use-after-free of the fence timeline name
drm/sched/tests: add a UAF regression test for the timeline name
drivers/gpu/drm/scheduler/sched_fence.c | 46 +++++-----
drivers/gpu/drm/scheduler/sched_main.c | 9 ++
drivers/gpu/drm/scheduler/tests/Makefile | 1 +
.../drm/scheduler/tests/tests_integration.c | 92 +++++++++++++++++++
include/drm/gpu_scheduler.h | 22 +++--
5 files changed, 139 insertions(+), 31 deletions(-)
create mode 100644 drivers/gpu/drm/scheduler/tests/tests_integration.c
--
2.43.0
Well, that was a quick investigation ;)
On Wed, 2026-09-02 at 23:42 +0900, Jonghyuk Kim(MalHyuk) wrote:
>
[…]
> Philipp suggested dropping the finished fence's ->release callback instead.
> That is what this series does. dma_fence detaches a fence's ops on signalling
> when it has neither .release nor .wait (dma_fence_signal_timestamp_locked()),
> and dma_fence_timeline_name() returns a static string once the ops are gone.
> So with the callback removed, get_timeline_name() is simply never reached on
> a signalled finished fence - no ->sched dereference at all, for static and
btw, you only ever mention get_timeline_name(), but get_driver_name()
is running into the same issue, isn't it?
> Link to v2 (name caching):
> https://lore.kernel.org/dri-devel/20260902105808.1541063-1-malhyuk97@gmail.com/
That link is dead (weirdly enough. Why isn't it in dri-devel?). Correct
one seems to be:
https://lore.kernel.org/lkml/20260902105808.1541063-1-malhyuk97@gmail.com/
Your help and industriousness is highly appreciated :)
Just be so kind and wait >24h with sending new revisions so that more
folks, especially from different time zones, can jump into the
discussion.
>
> Note: detaching the finished fence's ops on signalling also makes
> to_drm_sched_fence() return NULL for a signalled finished fence. Callers
> already handle NULL (the normal foreign-fence result), a signalled fence is
> an already-satisfied dependency so the scheduler's dependency collapsing is
> unaffected, and it avoids the container_of() on a possibly-freed foreign
> scheduler that amdgpu_sync_same_dev() and pvr_queue_fence_is_native() would
> otherwise do. Flagging it explicitly since it touches an exported helper.
That unfortunately does look a bit dangerous.
Isn't pvr here already a race condition?
if (pvr_queue_fence_is_native(uf)) {
struct drm_sched_fence *s_fence = to_drm_sched_fence(uf);
> I did not add Fixes:/Cc: stable tags: the ->sched->name deref dates back to
> 1b1f42d8fde4 ("drm: move amd_gpu_scheduler into common location") but only
> became reachable once drivers began allocating per-context schedulers, so
> the right attribution is unclear to me. This is stable material as the
> driver instances are live - happy to add whatever tags you prefer.
I think for such cases merely adding Cc: stable and let the stable
folks figure out how far they want to backport is fine. You can hint at
us not knowing since when userspace can access this in a commit
Cc: stable … # we don't know since when
What I'm a bit more nervous about is that we probably really want to
backport this, but it's also a bit regression-endangered. So I suppose
we want to give it careful testing. I hope the others can help with
that, too.
>
> Tested with KUnit under KASAN (kunit.py --arch=x86_64), matched pair:
Did you test with kmemleak? That's always a tool of choice when it
comes to refcounting.
>
> Jonghyuk Kim(MalHyuk) (2):
> drm/sched: fix use-after-free of the fence timeline name
> drm/sched/tests: add a UAF regression test for the timeline name
I answer on those soonish.
Thanks
Philipp
+Cc Alessio, Luigi
On Wed, 2026-09-02 at 18:09 +0200, Philipp Stanner wrote:
> Well, that was a quick investigation ;)
>
> On Wed, 2026-09-02 at 23:42 +0900, Jonghyuk Kim(MalHyuk) wrote:
> >
[…]
>
> >
> > Note: detaching the finished fence's ops on signalling also makes
> > to_drm_sched_fence() return NULL for a signalled finished fence. Callers
> > already handle NULL (the normal foreign-fence result), a signalled fence is
> > an already-satisfied dependency so the scheduler's dependency collapsing is
> > unaffected, and it avoids the container_of() on a possibly-freed foreign
> > scheduler that amdgpu_sync_same_dev() and pvr_queue_fence_is_native() would
> > otherwise do. Flagging it explicitly since it touches an exported helper.
>
> That unfortunately does look a bit dangerous.
>
> Isn't pvr here already a race condition?
>
> if (pvr_queue_fence_is_native(uf)) {
> struct drm_sched_fence *s_fence = to_drm_sched_fence(uf);
I looked through the code base and it seems no one touches the ops
pointer.
The exception is imagination, which uses it to identify whether a fence
stems from itself. So if we implement a change such as the proposed
one, explosions are thinkable:
bool pvr_queue_fence_is_native(struct dma_fence *f)
{
struct drm_sched_fence *sched_fence = f ? to_drm_sched_fence(f) : NULL; // <-- ops pointer still valid, sched_fence != NULL
// race: dma_fence_signal(sched_fence->finished) -> sched->ops becomes NULL
if (sched_fence &&
sched_fence->sched->ops == &pvr_queue_sched_ops)
return true; // might return false now although the fence was created by imagination
return pvr_queue_fence_is_ufo_backed(f);
}
So depending on when the finished-fence gets signaled, the function
could now sometimes return true, then false, depending on how it's
racing. Not entirely sure, depends probably a bit on when imagination
is signaling its hardware fences and so on.
But I'm not entirely sure to what degree we have a problem here, and if
so how we should best solve it.
P.
© 2016 - 2026 Red Hat, Inc.