# DF-4191 — VERDICT

**Status: UNTETED / SPECULATIVE (hardware-gated; source-verified mechanism)**
**Impact (if realized): device-DMA to stale addresses (kernel memory corruption) / busdma map-state corruption**
**Confidence: speculative**

## Mechanism, line by line

1. `sdhci_start_data()` — ADMA2 path:
   `sys/dev/disk/sdhci/sdhci.c:1320-1340`
   ```c
   err = bus_dmamap_load(slot->adma2_tag, slot->adma2_map,
       data->data, data->len, sdhci_adma2_getaddr, slot,
       dumping ? BUS_DMA_NOWAIT : BUS_DMA_WAITOK);      /* :1326-1328 */
   if (err != 0) {                                       /* :1329 */
       device_printf(slot->bus, "Dma load for ADMA2 fail: %d\n", err);
   } else if (descs[0].address == 0) { ... }
   if (err != 0 || descs[0].address == 0) {
       slot->flags &= ~SDHCI_USE_ADMA2;                  /* :1338 */
       goto pio_fallback;                                /* :1339 */
   }
   ```
   `err == EINPROGRESS` is handled exactly like `EFBIG`/`ENOMEM`: fall back to
   PIO. The request proceeds and completes. Nothing cancels the deferred load.

2. Why EINPROGRESS is possible: the adma2 tag is created 32-bit-limited
   (`sdhci.c:549-554`,    `BUS_SPACE_MAXADDR_32BIT`). Buffers at physical addresses above 4 GB need
   bounce pages; when none are free, `_bus_dmamap_load_buffer` queues the map
   with `map->buf`/`map->buflen`/`map->callback` saved and returns EINPROGRESS
   (`sys/platform/pc64/x86_64/busdma_machdep.c:644-657`, `bus_dmamap_load`
   `:793-803` returns it to the driver). The pre-allocation from
   `BUS_DMA_ALLOCNOW|BUS_DMA_ALLOCALL` only sizes the initial pool to
   `atop(maxsize)` (`busdma_machdep.c:405-411`); shared-zone exhaustion under
   system-wide pressure still defers.

3. Later, when bounce pages free up, busdma re-runs the load with the SAVED
   `map->buf` (already-completed request's `data->data` — the bio buffer may
   be long gone) and calls `sdhci_adma2_getaddr` (`sdhci.c:598-630`), which
   writes `slot->adma2_descs` — the descriptor table a *concurrently running*
   ADMA2 request may be streaming from. Descriptor rewrite mid-transfer =
   controller consumes addresses of the previous request's buffer → DMA to
   stale/freed kernel pages.

4. Compounding: the PIO fallback skips `bus_dmamap_unload()` —
   `sdhci_finish_data()` unloads only under `SDHCI_USE_ADMA2`
   (`sdhci.c:1413-1425`) — and the *next* ADMA2 request calls
   `bus_dmamap_load()` on the still-queued map (`map->pagesneeded != 0`),
   re-reserving and re-enqueueing (`busdma_machdep.c:613-660`) → waiting-list
   double-enqueue of the same map.

## Attack surface / preconditions

- ADMA2 active: whitelisted Intel/AMD controllers (`sdhci_pci.c:100-128`,
  `sdhci_acpi.c:147,170-171`) or `hw.sdhci.adma2_test=1`.
- Host with > 4 GB RAM (mmcsd bounce for the 32-bit tag).
- Transient bounce-pool exhaustion exactly at an ADMA2 load; sustained disk
  I/O (attacker-controllable as any local user reading/writing the card)
  widens the window.

## Why untested

No SDHCI controller exists on the audit guest (stock QEMU), and the failure
mode needs live hardware DMA plus allocator pressure; a userspace replica
would only restate the trace. Filed as speculative per pass-2 rules; the
defensive `fix.diff` is compile-proven (`sdhci.ko` under `-Werror`,
`MAKE_RC=0` — `sdhci_module_build.log`).

## Kernel references

- sys/dev/disk/sdhci/sdhci.c:1326-1340 (EINPROGRESS treated as plain failure)
- sys/dev/disk/sdhci/sdhci.c:598-630 (deferred callback target, shared descs)
- sys/dev/disk/sdhci/sdhci.c:1413-1425 (unload skipped on PIO fallback)
- sys/dev/disk/sdhci/sdhci.c:549-554 (32-bit-limited adma2 tag)
- sys/platform/pc64/x86_64/busdma_machdep.c:644-657, :793-803 (EINPROGRESS
  contract: callback deferred, map->buf saved)

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