twa(4): tw_cl_shutdown_ctlr tears the Common Layer down (active=FALSE, interrupts/watchdog gone, CL locks destroyed) before the firmware shutdown handshake result is known and returns that error β twa_detach then aborts, leaving a still-attached half-dead device whose I/O entry points have no ctlr->active gate, so subsequent unprivileged disk I/O is submitted to a dead controller until reboot
Summary
Irreversible teardown (active=FALSE, lock destruction) executes before the fallible firmware handshake and the propagated error makes detach abort mid-state - /dev/twa0 and the CAM sim stay registered while the controller was told to go down with 1 credit. The only active check is in the ISR; start_io/fw_passthru submit every later request to the dead controller, and the watchdog that would reset is cancelled. Root+wedged-firmware sets it up; any unpriv I/O to the volume then sustains a DoS until reboot. Same shape at system shutdown can preempt CAM sync-cache flushes. Log-and-return-success + active-gate fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4701 Β· 7 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 4.4 KB | β raw | |
| VERDICT.md | β | 7.1 KB | β raw | |
| trigger.sh | β | 2.1 KB | view raw | |
| env.txt | β | 1.3 KB | view raw | |
| fix.diff | β | 2.5 KB | view raw | |
| manifest.json | β | 1.5 KB | view raw | |
| verdict.json | β | 3.9 KB | view raw |
DF-4701 β twa(4): tw_cl_shutdown_ctlr leaves a half-shut-down, still-attached controller when the firmware handshake fails; no ctlr->active gate on the I/O entry paths
- File:
sys/dev/raid/twa/tw_cl_init.c(pass 2) - Severity: Low Β· Class: improper shutdown state machine / missing liveness gate (CWE-667 / CWE-362)
- PoC status:
untestedβ requires a physical 3ware 9000-series PCI/PCIe controller (vendor 0x13C1, device 0x1002β0x1005). The audit QEMU guest has no such device (seeenv.txt):twanever attaches there, so neither the baseline nor a patched kernel can exercise the path on the guest.
What the bug is
twa_detach() (tw_osl_freebsd.c:813-815) calls twa_shutdown() and aborts the
detach if it returns an error. twa_shutdown() calls
tw_cl_shutdown_ctlr(handle, 0) (tw_osl_freebsd.c:853), which:
- marks the controller inactive (
ctlr->active = TW_CL_FALSE, tw_cl_init.c:569), - disables interrupts (tw_cl_init.c:571; the OSL already did
bus_teardown_intr, tw_osl_freebsd.c:846, and cancelled the watchdog callouts, :849-850), - sends the shutdown message via
tw_cli_init_connection(tw_cl_init.c:574-576) β on a wedged/hung firmware this times out and returns an error, - destroys the CL locks unconditionally, even after the failed handshake
(
tw_osl_destroy_lock(gen_lock/io_lock), tw_cl_init.c:587-588), - returns the handshake error (tw_cl_init.c:591).
The caller then aborts the detach β but the device is still fully attached: the CAM
sim is still registered, /dev/twa0 still exists (both are only torn down later in
tw_osli_free_resources, tw_osl_freebsd.c:703/777-779, which was skipped).
Nothing re-arms the driver: interrupts stay torn down, the watchdog stays cancelled,
ctlr->active stays FALSE β yet the only place in the driver that checks
ctlr->active is the interrupt handler (tw_cl_intr.c:81). Neither
twa_action()/XPT_SCSI_IO (tw_osl_cam.c:357-393) nor twa_ioctl() β
tw_osli_fw_passthru (tw_osl_freebsd.c:147-175, 895+) nor tw_cl_start_io() /
tw_cl_fw_passthru() (tw_cl_io.c:70, 261) checks it. Every subsequent I/O request
is therefore submitted to a controller the driver has already told to go down.
How to reproduce (needs the hardware)
See trigger.sh. On a machine with a 3ware 9000/9550/9650/9690 card:
- Wedge or half-wedge the firmware (e.g.
tw_climaintenance operation that hangs the microcontroller, or a controller that is failing) β the handshake intw_cli_init_connectionmust time out (TW_CLI_REQUEST_TIMEOUT_PERIOD). - As root:
kldunload twa(or unbind via devctl).twa_detachβtwa_shutdownβtw_cl_shutdown_ctlrerrors β detach aborts, device half-dead but attached. Expected marker:tw_cl_init_connection failed/init_connection failedevent (tw_cl_init.c:700-704) followed byFailed to shutdown Common Layer/controller(tw_osl_freebsd.c:854-859) andkldunload: kldunload(...) returned 5. - As an unprivileged user, issue I/O against any filesystem on the twa volume
(
ls -lR /twa-volume). Expected: commands stall for the CAM timeout periods and error out; on INVARIANTS/debug kernels the spin-lock state assertions may fire. The volume stays broken until reboot.
A second-order instance of the same defect: twa_shutdown is the
device_shutdown handler (tw_osl_freebsd.c:202). CAM issues SYNCHRONIZE CACHE
flushes during system shutdown; if twa_shutdown runs first, those XPT_SCSI_IO
CCBs are submitted to a controller that was already told to go down with 1 message
credit and torn-down interrupts.
Why this is Low
Triggering the broken state requires root (kldunload) plus a controller whose
firmware fails the shutdown handshake. What the defect then buys is that the
consequences (dead volume, CAM timeout storms, debug-kernel assertions) are
sustained by plain unprivileged disk I/O β there is no self-healing path (watchdog
cancelled) and no gate refusing new work.
Fix
fix.diff (authored, never applied to sys/): once tw_cl_shutdown_ctlr has torn
the CL context down (locks destroyed, active=FALSE) it must not report an error
that makes the OSL abort the detach with the device half-dead β the failure is
already logged as an event; and the two CL entry points gain an ctlr->active
bail-out so a shut-down controller cleanly refuses work instead of submitting to a
dead firmware queue.
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-815c /* 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:846twa_teardown_intr(sc)(bus_teardown_intr)sys/dev/raid/twa/tw_osl_freebsd.c:849-850callout_cancel(watchdog[0/1])sys/dev/raid/twa/tw_osl_freebsd.c:853tw_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:569ctlr->active = TW_CL_FALSE;sys/dev/raid/twa/tw_cl_init.c:571tw_cli_disable_interrupts(ctlr);sys/dev/raid/twa/tw_cl_init.c:574-581shutdown-messagetw_cli_init_connection(...)β on a wedged firmware this pollsTW_CLI_REQUEST_TIMEOUT_PERIODand returnsTW_OSL_ETIMEDOUT(tw_cl_io.c:947-1033) β and additionally trips the known DF-1814 FREE_Q double-insert insideinit_connectionitself.sys/dev/raid/twa/tw_cl_init.c:583-588locks 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:591return(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:81if (ctlr->reset_in_progress || !(ctlr->active)) goto out;
Neither I/O entry path checks it:
- CAM:
twa_actionXPT_SCSI_IOsys/dev/raid/twa/tw_osl_cam.c:363-393βtw_osli_execute_scsiβtw_osli_map_requestβtw_cl_start_iosys/dev/raid/twa/tw_cl_io.c:70-101β no active check; requests are queued/submitted to firmware that was told "going down" withTWA_SHUTDOWN_MESSAGE_CREDITS == 1(tw_cl_fwif.h:137). - ioctl:
twa_ioctlsys/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/twa0is 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:
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 viatw_cl_create_event.- Defense-in-depth:
tw_cl_start_io/tw_cl_fw_passthrurefuse 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_connectionout-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_ctlrsize 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_reqscapped at 256 (:190-199, 268-277);max_aensis the constant 256 (tw_osl.h:56) so the% max_aens_supportedsites can't divide by zero.TW_CL_START_CTLR_ONLYjump (tw_cl_init.c:262-266) is dead: OSLsc->flagsonly 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_infoinner switch has nodefault: an invalidbar_typewould 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_uninitis 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.
Fix verification
not_testablefix.diff authored from line-accurate source analysis and verified git-apply-clean against sys/; single-fix kernel build + baseline/patched runs require the physical 3ware controller, which the audit guest cannot provide (env.txt)
findings/poc/DF-4701/fix.diff (git apply --check OK; never applied to sys/)
Confirmed kernel references
- sys/dev/raid/twa/tw_cl_init.c:569
- sys/dev/raid/twa/tw_cl_init.c:574
- sys/dev/raid/twa/tw_cl_init.c:587
- sys/dev/raid/twa/tw_cl_init.c:591
- sys/dev/raid/twa/tw_osl_freebsd.c:813
- sys/dev/raid/twa/tw_osl_freebsd.c:853
- sys/dev/raid/twa/tw_osl_cam.c:357
- sys/dev/raid/twa/tw_cl_io.c:70
- sys/dev/raid/twa/tw_cl_io.c:261
- sys/dev/raid/twa/tw_cl_intr.c:81
Detail
Evidence (decisive lines)
findings/poc/DF-4701/VERDICT.md section 1-2 (line-by-line trace); README.md reproduction procedure; env.txt proves hardware absence on the guest (no 0x13C1 device, no /dev/twa*, no twa kld); fix.diff is git-apply --check clean against sys/
PoC changes
No seed PoC existed. Authored trigger.sh as an on-hardware procedure (wedged fw -> kldunload abort -> unprivileged I/O) since the path is unreachable on the virtualized guest.
Verified recommended fix
After tearing the CL down, tw_cl_shutdown_ctlr must return success (error stays a logged event) so detach completes; gate tw_cl_start_io/tw_cl_fw_passthru on ctlr->active (see fix.diff)
Verdict
Source-proven shutdown state-machine defect: tw_cl_shutdown_ctlr (tw_cl_init.c:558-592) marks the controller inactive, lets the OSL tear down interrupts and cancel the watchdog, destroys the CL locks unconditionally (tw_cl_init.c:587-588) and only THEN learns the firmware shutdown handshake failed - and returns that error, so twa_detach (tw_osl_freebsd.c:813-815) aborts with the device still attached (CAM sim live, /dev/twa0 live). The only ctlr->active check in the driver is in the ISR (tw_cl_intr.c:81); twa_action (tw_osl_cam.c:357) and tw_cl_start_io/tw_cl_fw_passthru (tw_cl_io.c:70/261) submit every subsequent - including unprivileged - I/O to the dead controller with no self-healing path (watchdog cancelled). Not runtime-tested: requires a physical 3ware 9000-series HBA with wedged firmware; the audit QEMU guest has no 3ware PCI device and no /dev/twa (env.txt), so neither baseline nor fix validation is possible there. Impact ceiling: root-triggered broken state sustained as an unprivileged disk-volume DoS (CAM timeout storms) until reboot; debug-kernel lock assertions possible.
No comments yet.