β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-3833

Stale completion after bus-reset recovery: trm_Interrupt dereferences NULL pActiveDCB/pActiveSRB (KASSERT only guards INVARIANTS), and trm_SRBdone's going-list unlink walk has no termination

Summary

Sibling paths of known DF-2524. INT_BUSSERVICE|INT_CMDDONE with pActiveDCB==NULL (cleared at three sites while interrupts stay enabled) reaches pSRB = pDCB->pActiveSRB - on GENERIC (KASSERT compiled out) a NULL-page read panic. Independently, trm_RecoverSRB empties the going list without clearing pDCB->pActiveSRB, so a stale completion routed through disc1 -> trm_SRBdone walks the unlink loop off the list end -> NULL deref. Hostile or flaky SCSI target: bus reset with tagged commands in flight + stale chip interrupt -> kernel panic (local DoS). Timing-dependent, hence speculative.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3833 Β· 6 files
FileTypeDescriptionSize
README.md β€” 2.2 KB ↓ raw
VERDICT.md β€” 2.6 KB ↓ raw
verdict.json β€” 3.1 KB view raw
fix.diff β€” 1.3 KB view raw
env.txt β€” 1.1 KB view raw
manifest.json β€” 676 B view raw

DF-3833 β€” stale completion after bus-reset recovery: trm_Interrupt derefs NULL pActiveDCB/pActiveSRB; trm_SRBdone walks the going list without termination

  • File: sys/dev/disk/trm/trm.c (pass 2)
  • Class: CWE-476 NULL deref / CWE-835 unbounded walk β†’ kernel panic (DoS), hardware-timing-gated
  • Severity: Low, speculative (needs a stale chip interrupt in a specific window; sibling path of the known DF-2524 which was trm_Disconnect's pActiveSRB)

Build / Run / Expected

Hardware-gated (env.txt: no Tekram HBA on the audit guest). Trigger on hardware: issue camcontrol reset / bus reset (XPT_RESET_BUS) or provoke an external bus reset while tagged commands are in flight; the chip raises INT_BUSSERVICE/INT_CMDDONE for an aborted command after the driver already cleared pACB->pActiveDCB.

Root cause

  1. trm_Interrupt, trm.c:1337-1345: pDCB = pACB->pActiveDCB; KASSERT(pDCB != NULL, ("no active DCB")); pSRB = pDCB->pActiveSRB; <- also unchecked against NULL Windows where pActiveDCB is NULL while the chip can still signal INT_BUSSERVICE|INT_CMDDONE:
    • trm_Disconnect sets pACB->pActiveDCB = NULL (trm.c:2359) and the next command is only started later by trm_DoWaitingSRB;
    • trm_reset β†’ trm_DoingSRB_Done completes/frees all going SRBs and clears pActiveDCB (trm.c:1184-1185) before re-enabling interrupts (1176-1178) and starting new commands;
    • trm_ScsiRstDetect clears pActiveDCB (trm.c:2805). On a GENERIC kernel (no INVARIANTS β€” KASSERT compiles to nothing, sys/sys/systm.h:117) a stale interrupt in that window panics at pDCB->pActiveSRB (NULL+offset read); on an INVARIANTS kernel the KASSERT fires β€” either way, panic.
  2. trm_SRBdone, trm.c:2718-2729: if the completing SRB is not the list head, the walk while (psrb->pNextSRB != pSRB) psrb = psrb->pNextSRB; has no termination. trm_RecoverSRB (trm.c:1141-1160) moves every going SRB to the waiting list on external-reset detection, but does NOT clear pDCB->pActiveSRB; a stale completion routed through trm_Disconnect→disc1→trm_SRBdone then walks off the end of the (now empty or reordered) going list and NULL-derefs psrb->pNextSRB.
VERDICT.md
↓ download raw

VERDICT β€” DF-3833

status: inconclusive (speculative, hardware-gated) β€” reproduced: 0 β€” guest impact: none.

Window analysis

  1. trm_Interrupt BUSSERVICE/CMDDONE path (trm.c:1337-1350). pACB->pActiveDCB is: - cleared at trm.c:2359 in trm_Disconnect (every disconnect, before trm_DoWaitingSRB starts the next command); - cleared at trm.c:1185 in trm_reset after trm_DoingSRB_Done freed all going SRBs and after interrupts were re-enabled at trm.c:1176-1178 (EN_SCSIINTR at 3369 keeps bus-service interrupts on); - cleared at trm.c:2805 in trm_ScsiRstDetect (external reset). In any of these windows an INT_BUSSERVICE/INT_CMDDONE from the aborted command reaches pSRB = pDCB->pActiveSRB with pDCB == NULL: on X86_64_GENERIC (KASSERT compiled out, sys/sys/systm.h:117) that is a NULL-page read β†’ panic; on INVARIANTS the KASSERT itself panics. Note pDCB->pActiveSRB is also never NULL-checked even when pDCB is valid β€” pActiveSRB is NULL between disc1 (trm.c:2402) and the next start.
  2. trm_SRBdone unlink walk (trm.c:2718-2729). while (psrb->pNextSRB != pSRB) assumes the SRB is on the going list. trm_RecoverSRB (trm.c:1141-1160) moves all going SRBs of every DCB to their waiting lists on external-reset detection β€” but leaves pDCB->pActiveSRB pointing at the (now waiting-list) SRB. A subsequent stale completion routed through trm_Disconnect β†’ disc1 β†’ trm_SRBdone (trm.c:2397-2404) then walks past the emptied list and derefs psrb->pNextSRB with psrb == NULL.

Sibling of known DF-2524 (trm_Disconnect's own pActiveSRB deref) β€” this finding covers the two other members of the same stale-completion family that pass 1 did not file.

Why not reproduced

No Tekram adapter on the audit guest (env.txt); the trigger additionally requires chip-level timing (stale interrupt between reset recovery and next selection). Confidence: speculative (reachability timing-dependent), the missing checks are certain in source.

Fix

fix.diff: drop stale BUSSERVICE/CMDDONE interrupts when pActiveDCB/pActiveSRB is NULL, and make the going-list walk terminating (psrb != NULL guard + skip-unlink path). git apply --check passes.

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_testable
baseline no→ patch + rebuild →patched clean

Needs Tekram hardware plus reset/interrupt racing; absent on the audit guest. fix.diff is git-apply-able (verified).

fix.diff
↓ fix.diffper-fix-DF-3833

Confirmed kernel references

Detail

Exploit chain

hostile/flaky SCSI target: keep a tagged command in flight -> force bus reset (or wait for external reset) -> chip raises stale bus-service interrupt before the next StartSCSI re-arms pActiveDCB -> kernel panic (local DoS). No privilege gain.

Evidence (decisive lines)

README.md (both windows with line refs and the RecoverSRB non-cleanup); env.txt; fix.diff (NULL guards + terminating walk, git-apply --check passes)

PoC changes

No PoC β€” requires Tekram hardware and reset/interrupt racing; analysis + fix only.

Verified recommended fix

NULL-guard pActiveDCB/pActiveSRB in trm_Interrupt before the phase dispatch and terminate the trm_SRBdone going-list walk (fix.diff).

Verdict

Two sibling panic windows around the reset paths that pass-1's DF-2524 (trm_Disconnect pActiveSRB NULL) did not cover. (1) trm_Interrupt (sys/dev/disk/trm/trm.c:1337-1343): INT_BUSSERVICE|INT_CMDDONE dereferences pACB->pActiveDCB under a KASSERT and then pDCB->pActiveSRB with no NULL check; pActiveDCB is NULL in real windows β€” trm_Disconnect clears it at trm.c:2359 before the next start, trm_reset clears it at trm.c:1185 after completing all going SRBs but after re-enabling interrupts (trm.c:1176-1178), and trm_ScsiRstDetect at trm.c:2805 β€” so a stale chip completion in those windows panics on GENERIC (KASSERT is INVARIANTS-only, sys/sys/systm.h:94-118) via the NULL pDCB->pActiveSRB read. (2) trm_SRBdone (trm.c:2720-2724) unlinks the completed SRB with an unbounded 'while (psrb->pNextSRB != pSRB)' walk; trm_RecoverSRB (trm.c:1141-1160) empties the going list on external-reset detection without clearing pDCB->pActiveSRB, so a stale completion of a recovered SRB walks off the list end and NULL-derefs. Both need adversarial chip timing (bus reset mid-command + stale interrupt) β€” plausible with flaky or hostile targets, not demonstrable on the audit guest (no Tekram HBA, env.txt). Speculative-likely, hardware-gated panic/DoS.