postclose teardown order reversed: amdgpu_vm_fini before amdgpu_ctx_mgr_fini (DF-port divergence) - VM page tables freed under still-live context jobs (device-side UAF / GPU loss)
Summary
amdgpu_driver_postclose_kms calls vm_fini BEFORE ctx_mgr_fini. Upstream Linux invariantly finishes contexts first (verified in v5.0, v5.2, v5.4) precisely so context scheduler entities and in-flight jobs quiesce before the VM is destroyed. The DF order frees every GPU page table, drops the PASID, and destroys the VM entity while a context job can still be queued or executing - the scheduler's dependency callback reads the torn-down VM and executing IBs walk freed page tables: device-side UAF -> GPU faults/hang/reset triggered by an unprivileged render client closing its fd with CS in flight. Aggravated by the DF scheduler port dropping the fence-wait from entity_fini, leaving the ordering as the only teardown barrier. Swap-the-calls fix (validated by apply-check + full amdgpu module rebuild).
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4356 Β· 12 files| File | Type | Description | Size | |
|---|---|---|---|---|
| trigger.c | β | 2.6 KB | view raw | |
| README.md | β | 2.9 KB | β raw | |
| VERDICT.md | β | 4.0 KB | β raw | |
| build.sh | β | 82 B | view raw | |
| run.sh | β | 110 B | view raw | |
| run.log | β | 592 B | view raw | |
| env.txt | β | 414 B | view raw | |
| fix.diff | β | 608 B | view raw | |
| fix_apply_check.log | β | 146 B | view raw | |
| fix_build.log | β | 1.2 MB | β download | |
| verdict.json | β | 4.7 KB | view raw | |
| manifest.json | β | 892 B | view raw |
DF-4356 β postclose teardown order: amdgpu_vm_fini before amdgpu_ctx_mgr_fini (DF-port divergence)
Build / Run / Expected
cc -O2 -o trigger trigger.c # on a host with libdrm headers (drm/amdgpu_drm.h)
./trigger /dev/dri/renderD128 # as an UNPRIVILEGED user
# Expected on vulnerable host: within N iterations, dmesg shows
# "amdgpu: ... VM fault" / ring timeouts / amdgpu_device_gpu_recover
# (GPU page tables freed while context jobs still executing),
# X/ Wayland session loses the GPU -> system-wide GPU DoS.
# Clean host: "done: 4096 close-with-live-context cycles", no dmesg noise.
This audit's QEMU guest has no AMD GPU (/dev/dri does not exist), so the
runtime half could not be executed here; see VERDICT.md for the source-level
proof and the guest evidence.
What the bug is
sys/dev/drm/amd/amdgpu/amdgpu_kms.c:1073-1074 (amdgpu_driver_postclose_kms):
amdgpu_vm_fini(adev, &fpriv->vm); /* DF order: VM FIRST */
amdgpu_ctx_mgr_fini(&fpriv->ctx_mgr); /* ...contexts after */
Upstream Linux β verified in v5.0, v5.2 and v5.4 β has the opposite order:
amdgpu_ctx_mgr_fini(&fpriv->ctx_mgr); /* flush contexts: entities wait */
amdgpu_vm_fini(adev, &fpriv->vm); /* only then free page tables */
That order is a lifetime invariant: amdgpu_ctx_mgr_fini β
amdgpu_ctx_mgr_entity_fini (amdgpu_ctx.c:569-591) is what quiesces every
context scheduler entity (and, upstream, waits for the last scheduled job to
complete). Only after contexts are quiesced is it safe for amdgpu_vm_fini
(amdgpu_vm.c:3282-3361) to:
- remove the PASID from
adev->vm_manager.pasid_idr(:3300-3306), drm_sched_entity_destroy(&vm->entity)(:3311),- free all
vm->freedmappings (:3335-3343), amdgpu_vm_free_pts()β free/unpin all GPU page tables (:3348-3351).
With the DF order, both the host scheduler thread and the GPU itself can still be working for a context while the VM is torn down underneath them:
- host side:
amdgpu_job_dependency(amdgpu_job.c:193-198) callsamdgpu_vmid_grab(vm = job->vm, ...)which readsvm->reserved_vmid[],vm->pasidandvm->entity.fence_context(amdgpu_ids.c:406-459) β a VM whose internals were already destroyed. - device side: an executing IB walks page tables that
amdgpu_vm_free_ptshas just freed β device-side UAF of page-table memory β GPU VM faults / ring hangs / device reset (system-wide GPU loss).
Aggravating factor (different file, noted for triage): the DF scheduler port's
drm_sched_entity_fini (sys/dev/drm/scheduler/sched_entity.c:263-292) also
dropped upstream's dma_fence_wait(entity->last_scheduled) β so nothing in
the DF teardown path waits for in-flight jobs at all, widening this window on
both orderings; the correct (upstream) ctx-then-vm order is the only guard
left, and amdgpu_kms.c has it reversed.
Fix
fix.diff β restore the upstream order (2-line swap).
DF-4356 VERDICT β postclose VM/CTX fini order swapped (DF-port divergence)
Status: INCONCLUSIVE (hardware-gated). Source-level: divergence certain. Runtime: NOT executed β this audit's QEMU guest has no AMD GPU.
1. The divergence is certain and DF-introduced
amdgpu_kms.c:1073-1074 runs amdgpu_vm_fini(adev, &fpriv->vm) BEFORE
amdgpu_ctx_mgr_fini(&fpriv->ctx_mgr).
Upstream invariant (all verified from kernel.org sources fetched during this session):
- v5.0
amdgpu_kms.c:1055-1056:amdgpu_ctx_mgr_fini(...)thenamdgpu_vm_fini(...) - v5.2
amdgpu_kms.c:1083-1084: same order - v5.4
amdgpu_kms.c:1113-1114(approx): same order
The DF tree's amdgpu is a ~v5.0-base port (single VCN instance, .ready
instead of .sched.ready, no PSP TA/RAS query cases). v5.0 has ctx-first,
so the swap was made in the DragonFly port, not inherited.
2. Why the order is a lifetime guarantee (source trace)
postclose path, attacker = any local user who can open a render node (or
authenticate on the primary node β the AMDGPU_CTX/CS ioctls this needs are
DRM_AUTH|DRM_RENDER_ALLOW, amdgpu_kms.c:1198,1206):
- User allocates contexts (
AMDGPU_CTX_OP_ALLOC_CTX), submits CS jobs (AMDGPU_CS) on them β jobs are pushed toctx->entities[][]. - User
close()s the fd with jobs still queued/executing. amdgpu_driver_postclose_kms(amdgpu_kms.c:1040): *amdgpu_vm_finiFIRST β- pasid removed from
pasid_idr(vm.c:3300-3306) drm_sched_entity_destroy(&vm->entity)(vm.c:3311)- all
vm->freedmappings freed (vm.c:3335-3343) amdgpu_vm_free_ptsfrees every page-table BO (vm.c:3348-3351)amdgpu_ctx_mgr_finiSECOND β only NOW are context entities fini'd (ctx.c:569-591 βdrm_sched_entity_finiper entity).
- pasid removed from
- In the window between 3's free_pts and the ctx entity teardown, the
scheduler thread may still be running a context job:
*
amdgpu_job_dependency(job.c:173-204) dereferencesjob->vmand callsamdgpu_vmid_grab(ids.c:406-459) touchingvm->reserved_vmid[vmhub](:422),vm->pasid(:459),vm->entity.fence_context(:452) of the destroyed VM; * an already-scheduled IB executing on the GPU walks page tables that were freed at step 3 β device-side UAF of GPU page tables.
Note on host-side memory safety: the struct amdgpu_vm itself is embedded in
fpriv and only kfree'd at amdgpu_kms.c:1086 (after both finis), so the
host-side hazard is reading stale/destroyed internals (idr removal, freed
mappings) rather than freed slab memory; the primary memory-safety hazard is
the device-side page-table UAF. Aggravator: DF's drm_sched_entity_fini
(sched_entity.c:263-292) dropped upstream's dma_fence_wait(last_scheduled),
so nothing waits for executing jobs at teardown β the ctx-then-vm ordering
is the only remaining barrier and amdgpu_kms.c has it backwards.
3. Runtime attempt on this audit's guest (honest negative)
uname -a:DragonFly dfbsd 6.5-DEVELOPMENT ... X86_64_GENERIC(env.txt)ls /dev/driβNo such file or directory;pciconf -lshows no vendor 0x1002 device (no AMD GPU attached to the VM).trigger.ccompiled path therefore cannot run here; the run.log records the device-absent evidence gathered on the guest.
4. Fix
Two-line swap restoring the upstream order β fix.diff. Low risk: no
code between the two calls uses either structure, so the swap cannot
introduce a new dependency.
git apply --check verified against the guest's clean /usr/src copy
(fix_apply_check.log). Full patched-kernel behavioral validation requires
AMD hardware (page-table UAF is device-visible), which this guest cannot
provide β fix_status: not_testable.
Fix status (batch validation)
Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest make -j6 nativekernel KERNCONF=X86_64_GENERIC completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.
Fix verification
not_testablefix.diff restores the upstream (ctx-then-vm) order. patch --dry-run apply-check passed on the guest's pristine /usr/src (fix_apply_check.log, pristine md5 c38b5273...); with the fix applied the whole amdgpu module rebuilt cleanly (make -j6 in sys/dev/drm/amd/amdgpu -> amdgpu.ko, amdgpu_kms.o zero warnings, mod_done_0 - full log in fix_build.log). Behavioral validation (close-race with in-flight CS on real hardware) is not possible on this GPU-less guest, hence not_testable rather than fixed.
fix_apply_check.log (DF-4356_APPLY_CHECK_OK, pristine md5 c38b5273f3ad36ab64d5fc2fd782fdbb); fix_build.log (FULL 777-line module build: amdgpu_kms.o compiled zero-warnings, amdgpu.ko linked, mod_done_0)
Confirmed kernel references
- sys/dev/drm/amd/amdgpu/amdgpu_kms.c:1073
- sys/dev/drm/amd/amdgpu/amdgpu_kms.c:1074
- sys/dev/drm/amd/amdgpu/amdgpu_vm.c:3311
- sys/dev/drm/amd/amdgpu/amdgpu_vm.c:3348
- sys/dev/drm/amd/amdgpu/amdgpu_ctx.c:569
- sys/dev/drm/amd/amdgpu/amdgpu_job.c:193
- sys/dev/drm/amd/amdgpu/amdgpu_ids.c:406
- sys/dev/drm/scheduler/sched_entity.c:263
Detail
Exploit chain
unprivileged render-node client: open /dev/dri/renderD128 -> AMDGPU_CTX alloc -> AMDGPU_CS submit (long-running IB) -> close(fd) racing scheduler -> postclose frees page tables under executing job -> GPU page-table UAF -> GPU VM fault/ring hang -> device reset (system-wide GPU loss). Cross-process VRAM disclosure via freed-PT reuse is theoretically open but unproven without hardware.
Evidence (decisive lines)
VERDICT.md sections 1-3 (upstream v5.0/v5.2/v5.4 citations; amdgpu_vm.c:3300-3351 teardown inventory; amdgpu_job.c:193-198 + amdgpu_ids.c:406-459 post-teardown derefs; sched_entity.c:263-292 missing wait); run.log + env.txt (guest hardware-absent proof); fix_apply_check.log; trigger.c (compile-ready race trigger); fix.diff + fix_build.log (patched kernel build)
PoC changes
n/a (no seed PoC existed; trigger.c authored fresh; runtime blocked by absent hardware, not by code defects)
Verified recommended fix
Swap amdgpu_ctx_mgr_fini() before amdgpu_vm_fini() in amdgpu_driver_postclose_kms to restore the upstream Linux teardown order.
Verdict
DragonFly-port teardown-order divergence, source-certain: amdgpu_kms.c:1073-1074 runs amdgpu_vm_fini BEFORE amdgpu_ctx_mgr_fini, while upstream Linux v5.0/v5.2/v5.4 (all fetched and verified this session) invariantly finish contexts first so their scheduler entities and in-flight jobs quiesce before the VM page tables are freed. With the DF order, amdgpu_vm_fini frees all GPU page tables (amdgpu_vm.c:3348 amdgpu_vm_free_pts), drops the PASID and destroys vm->entity while context jobs can still be executing or queued: the scheduler thread's amdgpu_job_dependency (amdgpu_job.c:193-198) calls amdgpu_vmid_grab on the torn-down VM (amdgpu_ids.c:406-459) and executing IBs walk freed page tables -> device-side UAF -> GPU faults/hang/reset (system-wide GPU DoS); the DF scheduler port additionally dropped upstream's dma_fence_wait(last_scheduled) in drm_sched_entity_fini (sched_entity.c:263-292), removing any other barrier. Runtime reproduction is hardware-gated: the audit QEMU guest has no AMD GPU, no /dev/dri and no amdgpu UAPI header (all verified, run.log/env.txt), so status is inconclusive, consistent with prior amdgpu PoCs DF-1467/DF-1595. Fix is the two-line swap restoring the upstream order (fix.diff, apply-check OK on guest /usr/src, compile-validated by patched buildkernel).
No comments yet.