DF-5228 / verdict.json
{ "finding_id": "DF-5228", "status": "reproduced", "verdict": "REPRODUCED on stock drm.ko (DragonFly 6.5-DEVELOPMENT guest): dma_free_coherent (sys/dev/drm/linux_dma.c:81) is a bare kmem_free(kernel_map, addr, size), but the matching free for kmem_alloc_contig/contigmalloc is contigfree() (vm_contig.c:588-603: pmap_qremove + kmem_free + vm_page_free_contig). The contig regions pages are wired object-less pages pmap_kenterd over an object-less kmem_alloc_pageable entry, so vm_map_delete (vm_map.c:3325-3370) never pmap_removes the range and never unwires/frees the pages. Demonstrated per-free: kernel PTE still valid after free (pmap_kextract != 0) and the phys page still wire_count=1 (probe on both reserve [PG_FICTITIOUS] and real pages); demonstrated at scale: an alloc+free loop that must be loss-free burned 119-124MB permanently per 120-240MB cycled (after silently draining the 128MB shared DMA reserve), wire surplus 31199-32200 pages, control contigmalloc/contigfree deficit 0. Live in-tree leak paths: radeon_gart_fini (radeon_gart.c:110), amdgpu_ih_ring_fini (amdgpu_ih.c:111), TTM dma pools. Impact: permanent wired-memory exhaustion (unreclaimable under pressure) + DMA-reserve starvation for other drivers + orphaned-PTE/pmap accounting desync; triggers are driver lifecycle (unload/re-probe) -> Medium. Fix validated in the same run environment: contigfree-based dma_free_coherent removes the PTE (pmap_kextract=0) and the loop deficit collapses to 2 pages (noise).", "reproduced": true, "impact": "dos", "confidence": "certain", "exploit_chain": "any radeon/amdgpu driver unload or failed GPU probe cycles dma_alloc_coherent/dma_free_coherent (GART table, IH rings) -> every cycle permanently burns KBs-MBs of wired RAM and reserve capacity -> repeatable until the machine is out of memory with all of it unreclaimable (hard DoS); the shared 128MB DMA reserve starvation also degrades unrelated devices contig allocations.", "evidence": "run.log: pmap_kextract(va)=0000000018000000 -> kernel PTE STILL VALID (ORPHANED); phys page state: wire_count=1 ... -> page STILL WIRED (LEAKED); RESULT: 120 MB alloc+freed ... deficit=30691 pages (119MB), v_wire surplus=31199 with control ... deficit=0; run.fixed.log: kernel PTE gone, deficit=2 pages (0MB)", "kernel_refs": [ "sys/dev/drm/linux_dma.c:81", "sys/vm/vm_contig.c:588", "sys/vm/vm_contig.c:447", "sys/vm/vm_kern.c:171", "sys/vm/vm_map.c:3325", "sys/vm/vm_page.c:2858", "sys/dev/drm/radeon/radeon_gart.c:110", "sys/dev/drm/amdgpu/amdgpu_ih.c:111" ], "poc_changes": "Authored fresh (reserved-ID note: DF-5226 on disk holds the twin-batchs unrelated refuted-candidate negative record, left untouched; this finding used the next reserved ID). v1 measured only vmstats deltas and under-reported while the DMA reserve absorbed the leak; v2/v3 added the PTE/page-state probes, the reserve-exhausting sustainability loop and the contigmalloc/contigfree control - all baseline runs kept in run.log.", "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", "runtime_sec": 420, "guest_dirty": 0, "build_cmd": "cd /tmp/poc/DF-5228 && make", "run_cmd": "kldload drm; kldload /tmp/poc/DF-5228/poc5228.ko; dmesg | grep DF5228", "code_hash": "78fdf676aff768fbc0da8e1e502c78c729bfd1638e1b4ed31bb03bed64f8a7c8", "notes": "Fix shared with DF-5227 (same rewrite of the two functions). Guest-side observation kept for the record: kldunload drm with no device attached hung the linker subsystem once - pre-existing drm teardown defect family (DF-4532/DF-5064), not this file. Guest reset clean (vm.sh reset with-src) after evidence collection.", "recommended_fix": "Implement dma_free_coherent with contigfree(cpu_addr, size, M_DEVBUF) instead of bare kmem_free (and zero the allocation via contigmalloc_map(M_NOWAIT|M_ZERO) - DF-5227); fix.diff in this pack.", "fix_status": "fixed", "fix_baseline_reproduced": true, "fix_patched_reproduced": false, "fix_verdict": "fix.diff applied to guest /usr/src/sys/dev/drm/linux_dma.c, drm.ko rebuilt from source, loaded on a fresh boot (vm.sh reset with-src): identical PoC now shows pmap_kextract(va)=0 (PTE removed) and a 120MB alloc+free loop deficit of 2 pages (noise) vs 30691 on stock - the leak is gone; allocations still succeed.", "fix_evidence": "run.fixed.log: DF5228: after free: pmap_kextract(va)=0000000000000000 -> kernel PTE gone; RESULT: ... deficit=2 pages (0MB), v_wire surplus=2; compare run.log baseline deficit=30691 pages (119MB), v_wire surplus=31199" } |