drm_sched_entity_fini dependency cleanup check-then-uses entity->dependency while drm_sched_entity_clear_dep NULLs it unsynchronized from fence-signaling context β dma_fence_remove_callback(NULL/stale) NULL-deref lockmgr panic or UAF (tight-window TOCTOU, beyond DF-1927's scheduler-thread race)
Summary
fini's `if (entity->dependency)` then use races the armed callback's `dependency=NULL; dma_fence_put` from fence-signaling context - a signal landing in the window passes NULL into remove_callback (lockmgr NULL deref panic) or a stale pointer whose last ref the cb just dropped (lockmgr on freed memory). Distinct actor/sink from DF-1927 (fence signaler vs scheduler thread; dependency cleanup vs spsc queue). Enabling path: SIGKILL during a CS with an unsignaled dependency. xchg-before-use fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4844 Β· 2 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 2.7 KB | β raw | |
| verdict.json | β | 2.0 KB | view raw |
DF-4844 β drm_sched_entity_fini dependency TOCTOU vs the fence-signaled clear_dep callback
sys/dev/drm/scheduler/sched_entity.c:279-284 (fini, dependency cleanup):
if (entity->dependency) {
dma_fence_remove_callback(entity->dependency, &entity->cb);
dma_fence_put(entity->dependency);
entity->dependency = NULL;
}
entity->dependency is written unsynchronized by the fence callback
drm_sched_entity_clear_dep() (sched_entity.c:312-320) from
fence-signaling context β on amdgpu that is the IRQ ithread (or any thread
completing the dependency fence) running entity->dependency = NULL;
dma_fence_put(f); under fence->lock, not under anything the fini path
holds.
If the dependency fence signals exactly between fini's NULL-check and the
dma_fence_remove_callback() call, the argument is either:
NULL(cb won) βdma_fence_remove_callback(NULL, ...)βlockmgr(fence->lock, ...)NULL deref β fatal panic (sys/dev/drm/linux_fence.c:322-327); or- a stale pointer whose reference the cb just dropped β if that put was
the last, the fence is freed β
lockmgron freed memory (UAF).
Status: UNTESTED (speculative β single-window TOCTOU)
Reachability sketch (unprivileged, amdgpu render node): submit a CS job
depending on a long-running fence (entity sits with dependency armed via
drm_sched_entity_pop_job), then SIGKILL/close the fd so amdgpu_ctx_fini
runs drm_sched_entity_flush (interruptible wait bails on the signal, job
still queued, entity->dependency still set) and drm_sched_entity_fini
enters the cleanup β while the GPU completes the dependency fence at just
the wrong moment. The window is a few instructions wide; the race is real
(two unsynchronized writers to entity->dependency) but was not reproduced
on the audit guest (needs a GPU-driven fence-completion timing source; the
module harness would need a second thread signaling the dep fence inside
the checkβuse window β trivially racy, only probabilistically hittable).
Identical shape exists in upstream Linux 5.4-era code; the port inherits it.
Suggested fix
Take a local reference before the check, e.g.:
- if (entity->dependency) {
- dma_fence_remove_callback(entity->dependency, &entity->cb);
- dma_fence_put(entity->dependency);
- entity->dependency = NULL;
- }
+ struct dma_fence *dep = xchg(&entity->dependency, NULL);
+
+ if (dep) {
+ dma_fence_remove_callback(dep, &entity->cb);
+ dma_fence_put(dep);
+ }
(the callback's own entity->dependency = NULL write becomes the loser of
the xchg; dep cannot be NULL/stale inside the block). The same pattern
should guard drm_sched_entity_add_dependency_cb()'s field swaps.
Fix verification
not_testableConfirmed kernel references
Detail
Evidence (decisive lines)
README.md (check-then-use vs unsynchronized cb write, with path:line and fix sketch)
PoC changes
no runnable PoC (speculative race)
Verified recommended fix
exchange entity->dependency with xchg() before the remove_callback/put pair in drm_sched_entity_fini so the callback's NULL write cannot strand NULL/stale pointers in the cleanup
Verdict
Not reproduced (single-window TOCTOU; would need GPU-timed fence completion racing ctx teardown - the audit guest has no GPU). Code analysis: drm_sched_entity_fini (sched_entity.c:279-284) checks then uses entity->dependency while drm_sched_entity_clear_dep (sched_entity.c:312-320) writes entity->dependency = NULL unsynchronized from fence-signaling context (amdgpu IRQ ithread / any completing thread). A signal in the check->use window passes NULL (or a refcount-expired stale pointer) to dma_fence_remove_callback, whose lockmgr(fence->lock) (linux_fence.c:327) then NULL-derefs or touches freed memory -> fatal panic / UAF read. Reachable in principle from an unprivileged amdgpu render-node context kill during a pending dependency; window is a few instructions, hence speculative. Upstream 5.4-era shape is identical.
No comments yet.