# DF-5347 VERDICT — REPRODUCED (kernel UAF → arbitrary-call control-flow hijack)

## Root cause (path:line into sys/platform/pc64/x86_64/busdma_machdep.c)

1. `_bus_dmamap_load_buffer()` line 645-657: when a blocking load cannot
   reserve enough bounce pages, the map is queued on
   `dmat->bounce_zone->bounce_map_waitinglist` (map->buf/buflen/dmat/callback
   stashed in the map) and `EINPROGRESS` is returned.  At this moment
   `map->bpages` is EMPTY and `map->pagesneeded > 0`.
2. `bus_dmamap_destroy()` line 440-449: the ONLY liveness guard is
   `STAILQ_FIRST(&map->bpages) != NULL` → EBUSY.  A queued map passes the
   guard and is `kfree(map, M_DEVBUF)`d (72-byte chunk) while still linked
   on the waiting list.  Nothing ever validates queue membership.
3. Any later bounce-page release (`free_bounce_page` line 1399 →
   `get_map_waiting` line 1408) pops the freed chunk:
   `reserve_bounce_pages()` line 1279 writes `map->pagesreserved` into freed
   memory; the chunk is then threaded onto the global
   `bounce_map_callbacklist` (`add_map_callback` line 1430).
4. `busdma_swi()` line 1445 runs `bus_dmamap_load(map->dmat, map, map->buf,
   map->buflen, map->callback, map->callback_arg, 0)` on the freed chunk,
   which ends in `callback(callback_arg, segments, nsegs, error)`
   (bus_dma_machdep line 804) — an **indirect call through a function
   pointer read from freed, attacker-repopulatable memory**.

The destroy-after-EINPROGRESS pattern is ordinary driver error cleanup
(`if (error) { bus_dmamap_unload(); bus_dmamap_destroy(); }`).  The only
in-tree driver that ever receives EINPROGRESS (twa,
sys/dev/raid/twa/tw_osl_freebsd.c:1390) handles it correctly; every other
driver using a bouncing tag with conventional error handling would convert
bounce-zone pressure into this UAF.  Zone pressure is groomable by ordinary
I/O on lowaddr-constrained tags (and by default `hw.busdma.bounce_alignment=1`
makes *every* tag with alignment>1 and no BUS_DMA_ALIGNED bounce).

## Reproduction (all on DragonFly 6.5-DEVELOPMENT #0, clean snapshot boots)

KLD harness `busdmauaf.c` (plays the vulnerable driver):
- private 2-page bounce zone (lowaddr=16MB), mapB loads both pages,
  mapA's load → EINPROGRESS (queued),
- `bus_dmamap_destroy(tag, mapA)` → **0** — freed while linked,
- heap spray reclaims the chunk ("spray chunk 0 == freed mapA" —
  deterministic LIFO on the 72-byte M_DEVBUF zone),
- freeing mapB's pages wakes the freed map,
- `busdma_swi()` executes the deferred load on the freed chunk and calls
  the planted `map->callback` (`hijack_proof`) with planted arg 0xbaadf00d:

      BUSDMA-UAF: *** HIJACK PROVEN *** attacker-chosen function
      0xffffffff82600000 executed, arg=0xbaadf00d nsegs=1 error=0

  (twice on one boot; see run.log).

- POC_TEST=3 plants `callback=0x414141414000` → kernel jumps there:

      Stopped at      0x414141414000: kernel: type 12 trap, code=0

  (see panic.txt) — full RIP control from freed memory.

Two additional independent manifestations captured:
- getmapwaiting_null_panic.txt — NULL-deref panic inside the inlined
  `reserve_bounce_pages` when the freed chunk held garbage dmat.
- slab_corruption_panic.txt — the deferred load's
  `STAILQ_INSERT_TAIL(&map->bpages, ...)` wrote the bounce-page pointer at
  offset 0 of the *still-free* chunk, corrupting kern_slaballoc's free-chunk
  linkage; the NEXT unrelated kmalloc(72) panicked in
  `chunk_mark_allocated` (kern_slaballoc.c:1659).  Reproduced twice.

## Primitive / exploit chain

Arbitrary indirect call in ring 0: target = reclaimed `map->callback`,
RDI = `map->callback_arg` (both fully attacker-set via the spray).
With no SMEP/SMAP/KASLR on the audit guest, pointing the callback at any
kernel text gadget (e.g. stack pivot → ROP, or a function that grants
privileges when called with controlled RDI) is mechanically trivial — the
harness demonstrates the controlled call itself, which is the hard part.
Escalation to `uid=0` additionally requires an *unprivileged-reachable*
driver that destroys/unloads an EINPROGRESS map; no in-tree driver
currently does, so end-to-end unpriv→root was not demonstrated and severity
is High rather than Critical.

## Fix validation

`fix.diff` (git-apply clean against the sys/ tree; applied inside the guest
/usr/src only): destroy returns EBUSY while `pagesneeded||pagesreserved`,
unload dequeues + releases the reservation (includes the one-line
`add_map_callback(wait_map)` typo fix it depends on), busdma_swi drops
unloaded maps.  Patched kernel (#1 Oct 3 06:29) re-ran all three tests:

    bus_dmamap_destroy(queued mapA) -> 16   (EBUSY)   [tests 1 & 3]
    RESULT=FAILED (no phantom)                          [test 2]
    no panic, guest stays up

Baseline behavior is GONE (dmesg_fixrun.txt).

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