# DF-5227 VERDICT — dma_alloc_coherent() returns stale kernel memory

**Status: REPRODUCED** (impact: leak) — on the guest, against the stock
`/boot/kernel/drm.ko` of DragonFly 6.5-DEVELOPMENT (built Jul 2 2026).

## How it was proven

`poc5227.c` is a kernel module (KLD, `-Werror` clean) that calls the *real*
`dma_alloc_coherent()` exported by drm.ko:

1. `contigmalloc(2MB)` — the exact allocator the shim uses underneath
   (`kmem_alloc_contig` → `contigmalloc_map`, sys/vm/vm_contig.c:608-613).
   Allocation landed at physical 0x3000000.
2. Fills the block: every page starts with the ASCII marker
   `DF5227-STALE-KERNEL-MEMORY-MARKER!`, body 0xA5.
3. `contigfree()` — pages return to the DMA-reserve alist, **contents
   preserved** (alist_free, no zeroing anywhere: sys/vm/vm_page.c:2868-2877).
4. `dma_alloc_coherent(NULL, 2MB)` — per the Linux DMA API contract this must
   return **zeroed** memory.
5. Result (run.log): **same physical block** (0x3000000), **512/512 pages
   stale**, all 512 markers present verbatim, 0xA5 fill visible in the hexdump.

Why deterministic: both allocations resolve through the low-memory DMA
reserve's first-fit `alist_alloc` (vm_page.c:2806-2814), so the second
allocation reuses exactly the block freed in step 3.

## Why the shim is wrong

- linux_dma.c:47 `kmem_alloc_contig(size, 0, high, align)` →
  vm_contig.c:612 `contigmalloc_map(size, M_DEVBUF, M_NOWAIT, ...)` — **no
  M_ZERO**; vm_contig.c:533-534 zeroes only `if (flags & M_ZERO)`.
- The reserve path (vm_contig.c:288-290) never zeroes either.
- The Linux contract ("coherent memory is zeroed") is what the disabled
  FreeBSD reference implementation in the same function implements
  (linux_dma.c:64-67 passes BUS_DMA_ZERO) — and what all live callers are
  forced to hand-roll: `memset()` at amdgpu_ih.c:66, drm_pci.c:130,
  radeon_gart.c:85.

## Threat model / ceiling

On real hardware the "device" is the GPU. Any LinuxKPI consumer that trusts
the contract and lets the engine (or userspace, via GEM/GTT mappings) read its
coherent allocation discloses stale kernel/user memory. Today's in-tree
callers mask the bug with memsets, so there is no *live* unprivileged leak on
a stock desktop — hence Medium, not High. The defect is in the shim: every
future/upstream-synced caller that drops its memset becomes a live
kernel-info-leak (the classic dma-coherent leak class).

No uid=0 chain applies (info-leak class).

## Fix validation (same run, same guest)

`fix.diff` (shared with DF-5228): allocate with
`contigmalloc_map(..., M_NOWAIT | M_ZERO, ...)`.

- Guest reset (`vm.sh reset with-src`), fix applied to `/usr/src`, drm.ko
  rebuilt from source, loaded fresh.
- Re-run of the identical PoC (run.fixed.log):
  `DF5227 RESULT: marker_hits=0 stale_nonzero_pages=0/512` — same physical
  block reuse, buffer now **zeroed**. Baseline behavior GONE.

## Observations / notes

- `kldunload drm` (stock or patched, no device attached) wedged the guest's
  kernel-linker subsystem on one occasion; pre-existing drm teardown behavior
  (see DF-4532/DF-5064 family), not related to this finding — the fix run
  avoided unload entirely (fresh boot + load).
- Guest reset clean after the runs.

## 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.
