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)
PoC verification
Evidence pack
findings/poc/DF-2524 Β· 15 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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.
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
fixedRebuilt 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'.
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.
No comments yet.