{
  "finding_id": "DF-4772",
  "status": "reproduced",
  "reproduced": 1,
  "impact": "leak",
  "confidence": "certain",
  "verdict": "Reproduced deterministically on the audit guest with a KLD that executes amdgpu_pasid_free_delayeds exact call sequence (amdgpu_ids.c:111-126) against the ports own exported reservation-object shim: reservation_object_get_fences_rcu(resv, NULL, &count, &fences) takes a reference on resv->fence_excl unconditionally (linux_reservation.c:356-357) but only hands it out when pfence_excl != NULL (linux_reservation.c:410-411) \u2014 upstream Linuxs trailing else if (fence_excl) dma_fence_put(fence_excl); is missing from the port. Baseline: fence refcount goes 1 -> 2 (add_excl) -> 3 (get_fences_rcu with NULL) and 1000/1000 then 4096/4096 fence objects are never released (released=0, leaked=N, LEAK CONFIRMED); the leak scales linearly. Real-world reachability is unprivileged: every amdgpu drm fd open allocates a PASID (amdgpu_kms.c:985) and every close calls amdgpu_pasid_free_delayed on the VM page-directory resv (amdgpu_kms.c:1076-1077), which carries an exclusive fence after every page-directory update (amdgpu_vm.c:1559/1925 -> amdgpu_bo_fence(...,true) -> reservation_object_add_excl_fence, amdgpu_object.c:1330) \u2014 an open/CS/close loop leaks one dma_fence per cycle, unbounded kernel heap growth (kernleak). amdgpu_prime.c:208 has the identical pfence_excl==NULL call (same root cause, separate file). Fix validated on a rebuilt guest kernel: with the else-put restored (fix.diff), the same PoC shows refcount 1->2->2, released=1000, leaked=0, BALANCED (run.fixed.log).",
  "exploit_chain": "unprivileged render-node user -> open /dev/dri/cardD (amdgpu_pasid_alloc) -> submit CS so page tables update (excl fence lands on the PD resv) -> close fd (amdgpu_pasid_free_delayed -> get_fences_rcu(NULL,...) leaks one excl-fence ref) -> repeat; each cycle strands one struct dma_fence (~200B M_DRM + everything its release would have freed) forever -> unbounded kernel heap exhaustion / memory-pressure DoS. No memory corruption: the primitive is a pure reference leak.",
  "evidence": "run.log (baseline: after_get_fences_rcu_NULL=3 (correct value: 2), iterations=1000 released=0 leaked=1000, LEAK CONFIRMED); run.2.log (4096/4096); run.fixed.log (patched kernel: =2 (correct value: 2), released=1000 leaked=0, BALANCED); build.log (module, -Werror clean); build_kernel.log tail (nativekernel RC=0); fix.diff (git-apply clean vs sys/)",
  "kernel_refs": [
    "sys/dev/drm/amd/amdgpu/amdgpu_ids.c:119",
    "sys/dev/drm/amd/amdgpu/amdgpu_ids.c:111-166",
    "sys/dev/drm/linux_reservation.c:356-357",
    "sys/dev/drm/linux_reservation.c:406-413",
    "sys/dev/drm/amd/amdgpu/amdgpu_kms.c:985",
    "sys/dev/drm/amd/amdgpu/amdgpu_kms.c:1076-1077",
    "sys/dev/drm/amd/amdgpu/amdgpu_vm.c:1559",
    "sys/dev/drm/amd/amdgpu/amdgpu_vm.c:1925",
    "sys/dev/drm/amd/amdgpu/amdgpu_object.c:1330"
  ],
  "poc_changes": "The findings PoC sketch had to be written from scratch as a KLD harness (no GPU on the guest): kzalloc/kfree (linux/slab.h shim hardwires M_DRM) for the fence containers, lockinit for the fence spinlock (shim has no spin_lock_init), flat df4772.* sysctl node (SYSCTL_NODE(,) root, not kern.*), per-iteration release bookkeeping via rel_before snapshot (a release_count==i comparison under-counted), and manual kfree of leaked containers at unload so the guest is left clean.",
  "attempts": 4,
  "guest_uname": "DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #0: Thu Jul  2 06:02:54 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64 (baseline + 4096-run); fix validated on rebuilt nativekernel #1: Wed Sep 23 13:15:01 UTC 2026",
  "runtime_sec": 5400,
  "guest_dirty": 0,
  "build_cmd": "cd /root/poc/DF-4772 && make -m /usr/share/mk KMODDIR=/root/poc/DF-4772   # needs kldload drm first (shim symbols live in drm.ko)",
  "run_cmd": "kldload drm && kldload /root/poc/DF-4772/df4772.ko && sysctl df4772.n=1000 && sysctl df4772.run=1 && kldunload df4772 && dmesg | tail -5",
  "code_hash": "991cc5f575f5945221bbad0403435c798587b9cd0ded343fee34f654955911da",
  "notes": "No AMD GPU on the audit guest \u2014 the leak is pure shim logic, so the replica uses the ports exported reservation_object_get_fences_rcu/reservation_object_add_excl_fence/dma_fence_* exactly as the driver does; this is the same methodology as the verified DF-4684/DF-4685 packs. The shim also has a second, non-security deviation observed while auditing: ida_simple_gets range math (linux/idr.h:92-99 + linux_idr.c idr_alloc lim) under-allocates each id range by one (max pasid 0xFFFE for bits=16 instead of 0xFFFF) \u2014 functional only. The DF-4506 lockless-idr_find-vs-idr_grow hazard does NOT instantiate for amdgpu_pasid_ida: nothing calls idr_find/idr_get_next on it, and alloc/remove both serialize on idr_token.",
  "recommended_fix": "restore upstreams trailing else if (fence_excl) dma_fence_put(fence_excl); in reservation_object_get_fences_rcu (linux_reservation.c:410-411)",
  "fix_status": "fixed",
  "fix_baseline_reproduced": 1,
  "fix_patched_reproduced": 0,
  "fix_verdict": "A/B on the guest: baseline kernel -> after_get_fences_rcu_NULL=3, released=0 leaked=1000/4096 (LEAK CONFIRMED); after applying fix.diff to the guest /usr/src, make -j6 nativekernel RC=0 (under -Werror), installkernel + reboot into #1: Wed Sep 23 13:15:01 UTC 2026, module rebuilt and the identical trigger re-run -> after_get_fences_rcu_NULL=2 (correct), released=1000 leaked=0 (BALANCED), guest survived and module unloaded cleanly. /usr/src then restored by vm.sh reset with-src.",
  "fix_evidence": "run.log + run.2.log (baseline leak) vs run.fixed.log (patched: BALANCED); fix.diff applies clean against sys/ (git apply --check); build_kernel.log (RC=0 tail of the patched nativekernel build)"
}