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

trm_Disconnect dereferences pDCB->pActiveSRB without NULL check kernel panic on spurious disconnect (acknowledged in source)

Summary

trm_Disconnect fetches pSRB=pDCB->pActiveSRB and immediately dereferences pSRB->pccb. Author left literal comment bug pSRB=0 at :2355 acknowledging unguarded dereference. DISCONNECT or selection-timeout interrupt arriving without prior successful selection (pActiveSRB would have been set by trm_StartSCSI) NULL-dereferences panics kernel. pActiveSRB only assigned in trm_StartSCSI:1287 and trm_Reselect cleared to NULL in trm_Disconnect:2402 after SRB_COMPLETED. Malicious/misbehaving SCSI device can trigger spurious DISCONNECT/SELTIMEOUT.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-2524 Β· 15 files
FileTypeDescriptionSize
README.md β€” 2.1 KB ↓ raw
VERDICT.md β€” 4.9 KB ↓ raw
df252x_trm_rig.c β€” 18.2 KB view raw
trm.h β€” 34.2 KB view raw
Makefile β€” 125 B ↓ download
build.sh β€” 192 B view raw
run.sh β€” 193 B view raw
build.log β€” 5.7 KB view raw
run.log β€” 2.3 KB view raw
run.patched.log β€” 394 B view raw
panic.txt β€” 2.3 KB view raw
fix.diff β€” 636 B view raw
fix_build.log β€” 5.6 MB ↓ download
env.txt β€” 710 B view raw
verdict.json β€” 3.9 KB view raw

DF-2524 β€” PoC evidence pack (verify run, 2026-10-14)

Finding: sys/dev/disk/trm/trm.c β€” trm_Disconnect() fetches pSRB = pDCB->pActiveSRB and immediately dereferences pSRB->pccb (trm.c:2354-2356). The author left a literal acknowledgment at :2355: /* bug pSRB=0 */. A DISCONNECT / selection-timeout interrupt arriving while pACB->pActiveDCB != NULL but that DCB has no active SRB (pActiveSRB == NULL β€” e.g. racing bus-reset recovery (trm_RecoverSRB) / abort paths clearing it, or a misbehaving device asserting disconnect before selection completed) NULL-dereferences and panics the kernel.

How it was verified

Same production-handler rig (df252x_trm_rig.c, zero driver code): fabricated ACB with pActiveDCB β†’ a DCB whose pActiveSRB is NULL, register-file emulation behind the kernel's own bus_space accessors, and the running kernel's own trm_Interrupt() dispatched with INT_DISCONNECT (the same branch a selection timeout takes, trm.c:1323-1325).

What the run showed (stock kernel)

df2524: offsetof(SRB,pccb)=88 -> faulting read at 0x58
df2524: dispatching production trm_Interrupt: DISCONNECT with
        pACB->pActiveDCB=0xfffff8008d9e4560, pDCB->pActiveSRB=NULL
Fatal user address access from kernel mode from sysctl at ffffffff82601ea6
Fatal trap 12: page fault while in kernel mode
fault virtual address   = 0x58
instruction pointer = 0x8:0xffffffff82601ea6     <- trm.ko base + 0x1ea6
Stopped at      trm_Interrupt+0x166:    movq    0x58(%r12),%rax
db>

0x58 = 88 = offsetof(struct _SRB, pccb) β€” the exact pSRB->pccb->ccb_h.target_id read at trm.c:2356 with pSRB == NULL. Kernel panicked into ddb; full serial capture in panic.txt.

Build / run (in guest, as root)

sh build.sh
kldload trm
sh run.sh                # kernel panics; capture with: vm.sh log 40

Fix validation

fix.diff adds the missing NULL guard: a disconnect with no active SRB now recovers the bus (DO_CLRFIFO | DO_HWRESELECT, same recovery the pDCB == NULL exception branch uses) and returns. Validated on the rebuilt kernel: go=3 prints df2524: trm_Interrupt RETURNED - no NULL deref (guard present) and the guest stays up.

VERDICT.md
↓ download raw

