# DF-4701 VERDICT — twa(4) half-shutdown state on firmware-handshake failure

**Status: `untested`** (hardware absent from the audit guest) · impact `dos` ·
confidence `likely` (the state machine is fully source-provable; only the trigger
needs physical 3ware hardware).

## 1. Root cause, line by line

`twa_detach` aborts on shutdown error and leaves the device attached:

- `sys/dev/raid/twa/tw_osl_freebsd.c:813-815`
  ```c
  /* Shut the controller down. */
  if ((error = twa_shutdown(dev)))
          goto out;              /* <-- device stays attached */
  ```

`twa_shutdown` tears down interrupts and the watchdog *before* the CL handshake,
then propagates the CL error:

- `sys/dev/raid/twa/tw_osl_freebsd.c:846`  `twa_teardown_intr(sc)` (bus_teardown_intr)
- `sys/dev/raid/twa/tw_osl_freebsd.c:849-850` `callout_cancel(watchdog[0/1])`
- `sys/dev/raid/twa/tw_osl_freebsd.c:853` `tw_cl_shutdown_ctlr(&(sc->ctlr_handle), 0)`

`tw_cl_shutdown_ctlr` commits the teardown *before* it knows whether the firmware
handshake will succeed, and destroys the locks regardless of the handshake result:

- `sys/dev/raid/twa/tw_cl_init.c:569`  `ctlr->active = TW_CL_FALSE;`
- `sys/dev/raid/twa/tw_cl_init.c:571`  `tw_cli_disable_interrupts(ctlr);`
- `sys/dev/raid/twa/tw_cl_init.c:574-581` shutdown-message `tw_cli_init_connection(...)`
  → on a wedged firmware this polls `TW_CLI_REQUEST_TIMEOUT_PERIOD` and returns
  `TW_OSL_ETIMEDOUT` (tw_cl_io.c:947-1033) — and additionally trips the known
  DF-1814 FREE_Q double-insert inside `init_connection` itself.
- `sys/dev/raid/twa/tw_cl_init.c:583-588` locks destroyed **unconditionally**
  (`tw_osl_destroy_lock(gen_lock)`, `tw_osl_destroy_lock(io_lock)` =
  `spin_uninit`, tw_osl_inline.h:77-80 — a no-op on this tree, sys/sys/spinlock2.h).
- `sys/dev/raid/twa/tw_cl_init.c:591` `return(error);` → detach aborts.

Post-condition when the handshake fails: device attached, `/dev/twa0` alive
(`destroy_dev` only in `tw_osli_free_resources`, tw_osl_freebsd.c:777-779, which
was skipped), CAM sim still registered (`tw_osli_cam_detach` skipped, :703),
`ctlr->active == FALSE`, interrupts torn down, watchdog cancelled, no self-healing.

## 2. Why subsequent (unprivileged) I/O reaches the dead controller

The **only** `ctlr->active` check in the driver is in the ISR:

- `sys/dev/raid/twa/tw_cl_intr.c:81` `if (ctlr->reset_in_progress || !(ctlr->active)) goto out;`

Neither I/O entry path checks it:

- CAM: `twa_action` XPT_SCSI_IO `sys/dev/raid/twa/tw_osl_cam.c:363-393` →
  `tw_osli_execute_scsi` → `tw_osli_map_request` →
  `tw_cl_start_io` `sys/dev/raid/twa/tw_cl_io.c:70-101` — no active check;
  requests are queued/submitted to firmware that was told "going down" with
  `TWA_SHUTDOWN_MESSAGE_CREDITS == 1` (tw_cl_fwif.h:137).
- ioctl: `twa_ioctl` `sys/dev/raid/twa/tw_osl_freebsd.c:147-175` →
  `tw_osli_fw_passthru` (:895) → `tw_cl_fw_passthru` (tw_cl_io.c:261) — no check.
  (`/dev/twa0` is 0660 root:operator, tw_osl_freebsd.c:405-407, so this half needs
  group operator; the CAM path needs no privilege at all.)

