# DF-4640 — ips attach-time info commands: timeout unwind frees the static command slot and its DMA resources while the adapter still owns the command

**File:** `sys/dev/raid/ips/ips_commands.c:313-332` (adapter info, 30 s budget), `:411-433` (drive info, 10 s)
**Severity:** Low (trigger = adapter that exceeds the timeout, i.e. failing/flaky hardware) · **Confidence:** certain (code path unconditional once the timeout fires)
**Class:** CWE-416 (use-after-free of DMA backing) + NULL/wrong indirect callback on late completion

No runnable PoC exists for this class on the audit guest: the path requires a
real IBM ServeRAID PCI adapter that is slow (not dead) — QEMU has no device
model, the guest has zero 0x1014/0x9005 devices and no /dev/ips* (env.txt).
The evidence is the line-accurate static trace below; `timeout_uaf_path.c`
encodes it as an annotated path listing.

## Trace

1. `ips_adapter_init` → `ips_get_adapter_info` (ips.c:426, ips.c:336) →
   `ips_send_adapter_info_cmd` (ips_commands.c:288) with the STATIC command
   slot 0 (`IPS_STATIC_FLAG`, ips.c:211-217).
2. The info buffer is allocated (`bus_dmamem_alloc`, :307), the load callback
   issues the command to the adapter (`sc->ips_issue_cmd`, :284 via
   `ips_adapter_info_callback`).
3. `ips_timed_wait(command, "ips", 30*hz)` (:317) — the ONLY completion
   signal is the adapter's interrupt (`ips_wakeup_callback`, :66-72). If the
   adapter is wedged-but-alive, tsleep returns ETIMEDOUT.
4. The unwind then does, unconditionally:
   - `bus_dmamap_unload` (:325) — unload while the device may still DMA,
   - `bus_dmamem_free(data_dmatag, data_buffer, ...)` (:328) — **the DMA
     target buffer is returned to the kernel heap while the adapter still
     holds the command**,
   - `bus_dma_tag_destroy(data_dmatag)` (:330),
   - `ips_insert_free_cmd` (:331) — clears `IPS_STATIC_BUSY` so slot 0 can
     be handed out again (`ips_get_free_cmd`, ips.c:211-217) — during
     attach, the very next consumer is `ips_update_nvram` (ips.c:432) or
     `ips_get_drive_info` (ips.c:437).
5. When the adapter eventually completes the stale command:
   - **hardware DMA-writes IPS_ADAPTER_INFO_LEN bytes into the freed
     buffer** (device-side UAF write into kernel heap), and
   - `ips_morpheus_check_intr` (ips.c:567-576) /
     `ips_copperhead_intr` execute `commandarray[0]->callback(command)`:
     if slot 0 was re-gotten, `ips_get_free_cmd` has zeroed `callback`
     (ips.c:232 bzero region includes `callback`) → **NULL indirect call,
     kernel panic**; if the new owner already installed its callback, the
     NEW command is falsely completed (`ips_timed_wait` returns while its
     data buffer is still in flight → `memcpy(&sc->adapter_info, ...)`
     :322 of stale data / nvram write of a half-DMA'd page :603-608).

## Distinction from the filed ips family

- DF-4614 covers the *queue teardown* frees (`ips_cmdqueue_free` /
  `ips_cmdqueue_init` at ips.c) freeing under live commands on detach/error.
  DF-4640 is the *per-command timeout unwind inside ips_commands.c* handing
  back a slot and freeing DMA resources the hardware still owns — a
  different call site and a different trigger (no detach needed).
- DF-4613 covers the watchdog-path lock inversion; the sibling wait-family
  defect noted here is that every OTHER static command uses
  `ips_timed_wait(..., 0)` = infinite tsleep (ips_commands.c:471, :558,
  :673, :722, :743), so a card wedging mid-attach hangs the boot thread
  forever rather than timing out.

## Fix sketch (see verdict.json recommended_fix)

On ETIMEDOUT, do NOT free/recycle: leave the command owned (keep
IPS_STATIC_BUSY set, keep the DMA allocation) and let the watchdog
(ips_timeout) — or attach failure path with the adapter quiesced — reclaim
it; alternatively fail attach hard (ENXIO) without recycling slot 0 until
the adapter is reset, and give the infinite waits finite budgets.

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

(No fix.diff for this item: documented recommendation only - see Fix above.)