DF-2524 VERDICT β€” REPRODUCED (production handler, stock module; kernel panic)

Status: reproduced (panic) β€” the production trm_Disconnect() (sys/dev/disk/trm/trm.c:2354-2356) fetches pSRB = pDCB->pActiveSRB and immediately dereferences pSRB->pccb with no NULL guard (the author's own comment at :2355: /* bug pSRB=0 */). Dispatching the running kernel's own trm_Interrupt() with INT_DISCONNECT while pACB->pActiveDCB != NULL and that DCB's pActiveSRB == NULL produced an immediate kernel panic: Fatal trap 12, fault address 0x58 β€” exactly offsetof(struct _SRB, pccb). The guard fix returns cleanly on a rebuilt module.

Rig (production code)

Same production-handler rig as DF-2522/23: real /boot/kernel/trm.ko loaded (trm_Disconnect is inlined into trm_Interrupt), fabricated ACB/DCB, register file emulated behind the kernel's own bus_space accessors, dispatch of the module's real trm_Interrupt() (0xffffffff826001d40). The chip choreography: SCSI_STATUS has SCSIINTERRUPT, SCSI_INTSTATUS = INT_DISCONNECT β€” the same branch a selection timeout takes (trm.c:1323-1325).

What the run showed (stock module, panic.txt / run.log)

df2524: offsetof(SRB,pccb)=88 -> faulting read at 0x58
df2524: dispatching production trm_Interrupt: DISCONNECT with
        pACB->pActiveDCB=0xfffff8008d9e4560, pDCB->pActiveSRB=NULL
Fatal user address access from kernel mode from sysctl at ffffffff82601ea6
Fatal trap 12: page fault while in kernel mode
cpuid = 0; lapic id = 0
fault virtual address   = 0x58
fault code      = supervisor read data, page not present
instruction pointer = 0x8:0xffffffff82601ea6      (= trm.ko + 0x1ea6)
stack pointer           = 0x10:0xfffff801182f56f8
current process     = 854 (the sysctl dispatch thread)
kernel: type 12 trap, code=0
Stopped at      trm_Interrupt+0x166:    movq    0x58(%r12),%rax
db>

0x58 = 88 = offsetof(struct _SRB, pccb) (trm.h:160): the faulting instruction is precisely the pSRB->pccb load of target_id = pSRB->pccb->ccb_h.target_id (trm.c:2356) with pSRB == NULL in %r12. Kernel trapped into ddb β€” unrecoverable panic. Full serial capture in panic.txt.

When the state occurs on hardware

pActiveDCB and the DCB's pActiveSRB are set together by trm_StartSCSI (trm.c:1286-1287), but they are not cleared together everywhere: trm_Disconnect itself clears pDCB->pActiveSRB at trm.c:2402 (SRB completion) after pACB->pActiveDCB survives other paths β€” e.g. trm_ScsiRstDetect only clears pActiveDCB in its RESET_DETECT branch (trm.c:2805), and the bus-reset/recovery/abort machinery (trm_RecoverSRB, trm_DoingSRB_Done) requeues and clears SRB lists while a device keeps asserting disconnect/selection-timeout interrupts. A misbehaving SCSI target can drive DISCONNECT/ SELTIMEOUT interrupts at any time (both take the same branch, trm.c:1323); whenever one lands in the window where the DCB is still the active DCB but its pActiveSRB has been cleared, the driver panics. The author flagged exactly this with /* bug pSRB=0 */. The rig fabricates that acknowledged state directly and shows the unguarded deref is a kernel panic β€” the missing-guard defect itself, not a new state-machine claim.

Fix validation (fix.diff, run.patched.log, fix_build.log)

The fix adds the missing guard: a disconnect with no active SRB now recovers the bus (DO_CLRFIFO | DO_HWRESELECT, the same recovery the pDCB == NULL exception branch uses at trm.c:2349-2352) and returns. (It deliberately omits that branch's crit_exit() β€” see incidental note below.) On the rebuilt module (same fix.all.diff build as DF-2522/23; this scenario touches only the Disconnect hunk):

df2524: dispatching production trm_Interrupt: DISCONNECT with
        pACB->pActiveDCB=0xfffff8008faabc70, pDCB->pActiveSRB=NULL
df2524: trm_Interrupt RETURNED - no NULL deref (guard present)

Guest stayed up; no trap. Baseline (panic) vs patched (clean return) is the decisive pair.

Incidental observation (not part of this finding)

The adjacent exception branch at trm.c:2343-2352 (pDCB == NULL) ends with a crit_exit() that has no matching crit_enter() anywhere in trm_Disconnect β€” an unbalanced crit-section exit (critcount underflow) on that path. My fix's guard does not replicate it. Worth a separate look.

Bottom line

Real bug, acknowledged in-source, reproduced as a hard kernel panic through the running kernel's own driver code at the exact cited instruction; one-guard fix eliminates it, verified on a rebuilt module. Gate: machines with Tekram DC395/TRM-S1040 HBAs and a misbehaving device (or racing recovery) producing the no-active-SRB disconnect.

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

fixed
baseline reproduced→ patch + rebuild →patched clean

Rebuilt the driver module from the patched source (same nativekernel build as DF-2522, full log fix_build.log) and re-ran the identical scenario against the patched module's own trm_Interrupt: 'df2524: trm_Interrupt RETURNED - no NULL deref (guard present)', guest stayed up, no trap. Baseline panic gone.

run.patched.log: 'df2524: trm_Interrupt RETURNED - no NULL deref (guard present)' vs panic.txt: 'Fatal trap 12 ... fault virtual address = 0x58 ... Stopped at trm_Interrupt+0x166: movq 0x58(%r12),%rax'.
↓ fix.diffper-fix-DF-2524

Confirmed kernel references

Detail

Exploit chain

NULL-deref kernel panic (read at 0x58); no info-leak or control-flow primitive - impact is denial of service (crash).

Evidence (decisive lines)

panic.txt / run.log: 'Fatal trap 12: page fault while in kernel mode ... fault virtual address = 0x58 ... Stopped at trm_Interrupt+0x166: movq 0x58(%r12),%rax'; rig pre-printed 'offsetof(SRB,pccb)=88 -> faulting read at 0x58'; run.patched.log: 'df2524: trm_Interrupt RETURNED - no NULL deref (guard present)', guest up.

PoC changes

No seed PoC existed (empty pack). Built from scratch: production-handler dispatch rig (kldload real trm.ko; trm_Disconnect reached via the module's inlined trm_Interrupt at kldstat base + nm offset 0x1d40); INT_DISCONNECT choreography through register-file emulation behind the kernel's own bus_space accessors.

Verified recommended fix

Guard the fetch: if (pSRB == NULL) recover the bus (DO_CLRFIFO|DO_HWRESELECT write) and return instead of dereferencing pSRB->pccb.

Verdict

Reproduced with the production handler: dispatched the real trm_Interrupt() of kldload'd /boot/kernel/trm.ko with INT_DISCONNECT while pACB->pActiveDCB points at a DCB whose pActiveSRB is NULL. Production trm_Disconnect (trm.c:2354-2356, the author's own in-source comment at :2355 reads 'bug pSRB=0') immediately executes target_id = pSRB->pccb->ccb_h.target_id with pSRB==NULL: Fatal trap 12, fault virtual address 0x58 = offsetof(struct _SRB, pccb) (trm.h:160), instruction pointer trm_Interrupt+0x166 (module base 0xffffffff82600000 + 0x1ea6), 'Stopped at trm_Interrupt+0x166: movq 0x58(%r12),%rax' - the exact cited dereference. Kernel trapped into ddb; serial capture in panic.txt. The state is the one the author acknowledged: pActiveDCB and pActiveSRB are set together by trm_StartSCSI (trm.c:1286-1287) but not cleared together (trm.c:2402 clears pActiveSRB while reset/abort recovery paths handle pActiveDCB separately), and a misbehaving device can assert DISCONNECT/SELTIMEOUT (same branch, trm.c:1323) at any time. The NULL-guard fix returns cleanly on a rebuilt module.