dptexecuteccb send-failure path frees CCB without callout_stop β armed timeout later runs dpttimeout on stale/recycled state (premature abort of unrelated command / UAF read)
Summary
callout_reset arms the CCB timer before dpt_send_eata_command; its failure path unloads, frees and xpt_dones WITHOUT callout_stop (the normal completion path stops it). When the stale callout fires, dpttimeout reads the stale ccb and, if the dccb was re-dispatched, passes the ACTIVE check and EATA_SPECIFIC_ABORTs the new healthy command while stamping CAM_CMD_TIMEOUT. One-line callout_stop fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4243 Β· 6 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 982 B | β raw | |
| VERDICT.md | β | 1.2 KB | β raw | |
| env.txt | β | 1.0 KB | view raw | |
| fix.diff | β | 493 B | view raw | |
| verdict.json | β | 1.9 KB | view raw | |
| manifest.json | β | 614 B | view raw |
DF-4243 β send-failure path in dptexecuteccb leaves the timeout callout armed
dptexecuteccb arms the CCB timeout (callout_reset, dpt_scsi.c:778) before handing the command to the controller. If dpt_send_eata_command() then fails (HBA stays HA_ABUSY for ~1s: wedged/faulty controller), the error path at dpt_scsi.c:780-788 unloads the map, frees the dccb and xpt_done()s the ccb β but never callout_stop()s the armed timer (compare the normal completion path, which does: dpt_scsi.c:1572).
When the stale callout later fires, dpttimeout() runs on a freed/possibly
recycled state:
* dccb->ccb is stale β xpt_print_path(ccb->ccb_h.path) reads a possibly
recycled CAM ccb (dpt_scsi.c:1683-1685);
* if the dccb was already re-allocated to a new command, the
DCCB_ACTIVE check passes and dpttimeout sends EATA_SPECIFIC_ABORT
(dpt_scsi.c:1707) against the new, healthy command and stamps
CAM_CMD_TIMEOUT on its ccb β premature abort of unrelated I/O.
DF-4243 verdict β UNTESTED (hardware-gated); callout leak certain from source
Reproduced?
No β triggering requires a DPT controller wedged in HA_ABUSY long enough to fail dpt_send_eata_command (dpt_scsi.c:1111-1119), then the (tens of seconds) SCSI timeout to fire on the stale callout. No hardware in guest.
Certainty
The asymmetry is provable: callout_reset at dpt_scsi.c:778 vs no callout_stop in the 780-788 failure path, while the completion path stops it at 1572. After xpt_done() the CAM ccb (and its embedded timeout_ch) is owned by the periph again β a later dpttimeout() on it is a use-after-free read (xpt_print_path) at minimum, and a premature abort of whatever the recycled dccb is now doing at worst. Severity Low: requires a wedged HBA to arm, and the damage is mostly availability.
Fix validation
Not possible without hardware; fix.diff adds the one-line callout_stop.
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 against the read-only sys/ tree; kernel-level fix validation impossible without DPT SmartRAID hardware (none in guest, no QEMU emulation exists).
fix.diff
Confirmed kernel references
Detail
Evidence (decisive lines)
VERDICT.md; dpt_scsi.c:778-788 vs 1572; dpttimeout 1676-1711
PoC changes
No seed PoC existed; evidence authored from scratch in this run.
Verified recommended fix
callout_stop(ccb->ccb_h.timeout_ch) in the send-failure path before dptfreeccb/xpt_done (fix.diff).
Verdict
Untested on guest (hardware-gated). Source-certain: dpt_scsi.c:778 arms the timeout, the failure path 780-788 frees dccb+ccb without callout_stop (the normal path stops it at 1572). Later dpttimeout() runs on stale pointers (1683-1707): UAF read via xpt_print_path or premature EATA_SPECIFIC_ABORT of a re-dispatched command.
No comments yet.