# 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(...)` then `amdgpu_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`):

1. User allocates contexts (`AMDGPU_CTX_OP_ALLOC_CTX`), submits CS jobs
   (AMDGPU_CS) on them — jobs are pushed to `ctx->entities[][]`.
2. User `close()`s the fd with jobs still queued/executing.
3. `amdgpu_driver_postclose_kms` (`amdgpu_kms.c:1040`):
   * `amdgpu_vm_fini` FIRST —
     - pasid removed from `pasid_idr` (vm.c:3300-3306)
     - `drm_sched_entity_destroy(&vm->entity)` (vm.c:3311)
     - all `vm->freed` mappings freed (vm.c:3335-3343)
     - **`amdgpu_vm_free_pts` frees every page-table BO** (vm.c:3348-3351)
   * `amdgpu_ctx_mgr_fini` SECOND — only NOW are context entities fini'd
     (ctx.c:569-591 → `drm_sched_entity_fini` per entity).
4. 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) dereferences `job->vm` and
     calls `amdgpu_vmid_grab` (ids.c:406-459) touching
     `vm->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 -l` shows no
  vendor 0x1002 device (no AMD GPU attached to the VM).
* `trigger.c` compiled 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.