Observed effect chain (expected, not exercised): unprivileged read on a twa-backed
filesystem → CCB submitted → no completion (interrupts gone / fw down) → CAM
peripheral timeouts (`twa_timeout` sets reset-needed; the watchdog that would run
`tw_cl_reset_ctlr` was cancelled) → repeated CAM retries/timeouts → dead volume
until reboot. On INVARIANTS/debug kernels the reused/`spin_uninit`-ed lock state
trips assertions; on production kernels `spin_uninit` is `/* unused */`
(sys/sys/spinlock2.h) so the lock itself is benign and the impact is the DoS.

System-shutdown instance of the same ordering defect: `twa_shutdown` is the
`device_shutdown` method (tw_osl_freebsd.c:202); CAM's shutdown flush
(SYNCHRONIZE CACHE) can be issued after `twa_shutdown` already closed the
connection → same submit-to-dead-controller path during reboot.

## 3. Why not reproduced on the guest

The QEMU guest (see `env.txt`) has **no 3ware controller** (`pciconf -l` shows no
0x13C1:0x1002-0x1005 device), no `/dev/twa*`, no `twa` kld, and no drm/i915 either
(display is Bochs 0x1234:0x1111). `twa_probe` (tw_osl_freebsd.c:228-247 via
`tw_cl_ctlr_supported`, tw_cl_init.c:63-73) never matches, so the code path cannot
be entered at runtime — neither baseline nor fix-validation is possible on this
guest. Not `false_positive`: the defect is statically provable in the source
(above); it is simply hardware-gated.

## 4. Fix

See `fix.diff`:

1. `tw_cl_shutdown_ctlr`: after the CL context is torn down (locks destroyed,
   `active=FALSE`), do not return the handshake error — the caller treats non-zero
   as "device still fully usable" and aborts the detach in the worst possible
   state. The failure is already logged via `tw_cl_create_event`.
2. Defense-in-depth: `tw_cl_start_io` / `tw_cl_fw_passthru` refuse work when
   `!ctlr->active` (EBUSY) so a shut-down controller rejects new I/O cleanly.

Fix not build/boot-tested: single-fix kernel validation requires the controller.
`fix_status: not_testable`, `fix_baseline_reproduced: 0`, `fix_patched_reproduced: 0`.

## 5. Negative result context (what else was checked in tw_cl_init.c pass 2)

- The ETIMEDOUT re-queue class in `tw_cli_init_connection` out-label is **known**
  DF-1814; its siblings in get/set_param (DF-4335) and drain_aen (DF-4509) are also
  known. Not re-reported.
- `tw_cl_get_mem_requirements` / `tw_cl_init_ctlr` size math is consistent with the
  OSL allocation (tw_osl_freebsd.c:517-531): ctlr ctx + 256 req ctx + 256 event
  packets; `internal_req_data` = cmd_pkts + 512B sector — no overflow
  (tw_cl_init.c:210-223, 289-356). `max_simult_reqs` capped at 256 (:190-199,
  268-277); `max_aens` is the constant 256 (tw_osl.h:56) so the `% max_aens_supported`
  sites can't divide by zero.
- `TW_CL_START_CTLR_ONLY` jump (tw_cl_init.c:262-266) is dead: OSL `sc->flags` only
  ever holds TW_CL_64BIT_ADDRESSES (1<<0) / TW_CL_64BIT_SG_LENGTH (1<<1)
  (tw_osl_freebsd.c:511-512), no collision with (1<<2) (tw_cl_share.h:86-89).
- `tw_cl_get_pci_bar_info` inner switch has no `default`: an invalid `bar_type`
  would return success with uninitialized `*bar_num/*bar0_offset/*bar_size`
  (tw_cl_init.c:96-146). Only caller passes the constant TW_CL_BAR_TYPE_MEM
  (tw_osl_freebsd.c:323) → unreachable today; hardening nit, not filed.
- Init-failure unwind leaves `lockinit`/`spin_init`-ed locks behind in freed
  memory (tw_cl_init.c:322-324 vs tw_osl_freebsd.c:400); benign on DragonFly
  (nothing registered globally, `spin_uninit` is a no-op).
- Response-ID trust (`req_ctxt_buf[GET_RESP_ID(resp)]`, tw_cl_intr.c:238) is
  firmware-controlled but masked to 8 bits and the array holds 256 entries
  (TW_OSLI_MAX_NUM_REQUESTS = TW_CL_MAX_SIMULTANEOUS_REQUESTS = 256, tw_osl.h:53)
  → no OOB even from a lying firmware.

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