# DF-4307 — adw(4) unvalidated device-fed completion tokens → OOB kernel pointers (adw_intr response-queue walk)

**File:** `sys/dev/disk/advansys/adwcam.c` — severity Medium (device/DMA-gated
kernel memory corruption), bucket memcorrupt, confidence certain (source).

## Root cause (path:line)

`adw_intr()` walks the RISC-produced response queue and converts two
**device-written 32-bit carrier fields** directly into kernel pointers with
zero validation:

- `sys/dev/disk/advansys/adwcam.c:1280`
  `acb = acbbotov(adw, adw->responseq->areq_ba);`
  `acbbotov` (adwlib.h:773-777) is literally
  `(struct acb *)((caddr_t)adw->acbs + byteoffset)` — a raw u32 added to the
  ACB array base. The legitimate token is the byte offset the driver placed
  in `scsi_req_bo` (adwlib.h:399-408 documents the firmware copies it back
  into `areq_ba`); nothing constrains the value the DMA memory actually
  contains at interrupt time.

- `sys/dev/disk/advansys/adwcam.c:1288-1289`
  `adw->responseq = carrierbotov(adw, free_carrier->next_ba & ADW_NEXT_BA_MASK);`
  `carrierbotov` (adwlib.h:800-804) = `(carriers + byte_offset)`, no bounds
  check; the 28-bit masked device value can point up to ~256 MiB past the
  carrier array.

Consequences once either pointer goes wild:

- `adwcam.c:1290` `free_carrier->next_ba = ...` — **4-byte write** through the
  unvalidated response-queue pointer (value = a small in-ring offset).
- `adwcam.c:1294-1295` `ccb = acb->ccb; callout_stop(ccb->ccb_h.timeout_ch);`
  — wild-pointer reads + callout manipulation through an acb up to +4 GiB
  from `adw->acbs`.
- `adwcam.c:1296-1332` — `acb->queue` field reads, `bus_dmamap_unload` on
  `acb->dmamap`, then `adwfreeacb(adw, acb)` (adwcam.c:128-144) which does
  `LIST_REMOVE(&acb->ccb->ccb_h, ...)` and `SLIST_INSERT_HEAD(..., acb, ...)`
  — **writes through both wild pointers**, planting the forged acb on the
  driver's free list for reuse.
- `adwcam.c:1264` loop condition reads `responseq->next_ba` on every
  iteration of an unbounded `while` — a device-fed self-referential DONE ring
  livelocks the interrupt handler (DoS), and each OOB hop keeps reading
  `next_ba` from whatever kernel memory it lands in.

Bounds that should hold (and don't):
`areq_ba < adw->num_acbs * sizeof(struct acb)` (aligned);
`(next_ba & ADW_NEXT_BA_MASK) < (max_acbs + ADW_NUM_CARRIER_QUEUES + 1) *
sizeof(struct adw_carrier)` (16 bytes each, adwmcode.h:106-113).

## Threat model

The token fields are produced by the Advansys RISC firmware / any PCI device
able to DMA into the carrier rings: buggy firmware, a compromised HBA, or a
Thunderbolt/PCIe attacker. The narrow-driver sibling of this exact class was
already accepted (DF-2412/DF-2413/DF-2414 `ccb_infos[]` walk; DF-4151
`free_q_head` sense DMA). This is the wide driver's completion-queue
instance, which pass 1 missed because it lives behind macro pointer
arithmetic in the CAM-layer file.

## Reproduction

Hardware-gated: requires an Advansys PCI HBA (ABP[3]940UW/950UW/3940U2W,
PCI IDs 10cd:2300/2500/2700). The audit guest has no such device
(`pciconf -l | grep 10cd` → empty; QEMU has no Advansys emulation), and the
RISC must be made to emit a bad token. Source-level certainty only —
status `untested`. (Simple harnesses of `acbbotov` pointer arithmetic would
be tautological; no meaningful harness exists without the device or a full
bus_dma/CAM stub of adw_intr.)

## Fix

`fix.diff` — validate both tokens against the allocations before conversion;
on violation, log + `adw_reset_bus()` + abandon the walk (the rings are
rebuilt by recovery). `patch -p1` apply-verified.

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