DF-4587 / verdict.json
{ "finding_id": "DF-4587", "status": "inconclusive", "reproduced": 0, "impact": "none", "confidence": "certain", "verdict": "Code defect certain, runtime GPU-gated. ttm_bo_release_mmap (ttm_bo_vm.c:821-852) calls vm_object_deallocate(vm_obj) on every BO revocation, but cdev_pager_lookup takes no reference and the OBJT_MGTDEVICE pager objects only references are the per-mmap map-entry references (cdev_pager_allocate returns the callers ref, vm_map_find consumes it as the entry ref; N mmaps => ref_count == N). The function runs on every VRAM/GTT transition (ttm_bo.c:318-325 via ttm_bo_handle_move_mem, evictions ttm_bo.c:737, swapout ttm_bo.c:1830) and on every radeon dpm event (radeon_pm.c:146-157, ALL VRAM GEM bos). First revocation of a once-mapped BO: ref 1->0 -> vm_object_terminate -> dev_pager_dealloc -> ttm_bo_vm_dtor -> ttm_bo_unref \u2014 the vm_object is freed under the live map entry (next fault derefs freed object at vm_fault.c:2015/2162 and writes vm_object_pip_add into it; munmap/exit deallocates freed memory -> deallocated too many times panic or silent corruption), and if the mmap held the last BO kref the BO is destroyed while ttm_bo_handle_move_mem keeps writing it. Same defect shape as the i915 twins (DF-4444s file) but strictly worse: TTMs pager dtor drops a BO kref. Unprivileged trigger: open /dev/dri/cardX of radeon/amdgpu (the only DFly drivers setting drm_ttm_bdev), CREATE_DUMB + MAP_DUMB + mmap + allocation burst for eviction. Runtime reproduction impossible on the audit guest: no DRM device (QEMU bochs 1111:1234 VGA, no /dev/dri, no drm modules); trigger df4587.c compiled clean on guest cc 8.3 and stopped exactly at the gate (open ENOENT, run.log).", "exploit_chain": "On radeon/amdgpu hardware: open /dev/dri/card0 -> CREATE_DUMB 64MiB (VRAM) -> MAP_DUMB -> mmap -> fault one page (pager object ref=1, owned by the map entry) -> allocate further 64MiB dumb BOs until VRAM pressure evicts the mapped BO -> ttm_bo_handle_move_mem -> ttm_bo_release_mmap -> premature vm_object_terminate under the live mapping + early ttm_bo_unref -> re-touch the mapping (fault through freed vm_object: UAF; zone reuse enables cross-object page mapping) or munmap (vm_object_deallocate on freed memory). If the GEM handle was closed first, the BO itself is freed mid-move -> in-kernel UAF write.", "evidence": "VERDICT.md (reference-model proof: device_pager.c:109-168, vm_map.c:1614-1615/1323-1369, vm_mmap.c:1377/1485-1494; caller map ttm_bo.c:318-325/737/1830, radeon_pm.c:146-157); run.log (BUILD_RC=0, GATE open ENOENT, RUN_RC=2); fixbuild.log (drm.ko rebuilt with fix.diff, -Werror, DRM_BUILD_RC=0); fix.diff (git apply --check OK)", "kernel_refs": [ "sys/dev/drm/ttm/ttm_bo_vm.c:821", "sys/dev/drm/ttm/ttm_bo_vm.c:851", "sys/dev/drm/ttm/ttm_bo_vm.c:748", "sys/vm/device_pager.c:109", "sys/vm/device_pager.c:96", "sys/vm/vm_map.c:1614", "sys/vm/vm_object.c:705", "sys/dev/drm/ttm/ttm_bo.c:323", "sys/dev/drm/radeon/radeon_pm.c:155" ], "poc_changes": "no seed; complete trigger written from scratch (DragonFly drm UAPI structs inlined), fixed missing sys/mman.h include after first guest compile", "attempts": 2, "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", "runtime_sec": 420, "guest_dirty": 0, "build_cmd": "cc -O2 -Wall -Wextra -o /tmp/df4587 /root/df4587.c (guest; BUILD_RC=0)", "run_cmd": "/tmp/df4587 /dev/dri/card0 (guest: open ENOENT \u2014 no DRM device, RUN_RC=2)", "code_hash": "352ba72bac0d9e30fabd4c218765f9c9b09bdf04d8f21e1f3763a3fbce32e999", "notes": "Pass-2 negatives in VERDICT.md: fault bounds OK (bo->num_pages + object->size, no DF-4458 pattern for TTM because lookup spans the node); lookup start+pages wrap unreachable (offset pre-shifted); in-bo offset K lost (correctness only, no boundary); panic() fault unreachable (vm_ops never installed, ttm_fbdev_mmap dead); vm_access dead; reserve-retry bounded; tsleep stack wchan benign. radeon verify_access stub (radeon_ttm.c:234-244) noted as DF-1633 context, filed against no file here. Guest source restored pristine (md5 e1bd78ad3b804a05207e0eebb4447a66 == repo).", "recommended_fix": "Remove the unbalanced vm_object_deallocate from ttm_bo_release_mmap and vm_object_hold/vm_object_drop around vm_object_page_remove so the pager object dies only with its last mapping", "fix_status": "not_testable", "fix_baseline_reproduced": 0, "fix_patched_reproduced": 0, "fix_verdict": "fix.diff removes the unbalanced vm_object_deallocate, adds vm_object_hold/drop around the revocation (also closes the cdev_pager_lookup-without-ref race). Compile-validated on the guest: drm.ko fully rebuilt with the patched ttm_bo_vm.c, -Werror, DRM_BUILD_RC=0 (fixbuild.log); guest /usr/src restored pristine. Behavioral baseline/patched comparison impossible: no TTM-capable GPU on the guest (fix_status not_testable, compile-validated).", "fix_evidence": "fix.diff; fixbuild.log (patched drm.ko full -Werror compile+link, DRM_BUILD_RC=0); VERDICT.md Fix validation" } |