β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-4844

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4844 Β· 2 files
FileTypeDescriptionSize
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 β†’ lockmgr on 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_testable
per-fix-DF-4844

Confirmed 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.