* [PATCH v3 0/2] drm/sched: fix use-after-free of the fence timeline name
@ 2026-09-02 14:42 Jonghyuk Kim(MalHyuk)
2026-09-02 14:42 ` [PATCH v3 1/2] " Jonghyuk Kim(MalHyuk)
` (2 more replies)
0 siblings, 3 replies; 10+ messages in thread
From: Jonghyuk Kim(MalHyuk) @ 2026-09-02 14:42 UTC (permalink / raw)
To: tursulin, phasta, matthew.brost, dakr
Cc: christian.koenig, dri-devel, linux-kernel, Jonghyuk Kim(MalHyuk)
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
^ permalink raw reply [flat|nested] 10+ messages in thread* [PATCH v3 1/2] drm/sched: fix use-after-free of the fence timeline name 2026-09-02 14:42 [PATCH v3 0/2] drm/sched: fix use-after-free of the fence timeline name Jonghyuk Kim(MalHyuk) @ 2026-09-02 14:42 ` Jonghyuk Kim(MalHyuk) 2026-09-02 14:58 ` sashiko-bot 2026-09-03 8:46 ` Philipp Stanner 2026-09-02 14:42 ` [PATCH v3 2/2] drm/sched/tests: add a UAF regression test for the " Jonghyuk Kim(MalHyuk) 2026-09-02 16:09 ` [PATCH v3 0/2] drm/sched: fix use-after-free of the fence " Philipp Stanner 2 siblings, 2 replies; 10+ messages in thread From: Jonghyuk Kim(MalHyuk) @ 2026-09-02 14:42 UTC (permalink / raw) To: tursulin, phasta, matthew.brost, dakr Cc: christian.koenig, dri-devel, linux-kernel, Jonghyuk Kim(MalHyuk) drm_sched_fence_get_timeline_name() returns fence->sched->name. A driver that allocates a drm_gpu_scheduler per context, queue or VM frees that scheduler when the context is destroyed, but the finished fence can outlive it: unprivileged userspace can hold the exported fence through a sync_file or drm_syncobj and query its timeline name afterwards (e.g. via SYNC_IOC_FILE_INFO), dereferencing the freed scheduler. This is a slab-use-after-free read; once the slab is re-sprayed it becomes an info leak. It 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) allocate per-context schedulers and are still affected. The dma-fence contract already forbids touching driver-provided data - the memory reachable through &dma_fence.ops - once a fence is signalled, and dma_fence_timeline_name() enforces it: after the ops are detached it returns a static string instead of calling get_timeline_name(). dma_fence detaches the ops on signalling, but only for fences that carry neither a .release nor a .wait callback (see dma_fence_signal_timestamp_locked()). The finished fence carried a .release callback solely to drop the scheduled fence's reference. That callback kept the ops attached, leaving get_timeline_name() reachable on a signalled finished fence with a dangling ->sched. Drop the callback and move the reference handling instead: - The scheduled fence now holds a reference on the finished fence, so the finished fence, and with it the shared allocation, is released last. Its release drops the parent fence and that finished-fence reference; the finished fence is then freed from dma_fence_free(). This requires @finished to sit at offset 0 of struct drm_sched_fence, since dma_fence_free() ultimately kfree()s the fence pointer. - drm_sched_job_cleanup() drops the scheduled fence's initial reference, which the removed .release used to cascade. With the finished fence no longer carrying .release its ops are detached on signalling, so get_timeline_name() can no longer run against a freed scheduler. Unlike caching the name string, this also covers drivers whose timeline name is dynamically allocated (drm/panthor, drm/xe). Detaching the ops on signalling also makes to_drm_sched_fence() return NULL for a signalled finished fence. Callers already handle a NULL return - it is the normal result for a foreign fence - and a signalled fence is an already-satisfied dependency, so the scheduler's dependency-collapsing optimisation is unaffected. It additionally avoids the container_of() on a possibly-freed foreign scheduler that amdgpu_sync_same_dev() and pvr_queue_fence_is_native() would otherwise perform. Suggested-by: Philipp Stanner <phasta@kernel.org> Signed-off-by: Jonghyuk Kim(MalHyuk) <malhyuk97@gmail.com> --- drivers/gpu/drm/scheduler/sched_fence.c | 46 ++++++++-------- drivers/gpu/drm/scheduler/sched_main.c | 9 ++++ include/drm/gpu_scheduler.h | 72 ++++++++++++++----------- 3 files changed, 71 insertions(+), 56 deletions(-) diff --git a/drivers/gpu/drm/scheduler/sched_fence.c b/drivers/gpu/drm/scheduler/sched_fence.c index 096fe28aa9c9..f463afa0ee4e 100644 --- a/drivers/gpu/drm/scheduler/sched_fence.c +++ b/drivers/gpu/drm/scheduler/sched_fence.c @@ -95,15 +95,6 @@ static const char *drm_sched_fence_get_timeline_name(struct dma_fence *f) return (const char *)fence->sched->name; } -static void drm_sched_fence_free_rcu(struct rcu_head *rcu) -{ - struct dma_fence *f = container_of(rcu, struct dma_fence, rcu); - struct drm_sched_fence *fence = to_drm_sched_fence(f); - - if (!WARN_ON_ONCE(!fence)) - kmem_cache_free(sched_fence_slab, fence); -} - /** * drm_sched_fence_free - free up an uninitialized fence * @@ -132,21 +123,12 @@ static void drm_sched_fence_release_scheduled(struct dma_fence *f) struct drm_sched_fence *fence = to_drm_sched_fence(f); dma_fence_put(fence->parent); - call_rcu(&fence->finished.rcu, drm_sched_fence_free_rcu); -} - -/** - * drm_sched_fence_release_finished - drop extra reference - * - * @f: fence - * - * Drop the extra reference from the scheduled fence to the base fence. - */ -static void drm_sched_fence_release_finished(struct dma_fence *f) -{ - struct drm_sched_fence *fence = to_drm_sched_fence(f); - - dma_fence_put(&fence->scheduled); + /* + * Drop the reference the scheduled fence holds on the finished fence. + * The finished fence is released last and frees the shared allocation + * from its dma_fence_free() (see drm_sched_fence_init()). + */ + dma_fence_put(&fence->finished); } static void drm_sched_fence_set_deadline_finished(struct dma_fence *f, @@ -189,7 +171,13 @@ static const struct dma_fence_ops drm_sched_fence_ops_scheduled = { static const struct dma_fence_ops drm_sched_fence_ops_finished = { .get_driver_name = drm_sched_fence_get_driver_name, .get_timeline_name = drm_sched_fence_get_timeline_name, - .release = drm_sched_fence_release_finished, + /* + * No .release callback: dma_fence detaches ->ops on signalling for + * fences without .release/.wait, so get_timeline_name() is never called + * on a signalled finished fence and cannot dereference a freed + * scheduler. The shared allocation is freed from dma_fence_free() once + * this fence's refcount drops - it is released last, after @scheduled. + */ .set_deadline = drm_sched_fence_set_deadline_finished, }; @@ -233,6 +221,14 @@ void drm_sched_fence_init(struct drm_sched_fence *fence, &fence->lock, entity->fence_context, seq); dma_fence_init(&fence->finished, &drm_sched_fence_ops_finished, &fence->lock, entity->fence_context + 1, seq); + + /* + * Hold a reference on the finished fence from the scheduled fence, so + * the finished fence (and the shared allocation) outlives @scheduled. + * drm_sched_fence_release_scheduled() drops it; the finished fence is + * therefore released last and frees the allocation via dma_fence_free(). + */ + dma_fence_get(&fence->finished); } module_init(drm_sched_fence_slab_init); diff --git a/drivers/gpu/drm/scheduler/sched_main.c b/drivers/gpu/drm/scheduler/sched_main.c index 6cb6f9546493..fb238f51c0ed 100644 --- a/drivers/gpu/drm/scheduler/sched_main.c +++ b/drivers/gpu/drm/scheduler/sched_main.c @@ -842,6 +842,15 @@ void drm_sched_job_cleanup(struct drm_sched_job *job) * been called. */ dma_fence_put(&job->s_fence->finished); + /* + * Drop the initial reference on the scheduled fence. It no + * longer has a .release callback dropping it (the finished + * fence's .release was removed to allow ops-detach on signal), + * so the last put here lets drm_sched_fence_release_scheduled() + * run, which drops @parent and the scheduled fence's reference + * on @finished. @finished is freed last, from dma_fence_free(). + */ + dma_fence_put(&job->s_fence->scheduled); drm_sched_entity_stats_put(job->entity_stats); } else { /* The job was aborted before it has been committed to be run; diff --git a/include/drm/gpu_scheduler.h b/include/drm/gpu_scheduler.h index 7a64cc11de08..686c3687944f 100644 --- a/include/drm/gpu_scheduler.h +++ b/include/drm/gpu_scheduler.h @@ -287,48 +287,58 @@ struct drm_sched_rq { * struct drm_sched_fence - fences corresponding to the scheduling of a job. */ struct drm_sched_fence { - /** - * @scheduled: this fence is what will be signaled by the scheduler - * when the job is scheduled. - */ - struct dma_fence scheduled; - - /** - * @finished: this fence is what will be signaled by the scheduler - * when the job is completed. - * - * When setting up an out fence for the job, you should use - * this, since it's available immediately upon - * drm_sched_job_init(), and the fence returned by the driver - * from run_job() won't be created until the dependencies have - * resolved. - */ + /** + * @finished: this fence is what will be signaled by the scheduler + * when the job is completed. + * + * When setting up an out fence for the job, you should use + * this, since it's available immediately upon + * drm_sched_job_init(), and the fence returned by the driver + * from run_job() won't be created until the dependencies have + * resolved. + * + * @finished is kept first in the struct: it is the fence exported to + * userspace and therefore the one whose &dma_fence_ops.release is + * dropped so that dma_fence detaches its ops on signalling. It is + * released last (see &drm_sched_fence.scheduled) and frees the whole + * object via dma_fence_free(), which requires it to sit at offset 0. + */ struct dma_fence finished; + /** + * @scheduled: this fence is what will be signaled by the scheduler + * when the job is scheduled. + * + * It holds a reference on @finished so that the shared allocation is + * released only after @scheduled itself is done; its release drops + * that reference and the @parent one. + */ + struct dma_fence scheduled; + /** * @deadline: deadline set on &drm_sched_fence.finished which * potentially needs to be propagated to &drm_sched_fence.parent */ ktime_t deadline; - /** - * @parent: the fence returned by &drm_sched_backend_ops.run_job - * when scheduling the job on hardware. We signal the - * &drm_sched_fence.finished fence once parent is signalled. - */ + /** + * @parent: the fence returned by &drm_sched_backend_ops.run_job + * when scheduling the job on hardware. We signal the + * &drm_sched_fence.finished fence once parent is signalled. + */ struct dma_fence *parent; - /** - * @sched: the scheduler instance to which the job having this struct - * belongs to. - */ + /** + * @sched: the scheduler instance to which the job having this struct + * belongs to. + */ struct drm_gpu_scheduler *sched; - /** - * @lock: the lock used by the scheduled and the finished fences. - */ + /** + * @lock: the lock used by the scheduled and the finished fences. + */ spinlock_t lock; - /** - * @owner: job owner for debugging - */ + /** + * @owner: job owner for debugging + */ void *owner; /** -- 2.43.0 ^ permalink raw reply related [flat|nested] 10+ messages in thread
* Re: [PATCH v3 1/2] drm/sched: fix use-after-free of the fence timeline name 2026-09-02 14:42 ` [PATCH v3 1/2] " Jonghyuk Kim(MalHyuk) @ 2026-09-02 14:58 ` sashiko-bot 2026-09-03 8:46 ` Philipp Stanner 1 sibling, 0 replies; 10+ messages in thread From: sashiko-bot @ 2026-09-02 14:58 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: - [Critical] Unconditional dereference of `to_drm_sched_fence()` return value causes a deterministic kernel crash for signaled finished fences in downstream drivers. - [High] TOCTOU race during ops detachment causes `to_drm_sched_fence()` to return NULL, leading to a NULL pointer dereference in ops callbacks. Pre-existing issues: - [High] The scheduled fence retains its `.release` callback, leaving it vulnerable to the same use-after-free of the scheduler timeline name if it outlives the scheduler. -- commit cbf4f1124a22fb62bedbf4d0c94b024440ce5beb Author: Jonghyuk Kim(MalHyuk) <malhyuk97@gmail.com> drm/sched: fix use-after-free of the fence timeline name This commit aims to resolve a use-after-free vulnerability involving the fence timeline name by removing the .release callback from the finished fence. This allows dma_fence to detach the fence operations on signaling, while restructuring references so the scheduled fence holds a reference to the finished fence. > diff --git a/drivers/gpu/drm/scheduler/sched_fence.c b/drivers/gpu/drm/scheduler/sched_fence.c > --- a/drivers/gpu/drm/scheduler/sched_fence.c > +++ b/drivers/gpu/drm/scheduler/sched_fence.c > @@ -95,15 +95,6 @@ static const char *drm_sched_fence_get_timeline_name(struct dma_fence *f) > return (const char *)fence->sched->name; [Severity: High] Can a time-of-check to time-of-use race occur here leading to a null pointer dereference? Code paths like dma_fence_timeline_name() locklessly read f->ops under RCU. If another thread concurrently signals the fence, dma_fence_signal() will detach the ops by setting f->ops to NULL. If the first thread reads the valid &drm_sched_fence_ops_finished before the detachment, but calls ops->get_timeline_name(fence) after, to_drm_sched_fence() will re-read f->ops (which is now NULL) and return NULL. Since fence is unconditionally dereferenced in the return statement above, and similarly in drm_sched_fence_set_deadline_finished() for the deadline field, could this race window cause a regression if hit? Userspace could potentially trigger this by invoking SYNC_IOC_FILE_INFO while a job completes. > } [ ... ] > @@ -189,7 +171,13 @@ static const struct dma_fence_ops drm_sched_fence_ops_scheduled = { [Severity: High] This isn't a bug introduced by this patch, but does the scheduled fence remain vulnerable to the same use-after-free issue? This patch removes the .release callback from drm_sched_fence_ops_finished to allow ops detachment on signaling. However, drm_sched_fence_ops_scheduled retains its .release callback (drm_sched_fence_release_scheduled). Because it has a .release callback, dma_fence_signal() will not detach the ops for the scheduled fence. If a subsystem holds a reference to the scheduled fence after the scheduler is destroyed, won't any subsequent timeline name queries dereference the dangling fence->sched->name pointer, triggering the same slab-use-after-free this patch addresses? > static const struct dma_fence_ops drm_sched_fence_ops_finished = { > .get_driver_name = drm_sched_fence_get_driver_name, > .get_timeline_name = drm_sched_fence_get_timeline_name, > - .release = drm_sched_fence_release_finished, > + /* > + * No .release callback: dma_fence detaches ->ops on signalling for > + * fences without .release/.wait, so get_timeline_name() is never called > + * on a signalled finished fence and cannot dereference a freed > + * scheduler. The shared allocation is freed from dma_fence_free() once > + * this fence's refcount drops - it is released last, after @scheduled. > + */ [Severity: Critical] Does removing the .release callback here cause a regression in downstream drivers, resulting in a deterministic crash? Because the ops are now detached on signaling (setting f->ops to NULL), to_drm_sched_fence() will return NULL for a signaled finished fence. However, downstream drivers assume their scheduler fences are always recognized and unconditionally dereference the result of to_drm_sched_fence(). For example, in amdgpu_cs_p2_dependencies(): drivers/gpu/drm/amd/amdgpu/amdgpu_cs.c:amdgpu_cs_p2_dependencies() { ... s_fence = to_drm_sched_fence(fence); fence = dma_fence_get(&s_fence->scheduled); ... } And in amdgpu_ctx_fence_time(): drivers/gpu/drm/amd/amdgpu/amdgpu_ctx.c:amdgpu_ctx_fence_time() { ... s_fence = to_drm_sched_fence(fence); if (!test_bit(DMA_FENCE_FLAG_TIMESTAMP_BIT, &s_fence->scheduled.flags)) ... } When an unprivileged user closes a context with completed jobs or queries dependencies on completed jobs, won't this result in an immediate null pointer dereference? > .set_deadline = drm_sched_fence_set_deadline_finished, > }; -- Sashiko AI review · https://sashiko.dev/#/patchset/20260902144204.1843670-1-malhyuk97@gmail.com?part=1 ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v3 1/2] drm/sched: fix use-after-free of the fence timeline name 2026-09-02 14:42 ` [PATCH v3 1/2] " Jonghyuk Kim(MalHyuk) 2026-09-02 14:58 ` sashiko-bot @ 2026-09-03 8:46 ` Philipp Stanner 2026-09-03 10:02 ` Christian König 2026-09-03 10:22 ` Christian König 1 sibling, 2 replies; 10+ messages in thread From: Philipp Stanner @ 2026-09-03 8:46 UTC (permalink / raw) To: Jonghyuk Kim(MalHyuk), tursulin, phasta, matthew.brost, dakr Cc: christian.koenig, dri-devel, linux-kernel On Wed, 2026-09-02 at 23:42 +0900, Jonghyuk Kim(MalHyuk) wrote: > […] > > The finished fence carried a .release callback solely to drop the > scheduled fence's reference. That callback kept the ops attached, leaving > get_timeline_name() reachable on a signalled finished fence with a > dangling ->sched. Drop the callback and move the reference handling > instead: > > - The scheduled fence now holds a reference on the finished fence, so > the finished fence, and with it the shared allocation, is released > last. Its release drops the parent fence and that finished-fence > reference; the finished fence is then freed from dma_fence_free(). The backend doesn't know about this (admittedly weird) shared- allocation phenomenon. I suppose the reason why we don't run into double-free is that the scheduled-fence does still implement ops- >release. If that's the case, that needs to be documented in the code as a groundlayer for future cleanups (I suppose we should eliminate that shared allocation. If objects have distinct lifetimes, they should have distinct memory. It was probably done like that so that drivers can access both subfences through container_of()). a la "TODO: this release callback should be removed, too, but can't because double-free" > scheduler. Unlike caching the name string, this also covers drivers whose > timeline name is dynamically allocated (drm/panthor, drm/xe). It's good to have detailed commit messages, but I don't think hinting at that alternative solution (caching), which we don't implement, is necessary. > […] > - it is the normal result for a foreign fence - and a signalled fence is > an already-satisfied dependency, so the scheduler's dependency-collapsing > optimisation is unaffected. > I don't understand this phrase. You're saying that someone trying to register a dependency won't bother if he sees NULL? > > […] > > -static void drm_sched_fence_free_rcu(struct rcu_head *rcu) > -{ > - struct dma_fence *f = container_of(rcu, struct dma_fence, rcu); > - struct drm_sched_fence *fence = to_drm_sched_fence(f); > - > - if (!WARN_ON_ONCE(!fence)) > - kmem_cache_free(sched_fence_slab, fence); > -} > - > /** > * drm_sched_fence_free - free up an uninitialized fence > * > @@ -132,21 +123,12 @@ static void drm_sched_fence_release_scheduled(struct dma_fence *f) > struct drm_sched_fence *fence = to_drm_sched_fence(f); > > dma_fence_put(fence->parent); > - call_rcu(&fence->finished.rcu, drm_sched_fence_free_rcu); @Christian, Tvrtko, opinions on that? > -} > - > -/** > - * drm_sched_fence_release_finished - drop extra reference > - * > - * @f: fence > - * > - * Drop the extra reference from the scheduled fence to the base fence. > - */ > -static void drm_sched_fence_release_finished(struct dma_fence *f) > -{ > - struct drm_sched_fence *fence = to_drm_sched_fence(f); > - > - dma_fence_put(&fence->scheduled); > + /* > + * Drop the reference the scheduled fence holds on the finished fence. > + * The finished fence is released last and frees the shared allocation > + * from its dma_fence_free() (see drm_sched_fence_init()). > + */ > + dma_fence_put(&fence->finished); > } > > static void drm_sched_fence_set_deadline_finished(struct dma_fence *f, > @@ -189,7 +171,13 @@ static const struct dma_fence_ops drm_sched_fence_ops_scheduled = { > static const struct dma_fence_ops drm_sched_fence_ops_finished = { > .get_driver_name = drm_sched_fence_get_driver_name, > .get_timeline_name = drm_sched_fence_get_timeline_name, > - .release = drm_sched_fence_release_finished, > + /* > + * No .release callback: dma_fence detaches ->ops on signalling for > + * fences without .release/.wait, so get_timeline_name() is never called > + * on a signalled finished fence and cannot dereference a freed > + * scheduler. The shared allocation is freed from dma_fence_free() once > + * this fence's refcount drops - it is released last, after @scheduled. > + */ That comment is not necessary. The new code simply complies with the current idiomatic fence design. Comments are necessary at tricky bits or when one deviates from idiomatic usage. > .set_deadline = drm_sched_fence_set_deadline_finished, > }; > > @@ -233,6 +221,14 @@ void drm_sched_fence_init(struct drm_sched_fence *fence, > &fence->lock, entity->fence_context, seq); > dma_fence_init(&fence->finished, &drm_sched_fence_ops_finished, > &fence->lock, entity->fence_context + 1, seq); > + > + /* > + * Hold a reference on the finished fence from the scheduled fence, so > + * the finished fence (and the shared allocation) outlives @scheduled. > + * drm_sched_fence_release_scheduled() drops it; the finished fence is > + * therefore released last and frees the allocation via dma_fence_free(). > + */ > + dma_fence_get(&fence->finished); So what was the counter-part of this dma_fence_get() before? > } > > module_init(drm_sched_fence_slab_init); > diff --git a/drivers/gpu/drm/scheduler/sched_main.c b/drivers/gpu/drm/scheduler/sched_main.c > index 6cb6f9546493..fb238f51c0ed 100644 > --- a/drivers/gpu/drm/scheduler/sched_main.c > +++ b/drivers/gpu/drm/scheduler/sched_main.c > @@ -842,6 +842,15 @@ void drm_sched_job_cleanup(struct drm_sched_job *job) > * been called. > */ > dma_fence_put(&job->s_fence->finished); > + /* > + * Drop the initial reference on the scheduled fence. It no > + * longer has a .release callback dropping it (the finished > + * fence's .release was removed to allow ops-detach on signal), Same as above, I think the comment should not focus on the past situation. The past should be tracked by the commit message; a comment should only mention the past if it's still relevant, for example to solve an open TODO. > + * so the last put here lets drm_sched_fence_release_scheduled() Strictly speaking, you don't know whether it's the last put(). drm_sched_fence is a public object and drivers might have taken various references. > + * run, which drops @parent and the scheduled fence's reference > + * on @finished. @finished is freed last, from dma_fence_free(). finished and scheduled would be freed through dma_fence_free() simultaneously, since they still share the allocation. I think the shared allocation and how things are freed should be documented, but is drm_sched_job_cleanup() the right place? > + */ > + dma_fence_put(&job->s_fence->scheduled); > drm_sched_entity_stats_put(job->entity_stats); > } else { > /* The job was aborted before it has been committed to be run; > diff --git a/include/drm/gpu_scheduler.h b/include/drm/gpu_scheduler.h > index 7a64cc11de08..686c3687944f 100644 > --- a/include/drm/gpu_scheduler.h > +++ b/include/drm/gpu_scheduler.h > @@ -287,48 +287,58 @@ struct drm_sched_rq { > * struct drm_sched_fence - fences corresponding to the scheduling of a job. > */ > struct drm_sched_fence { > […] > struct dma_fence finished; > > + /** > + * @scheduled: this fence is what will be signaled by the scheduler > + * when the job is scheduled. > + * > + * It holds a reference on @finished so that the shared allocation is > + * released only after @scheduled itself is done; its release drops Won't it drop the endire drm_sched_fence? I think this struct's documentation is the right place to document the life time and allocation pattern, together with maybe the relevant places in sched_fence.c Then you could be a bit less verbose in the other code places; see above. > + * that reference and the @parent one. > + */ > + struct dma_fence scheduled; > + > /** > * @deadline: deadline set on &drm_sched_fence.finished which > * potentially needs to be propagated to &drm_sched_fence.parent > */ > ktime_t deadline; > > - /** > - * @parent: the fence returned by &drm_sched_backend_ops.run_job > - * when scheduling the job on hardware. We signal the > - * &drm_sched_fence.finished fence once parent is signalled. > - */ > + /** > + * @parent: the fence returned by &drm_sched_backend_ops.run_job > + * when scheduling the job on hardware. We signal the > + * &drm_sched_fence.finished fence once parent is signalled. > + */ > struct dma_fence *parent; > - /** > - * @sched: the scheduler instance to which the job having this struct > - * belongs to. > - */ > + /** > + * @sched: the scheduler instance to which the job having this struct > + * belongs to. > + */ > struct drm_gpu_scheduler *sched; > - /** > - * @lock: the lock used by the scheduled and the finished fences. > - */ > + /** > + * @lock: the lock used by the scheduled and the finished fences. > + */ > spinlock_t lock; > - /** > - * @owner: job owner for debugging > - */ > + /** > + * @owner: job owner for debugging > + */ > void *owner; Formatting fixes in a separate patch please, unless you need to modify those lines for semantically related reasons. Thanks P. ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v3 1/2] drm/sched: fix use-after-free of the fence timeline name 2026-09-03 8:46 ` Philipp Stanner @ 2026-09-03 10:02 ` Christian König 2026-09-03 10:22 ` Christian König 1 sibling, 0 replies; 10+ messages in thread From: Christian König @ 2026-09-03 10:02 UTC (permalink / raw) To: phasta, Jonghyuk Kim(MalHyuk), tursulin, matthew.brost, dakr Cc: dri-devel, linux-kernel On 9/3/26 10:46, Philipp Stanner wrote: > On Wed, 2026-09-02 at 23:42 +0900, Jonghyuk Kim(MalHyuk) wrote: >> > > […] > >> >> The finished fence carried a .release callback solely to drop the >> scheduled fence's reference. That callback kept the ops attached, leaving >> get_timeline_name() reachable on a signalled finished fence with a >> dangling ->sched. Drop the callback and move the reference handling >> instead: >> >> - The scheduled fence now holds a reference on the finished fence, so >> the finished fence, and with it the shared allocation, is released >> last. Its release drops the parent fence and that finished-fence >> reference; the finished fence is then freed from dma_fence_free(). > > The backend doesn't know about this (admittedly weird) shared- > allocation phenomenon. I suppose the reason why we don't run into > double-free is that the scheduled-fence does still implement ops- >> release. > > If that's the case, that needs to be documented in the code as a > groundlayer for future cleanups (I suppose we should eliminate that > shared allocation. If objects have distinct lifetimes, they should have > distinct memory. It was probably done like that so that drivers can > access both subfences through container_of()). > > a la Yes, the reason why that construct is necessary is because to_drm_sched_fence() needs to be able to cast from both scheduled fence and finished fence back to the parent object. IIRC that was used by both the scheduler itself as well as and drivers, but I'm not sure if that is still the case. At least in amdgpu I tried to avoid casting from scheduled -> finished and only do from finished -> scheduled. > > "TODO: this release callback should be removed, too, but can't because > double-free" Yes, I pointed that out before as well. But those release callbacks are actually unproblematic for the problem at hand as far as I can see. Regards, Christian. > >> scheduler. Unlike caching the name string, this also covers drivers whose >> timeline name is dynamically allocated (drm/panthor, drm/xe). > > It's good to have detailed commit messages, but I don't think hinting > at that alternative solution (caching), which we don't implement, is > necessary. > >> > > […] > >> - it is the normal result for a foreign fence - and a signalled fence is >> an already-satisfied dependency, so the scheduler's dependency-collapsing >> optimisation is unaffected. >> > > I don't understand this phrase. You're saying that someone trying to > register a dependency won't bother if he sees NULL? > > >> >> > > […] > >> >> -static void drm_sched_fence_free_rcu(struct rcu_head *rcu) >> -{ >> - struct dma_fence *f = container_of(rcu, struct dma_fence, rcu); >> - struct drm_sched_fence *fence = to_drm_sched_fence(f); >> - >> - if (!WARN_ON_ONCE(!fence)) >> - kmem_cache_free(sched_fence_slab, fence); >> -} >> - >> /** >> * drm_sched_fence_free - free up an uninitialized fence >> * >> @@ -132,21 +123,12 @@ static void drm_sched_fence_release_scheduled(struct dma_fence *f) >> struct drm_sched_fence *fence = to_drm_sched_fence(f); >> >> dma_fence_put(fence->parent); >> - call_rcu(&fence->finished.rcu, drm_sched_fence_free_rcu); > > @Christian, Tvrtko, opinions on that? > >> -} >> - >> -/** >> - * drm_sched_fence_release_finished - drop extra reference >> - * >> - * @f: fence >> - * >> - * Drop the extra reference from the scheduled fence to the base fence. >> - */ >> -static void drm_sched_fence_release_finished(struct dma_fence *f) >> -{ >> - struct drm_sched_fence *fence = to_drm_sched_fence(f); >> - >> - dma_fence_put(&fence->scheduled); >> + /* >> + * Drop the reference the scheduled fence holds on the finished fence. >> + * The finished fence is released last and frees the shared allocation >> + * from its dma_fence_free() (see drm_sched_fence_init()). >> + */ >> + dma_fence_put(&fence->finished); >> } >> >> static void drm_sched_fence_set_deadline_finished(struct dma_fence *f, >> @@ -189,7 +171,13 @@ static const struct dma_fence_ops drm_sched_fence_ops_scheduled = { >> static const struct dma_fence_ops drm_sched_fence_ops_finished = { >> .get_driver_name = drm_sched_fence_get_driver_name, >> .get_timeline_name = drm_sched_fence_get_timeline_name, >> - .release = drm_sched_fence_release_finished, >> + /* >> + * No .release callback: dma_fence detaches ->ops on signalling for >> + * fences without .release/.wait, so get_timeline_name() is never called >> + * on a signalled finished fence and cannot dereference a freed >> + * scheduler. The shared allocation is freed from dma_fence_free() once >> + * this fence's refcount drops - it is released last, after @scheduled. >> + */ > > That comment is not necessary. The new code simply complies with the > current idiomatic fence design. Comments are necessary at tricky bits > or when one deviates from idiomatic usage. > >> .set_deadline = drm_sched_fence_set_deadline_finished, >> }; >> >> @@ -233,6 +221,14 @@ void drm_sched_fence_init(struct drm_sched_fence *fence, >> &fence->lock, entity->fence_context, seq); >> dma_fence_init(&fence->finished, &drm_sched_fence_ops_finished, >> &fence->lock, entity->fence_context + 1, seq); >> + >> + /* >> + * Hold a reference on the finished fence from the scheduled fence, so >> + * the finished fence (and the shared allocation) outlives @scheduled. >> + * drm_sched_fence_release_scheduled() drops it; the finished fence is >> + * therefore released last and frees the allocation via dma_fence_free(). >> + */ >> + dma_fence_get(&fence->finished); > > So what was the counter-part of this dma_fence_get() before? > >> } >> >> module_init(drm_sched_fence_slab_init); >> diff --git a/drivers/gpu/drm/scheduler/sched_main.c b/drivers/gpu/drm/scheduler/sched_main.c >> index 6cb6f9546493..fb238f51c0ed 100644 >> --- a/drivers/gpu/drm/scheduler/sched_main.c >> +++ b/drivers/gpu/drm/scheduler/sched_main.c >> @@ -842,6 +842,15 @@ void drm_sched_job_cleanup(struct drm_sched_job *job) >> * been called. >> */ >> dma_fence_put(&job->s_fence->finished); >> + /* >> + * Drop the initial reference on the scheduled fence. It no >> + * longer has a .release callback dropping it (the finished >> + * fence's .release was removed to allow ops-detach on signal), > > Same as above, I think the comment should not focus on the past > situation. The past should be tracked by the commit message; a comment > should only mention the past if it's still relevant, for example to > solve an open TODO. > >> + * so the last put here lets drm_sched_fence_release_scheduled() > > Strictly speaking, you don't know whether it's the last put(). > drm_sched_fence is a public object and drivers might have taken various > references. > >> + * run, which drops @parent and the scheduled fence's reference >> + * on @finished. @finished is freed last, from dma_fence_free(). > > finished and scheduled would be freed through dma_fence_free() > simultaneously, since they still share the allocation. > > I think the shared allocation and how things are freed should be > documented, but is drm_sched_job_cleanup() the right place? > > >> + */ >> + dma_fence_put(&job->s_fence->scheduled); >> drm_sched_entity_stats_put(job->entity_stats); >> } else { >> /* The job was aborted before it has been committed to be run; >> diff --git a/include/drm/gpu_scheduler.h b/include/drm/gpu_scheduler.h >> index 7a64cc11de08..686c3687944f 100644 >> --- a/include/drm/gpu_scheduler.h >> +++ b/include/drm/gpu_scheduler.h >> @@ -287,48 +287,58 @@ struct drm_sched_rq { >> * struct drm_sched_fence - fences corresponding to the scheduling of a job. >> */ >> struct drm_sched_fence { >> > > […] > >> struct dma_fence finished; >> >> + /** >> + * @scheduled: this fence is what will be signaled by the scheduler >> + * when the job is scheduled. >> + * >> + * It holds a reference on @finished so that the shared allocation is >> + * released only after @scheduled itself is done; its release drops > > Won't it drop the endire drm_sched_fence? > > I think this struct's documentation is the right place to document the > life time and allocation pattern, together with maybe the relevant > places in sched_fence.c > > Then you could be a bit less verbose in the other code places; see > above. > >> + * that reference and the @parent one. >> + */ >> + struct dma_fence scheduled; >> + >> /** >> * @deadline: deadline set on &drm_sched_fence.finished which >> * potentially needs to be propagated to &drm_sched_fence.parent >> */ >> ktime_t deadline; >> >> - /** >> - * @parent: the fence returned by &drm_sched_backend_ops.run_job >> - * when scheduling the job on hardware. We signal the >> - * &drm_sched_fence.finished fence once parent is signalled. >> - */ >> + /** >> + * @parent: the fence returned by &drm_sched_backend_ops.run_job >> + * when scheduling the job on hardware. We signal the >> + * &drm_sched_fence.finished fence once parent is signalled. >> + */ >> struct dma_fence *parent; >> - /** >> - * @sched: the scheduler instance to which the job having this struct >> - * belongs to. >> - */ >> + /** >> + * @sched: the scheduler instance to which the job having this struct >> + * belongs to. >> + */ >> struct drm_gpu_scheduler *sched; >> - /** >> - * @lock: the lock used by the scheduled and the finished fences. >> - */ >> + /** >> + * @lock: the lock used by the scheduled and the finished fences. >> + */ >> spinlock_t lock; >> - /** >> - * @owner: job owner for debugging >> - */ >> + /** >> + * @owner: job owner for debugging >> + */ >> void *owner; > > Formatting fixes in a separate patch please, unless you need to modify > those lines for semantically related reasons. > > > Thanks > P. ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v3 1/2] drm/sched: fix use-after-free of the fence timeline name 2026-09-03 8:46 ` Philipp Stanner 2026-09-03 10:02 ` Christian König @ 2026-09-03 10:22 ` Christian König 1 sibling, 0 replies; 10+ messages in thread From: Christian König @ 2026-09-03 10:22 UTC (permalink / raw) To: phasta, Jonghyuk Kim(MalHyuk), tursulin, matthew.brost, dakr Cc: dri-devel, linux-kernel On 9/3/26 10:46, Philipp Stanner wrote: > On Wed, 2026-09-02 at 23:42 +0900, Jonghyuk Kim(MalHyuk) wrote: ... >> -} >> - >> -/** >> - * drm_sched_fence_release_finished - drop extra reference >> - * >> - * @f: fence >> - * >> - * Drop the extra reference from the scheduled fence to the base fence. >> - */ >> -static void drm_sched_fence_release_finished(struct dma_fence *f) >> -{ >> - struct drm_sched_fence *fence = to_drm_sched_fence(f); >> - >> - dma_fence_put(&fence->scheduled); >> + /* >> + * Drop the reference the scheduled fence holds on the finished fence. >> + * The finished fence is released last and frees the shared allocation >> + * from its dma_fence_free() (see drm_sched_fence_init()). >> + */ >> + dma_fence_put(&fence->finished); That doesn't looks correct to me. The reference *must* be from the finished to the scheduled fence and not the other way around. Background is that there are a lot of use cases which needs to convert from finished to scheduled and only a handful which does the other way around and in those cases we also hold a reference to the finished fence. That here would completely break this. Regards, Christian. ^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH v3 2/2] drm/sched/tests: add a UAF regression test for the timeline name 2026-09-02 14:42 [PATCH v3 0/2] drm/sched: fix use-after-free of the fence timeline name Jonghyuk Kim(MalHyuk) 2026-09-02 14:42 ` [PATCH v3 1/2] " Jonghyuk Kim(MalHyuk) @ 2026-09-02 14:42 ` Jonghyuk Kim(MalHyuk) 2026-09-02 15:04 ` sashiko-bot 2026-09-02 16:09 ` [PATCH v3 0/2] drm/sched: fix use-after-free of the fence " Philipp Stanner 2 siblings, 1 reply; 10+ messages in thread From: Jonghyuk Kim(MalHyuk) @ 2026-09-02 14:42 UTC (permalink / raw) To: tursulin, phasta, matthew.brost, dakr Cc: christian.koenig, dri-devel, linux-kernel, Jonghyuk Kim(MalHyuk) Add a KUnit test that reproduces the drm_sched_fence timeline-name use-after-free fixed by the previous patch. It submits a job on the mock scheduler, takes an independent reference on the finished fence (standing in for a userspace sync_file), lets the job finish, frees the scheduler, and then queries the timeline name through dma_fence_timeline_name(). Without the fix the finished fence keeps its ops attached after signalling, so this dereferences fence->sched of the freed scheduler and KASAN reports a slab-use-after-free read in drm_sched_fence_get_timeline_name(); with the fix the ops are detached on signalling and a static string is returned. The test needs no hardware - it exercises the drm_sched core through the existing mock scheduler under KASAN. Per review it lives in a new tests_integration.c rather than in tests_basic.c, since it is about the scheduler's interaction with the dma-fence API rather than scheduler behaviour in isolation. Signed-off-by: Jonghyuk Kim(MalHyuk) <malhyuk97@gmail.com> --- drivers/gpu/drm/scheduler/tests/Makefile | 1 + .../drm/scheduler/tests/tests_integration.c | 92 +++++++++++++++++++ 2 files changed, 93 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..5e1ca6c6fa5a --- /dev/null +++ b/drivers/gpu/drm/scheduler/tests/tests_integration.c @@ -0,0 +1,92 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include <linux/dma-fence.h> +#include <linux/rcupdate.h> + +#include "sched_tests.h" + +/* + * Integration-style regression tests that exercise the interaction between the + * DRM scheduler and the dma-fence API, rather than scheduler behaviour in + * isolation. + */ + +/* + * Reproduce the drm_sched_fence timeline-name use-after-free. + * + * drm_sched_fence_get_timeline_name() dereferences fence->sched. A driver may + * free a per-context/per-queue/per-VM drm_gpu_scheduler while userspace still + * holds the exported ->finished fence (via sync_file / drm_syncobj). Querying + * the timeline name afterwards must not touch the freed scheduler. + * + * The dma-fence contract only permits access to driver-provided data (which + * includes the memory reached through &dma_fence.ops) before the fence is + * signalled. dma_fence_timeline_name() enforces this by returning a static + * string once the ops have been detached on signalling. For that detach to + * happen the finished fence must not carry a .release (or .wait) callback. + * + * Without the fix (finished fence keeps a .release callback) the ops are never + * detached, so this reads fence->sched->name from freed slab memory and KASAN + * reports a slab-use-after-free. Same class as CVE-2025-38703 (drm/xe) and + * CVE-2025-71302 (drm/panthor). + */ +static void drm_sched_dma_fence_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 __rcu *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); + + /* 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 scheduled (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. + * kunit_kfree() releases the backing memory immediately (rather than at + * test teardown) so that fence->sched becomes a dangling pointer now. + */ + drm_mock_sched_entity_free(entity); + drm_mock_sched_fini(sched); + kunit_kfree(test, sched); + + /* + * Query the timeline name of the now-stale fence. With the fix the ops + * have been detached on signalling and a static string is returned; + * without it this is a use-after-free read of the freed scheduler. + */ + rcu_read_lock(); + name = dma_fence_timeline_name(finished); + KUNIT_EXPECT_NOT_NULL(test, name); + rcu_read_unlock(); + + dma_fence_put(finished); +} + +static struct kunit_case drm_sched_dma_fence_tests[] = { + KUNIT_CASE(drm_sched_dma_fence_uaf), + {} +}; + +static struct kunit_suite drm_sched_dma_fence = { + .name = "drm-sched-dma-fence-uaf", + .test_cases = drm_sched_dma_fence_tests, +}; + +kunit_test_suite(drm_sched_dma_fence); -- 2.43.0 ^ permalink raw reply related [flat|nested] 10+ messages in thread
* Re: [PATCH v3 2/2] drm/sched/tests: add a UAF regression test for the timeline name 2026-09-02 14:42 ` [PATCH v3 2/2] drm/sched/tests: add a UAF regression test for the " Jonghyuk Kim(MalHyuk) @ 2026-09-02 15:04 ` sashiko-bot 0 siblings, 0 replies; 10+ messages in thread From: sashiko-bot @ 2026-09-02 15:04 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 abortion on `KUNIT_ASSERT_TRUE` failure skips manual cleanup, causing Use-After-Free crashes from background tasks. -- commit a2c80bbffa3f457ad719c5864b695730ec7e1976 Author: Jonghyuk Kim(MalHyuk) <malhyuk97@gmail.com> drm/sched/tests: add a UAF regression test for the timeline name This commit adds an integration KUnit test to reproduce a use-after-free in the drm_sched_fence timeline-name. It exercises the interaction between the DRM scheduler and the dma-fence API by submitting a job, freeing the scheduler, and querying the timeline name. > 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..5e1ca6c6fa5a2 > --- /dev/null > +++ b/drivers/gpu/drm/scheduler/tests/tests_integration.c [ ... ] > +static void drm_sched_dma_fence_uaf(struct kunit *test) > +{ [ ... ] > + /* Let the job get scheduled (hw fence created), then signal + finish. */ > + done = drm_mock_sched_job_wait_scheduled(job, HZ); > + KUNIT_ASSERT_TRUE(test, done); [Severity: Medium] If this assertion fails, the test aborts and skips manual cleanup like drm_mock_sched_fini(). Because KUnit automatically frees memory allocated with kunit_kzalloc() upon test abortion, active background tasks such as DRM scheduler workqueues and job hrtimers will access freed memory. Does this cause a use-after-free crash if the wait times out and returns false? > + drm_mock_sched_advance(sched, 1); > + done = drm_mock_sched_job_wait_finished(job, HZ); > + KUNIT_ASSERT_TRUE(test, done); [Severity: Medium] Similarly, can a failure on this assertion cause a use-after-free by aborting the test before the cleanup code runs? > + > + /* > + * Free the per-context scheduler while the finished fence is held. > + * kunit_kfree() releases the backing memory immediately (rather than at > + * test teardown) so that fence->sched becomes a dangling pointer now. > + */ > + drm_mock_sched_entity_free(entity); > + drm_mock_sched_fini(sched); [ ... ] -- Sashiko AI review · https://sashiko.dev/#/patchset/20260902144204.1843670-1-malhyuk97@gmail.com?part=2 ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v3 0/2] drm/sched: fix use-after-free of the fence timeline name 2026-09-02 14:42 [PATCH v3 0/2] drm/sched: fix use-after-free of the fence timeline name Jonghyuk Kim(MalHyuk) 2026-09-02 14:42 ` [PATCH v3 1/2] " Jonghyuk Kim(MalHyuk) 2026-09-02 14:42 ` [PATCH v3 2/2] drm/sched/tests: add a UAF regression test for the " Jonghyuk Kim(MalHyuk) @ 2026-09-02 16:09 ` Philipp Stanner 2026-09-02 17:25 ` Philipp Stanner 2 siblings, 1 reply; 10+ messages in thread From: Philipp Stanner @ 2026-09-02 16:09 UTC (permalink / raw) To: Jonghyuk Kim(MalHyuk), tursulin, phasta, matthew.brost, dakr Cc: christian.koenig, dri-devel, linux-kernel, Michel Daenzer 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 ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v3 0/2] drm/sched: fix use-after-free of the fence timeline name 2026-09-02 16:09 ` [PATCH v3 0/2] drm/sched: fix use-after-free of the fence " Philipp Stanner @ 2026-09-02 17:25 ` Philipp Stanner 0 siblings, 0 replies; 10+ messages in thread From: Philipp Stanner @ 2026-09-02 17:25 UTC (permalink / raw) To: phasta, Jonghyuk Kim(MalHyuk), tursulin, matthew.brost, dakr Cc: christian.koenig, dri-devel, linux-kernel, Michel Daenzer, Alessio Belle, Luigi Santivetti +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. ^ permalink raw reply [flat|nested] 10+ messages in thread
end of thread, other threads:[~2026-09-03 10:22 UTC | newest] Thread overview: 10+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-09-02 14:42 [PATCH v3 0/2] drm/sched: fix use-after-free of the fence timeline name Jonghyuk Kim(MalHyuk) 2026-09-02 14:42 ` [PATCH v3 1/2] " Jonghyuk Kim(MalHyuk) 2026-09-02 14:58 ` sashiko-bot 2026-09-03 8:46 ` Philipp Stanner 2026-09-03 10:02 ` Christian König 2026-09-03 10:22 ` Christian König 2026-09-02 14:42 ` [PATCH v3 2/2] drm/sched/tests: add a UAF regression test for the " Jonghyuk Kim(MalHyuk) 2026-09-02 15:04 ` sashiko-bot 2026-09-02 16:09 ` [PATCH v3 0/2] drm/sched: fix use-after-free of the fence " Philipp Stanner 2026-09-02 17:25 ` Philipp Stanner
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox