tws_intr_resp invokes req->cb with no busy-state/completer validation β firmware replay of an in-range req_id double-completes surrendered ccbs or calls a NULL cb (distinct from DF-1590's index OOB)
Summary
The response walk calls reqs[req_id].cb for every firmware-supplied in-range id without checking the request is in flight. cb is only reset on the NEXT allocation and never on release, so a duplicate/stale id re-runs tws_scsi_complete on a completed slot (second xpt_done, callout_stop on dead ccb) or early-completes a reallocated slot's live ccb. get_param additionally submits a NULL-cb req (latent). Loop terminates only when the firmware stops producing responses. Validate-state+cb dispatch fix (also closes DF-1590's window at this site).
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4400 Β· 6 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 2.4 KB | β raw | |
| VERDICT.md | β | 2.8 KB | β raw | |
| trigger.c | β | 712 B | view raw | |
| fix.diff | β | 1.0 KB | view raw | |
| verdict.json | β | 2.6 KB | view raw | |
| env.txt | β | 541 B | view raw |
DF-4400 β tws_intr_resp invokes req->cb with no state validation (double completion / NULL cb)
What
sys/dev/raid/tws/tws_cam.c:1156-1173:
while ( tws_get_response(sc, &req_id, &mfa) ) {
...
if ( req_id == TWS_INVALID_REQID ) { ... tws_err_complete(sc, mfa); continue; }
sc->reqs[req_id].cb(&sc->reqs[req_id]); /* <-- no bounds, no state check */
}
Two distinct defects beyond DF-1590 (which covers the out-of-bounds index):
- No busy-state validation.
req->cbis only initialized when a request is allocated (tws_get_request,tws.c:803-814, which also setscb = NULL) or explicitly by the submitter; it is never cleared on release. A firmware-fed in-rangereq_idthat is stale (duplicate response for an already-completed request, or a response for a FREE slot) invokes the old callback: - staletws_scsi_complete(tws_cam.c:738) on a completed request βxpt_done(ccb)a second time on a ccb CAM may already have recycled, andcallout_stop(req->ccb_ptr->ccb_h.timeout_ch)on dead ccb memory; - if the slot was reallocated,req->ccb_ptris the new, in-flight ccb β the new transaction is completed early while the firmware still owns it (biodone of an in-flight bio β the tws/twa sibling bug DF-4178's signature). - NULL callback call.
tws_get_param()deliberately submits withreq->cb = NULL(tws_cam.c:924) because it polls for the response (:941-942). If an interrupt ever delivers that response first,sc->reqs[3].cb(...)is a NULL function-pointer call β panic. (Today this is latent:tws_get_paramruns only at attach with interrupts masked,tws.c:547/tws_hdm.c:112-114β but nothing enforces it.)
Impact
Device-fed trigger (misbehaving/duplicating firmware β same threat model as
DF-1590): kernel NULL-call panic or CAM use-after-free / double-done list
corruption. The response loop also terminates only when the firmware stops
producing responses (tws_get_response false), so a hostile controller can keep
the interrupt handler spinning through arbitrary stale callbacks.
Trigger
No local trigger β requires the controller to emit a duplicate/stale response.
Documented in VERDICT.md; trigger.c is a compile-only stub that documents the
expected response word (16-bit request_id bitfield, tws_hdm.h:348-353).
Status
QEMU guest has no 3ware HBA β inconclusive; logic proven from source.
VERDICT β DF-4400
Status: inconclusive (hardware-gated). The defect is certain from source; triggering requires the 3ware controller to emit a duplicate or stale response (same device-fed trust that DF-1590 documents for the OOB-index aspect).
Proof chain
tws_get_response()(tws_hdm.c:354-381) extractsrequest_idfrom a 16-bit firmware bitfield (struct tws_outbound_response,tws_hdm.h:348-353).tws_intr_resp()(tws_cam.c:1161-1171) treats every non-TWS_INVALID_REQIDvalue as an in-flight request and callssc->reqs[req_id].cb(...)β no bounds check (that is DF-1590) and no check that the request is BUSY / has a completer.- Request lifecycle (
tws.c:744-828):cbis assigned per submission (tws_cam.c:738, 829, 875;tws_user.c:124) and reset to NULL only on the next allocation (tws.c:812); release paths (tws_q_insert_tailto FREE_Q attws_scsi_complete,tws_cam.c:374-376) never clear it, and reserved slots 0-3 are gated bystatealone (never queued). - Consequence for a stale/duplicate in-range id:
- completed slot:
tws_scsi_completeruns again βtws_q_remove_requestharmlessly returns NULL (tws_services.c:314-362) butcallout_stop(req->ccb_ptr->ccb_h.timeout_ch)(tws_cam.c:365) touches a ccb already surrendered viaxpt_done(:371), andxpt_doneruns a second time (:371) β CAM done-queue double-insert / UAF (ccb recycled by periph between the two completions). - reallocated slot: earlyxpt_doneof the new, firmware-owned ccb β biodone of an in-flight bio (identical class to twe DF-4178). - NULL-cb instance:
tws_get_paramsubmitsreqs[3]withcb = NULL(tws_cam.c:924, submit at:941) and polls (:942); any interrupt that delivers that response first (tws_intr_resp) calls NULL β panic. Currently masked becausetws_get_paramruns only during attach (tws_hdm.c:218-225βtws_init_ctlr:112) with interrupts off (tws.c:547, turned on only attws_hdm.c:114).
Impact ceiling
Panic (NULL cb, double-done) or CAM-layer memory corruption from device-controlled completion replay. Not locally triggerable by an unprivileged user; requires errant/hostile firmware β rated Medium to stay consistent with DF-1590 (same trust boundary, comparable primitive).
Fix
Validate the request before invoking the callback (and, belt-and-braces, the bounds β see fix.diff, which also closes DF-1590's window at this call site).
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 adds index+state+completer validation at the response-walk call site (also closes DF-1590's window at this site); not validatable without the controller.
fix.diff
Confirmed kernel references
Detail
Evidence (decisive lines)
VERDICT.md (lifecycle proof); fix.diff (state+bounds guard at the call site, also closing DF-1590's window here); trigger.c (response-word documentation)
PoC changes
no seed; documentation stub only (no local trigger exists without the controller)
Verified recommended fix
In tws_intr_resp, validate req_id < tws_queue_depth, cb != NULL and state != TWS_REQ_STATE_FREE before invoking the callback.
Verdict
Certain from source, hardware-gated to trigger. tws_intr_resp (tws_cam.c:1170) calls sc->reqs[req_id].cb() on every firmware-supplied non-invalid req_id with no bounds check (DF-1590's index aspect) and no busy-state/completer validation. Released requests keep stale cb pointers (set at tws_cam.c:738/829/875, cleared only on re-allocation at tws.c:812), so a duplicate/stale in-range id double-completes a surrendered ccb (second xpt_done at tws_cam.c:371 + callout_stop on dead ccb memory at :365) or early-completes a reallocated in-flight ccb (biodone of live bio, DF-4178 class). tws_get_param submits reqs[3] with cb=NULL (tws_cam.c:924) and polls (:942) β an interrupt delivering that response first is a NULL function call (panic); latent today only because get_param runs at attach with interrupts masked (tws.c:547, tws_hdm.c:112-114). The drain loop also terminates only when the firmware stops producing responses. Guest lacks the 3ware HBA, so not exercisable.
No comments yet.