trm_Reselect spins forever (no exit) if reselected target/LUN does not match any DCB - interrupt-context DoS
Summary
trm_Reselect walks circular DCB list looking for DCB whose TargetID/LUN matches reselected ID. If no DCB matches (malicious/misbehaving SCSI device reselects with never-enumerated ID/LUN) while loop has no escape spins at interrupt context hanging CPU freezing all interrupt processing. DCB list circular no iteration counter no NULL/identity exit. Hardware already accepted reselection only way out bus reset.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-2523 Β· 14 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 3.0 KB | β raw | |
| VERDICT.md | β | 5.5 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 | β | 261 B | view raw | |
| build.log | β | 5.7 KB | view raw | |
| run.log | β | 5.2 KB | view raw | |
| run.patched.log | β | 1.0 KB | view raw | |
| fix.diff | β | 983 B | view raw | |
| fix_build.log | β | 5.6 MB | β download | |
| env.txt | β | 710 B | view raw | |
| verdict.json | β | 4.0 KB | view raw |
DF-2523 β PoC evidence pack (verify run, 2026-10-14)
Finding: sys/dev/disk/trm/trm.c β trm_Reselect() walks the circular
DCB list looking for the reselecting target/LUN with no exit when
nothing matches (trm.c:2428-2431):
pDCB = pACB->pLinkDCB;
while (RselTarLunId != *((u_int16_t *) &pDCB->TargetID)) {
pDCB = pDCB->pNextDCB;
}
RselTarLunId is a 13-bit register value (& 0x1FFF, trm.c:2426) while
a DCB can only ever pack to (LUN<<8)|ID <= 0x070F β a reselection with
any higher bit set (malicious or glitching SCSI device driving the
reselection phase) matches nothing and the loop spins forever at
interrupt context.
How it was verified
Same production-handler rig as DF-2522/24 (df252x_trm_rig.c, zero
driver code): fabricated post-attach ACB with a circular 3-DCB ring
(ID/LUN = 1/0, 2/0, 3/2), the TRM-S1040 register file emulated behind
the kernel's own bus_space accessors, and the running kernel's own
trm_Interrupt() dispatched with INT_RESELECTED.
- control (
go=4):TRMREG_SCSI_TARGETID=0x0203matches ring[2] β productiontrm_Reselectcompletes (pActiveDCB=ring[2], TmpSRB armed withSRB_UNEXPECT_RESEL,MSG_ABORTin MsgOutBuf,DO_DATALATCHwritten) and returns; - DF-2523 (
go=5):TRMREG_SCSI_TARGETID=0x1000β matches no DCB. Dispatch runs in a dedicated kernel thread (df2523_t1) with 3s/6s/9s heartbeat callouts.
What the run showed (stock kernel)
df2523[T1]: entering production trm_Interrupt (RESELECTED,
RselTarLunId=0x1000 matches NO DCB of the 3-DCB circular list)
df2523[HB]: *** NON-TERMINATION CONFIRMED: 3.-08038 s inside
trm_Interrupt/trm_Reselect - the DCB ring walk
(trm.c:2428-2431) has no exit ***
df2523[HB]: *** NON-TERMINATION CONFIRMED: 6.-07982 s ... ***
df2523[HB]: *** NON-TERMINATION CONFIRMED: 9.002041 s ... ***
The "RETURNED" marker never printed. Within ~1 minute the whole guest
wedged: ssh stopped answering (connection banner timeout), the serial
console stopped echoing, no kernel panic was printed, and recovery
required killing QEMU (vm.sh down: "guest not answering (likely DDB on
panic)"). An unkillable kernel thread spinning in production code starves
DragonFly's per-CPU user scheduler β the same non-termination is a
hard, unrecoverable lockup on real hardware where the loop runs in the
interrupt handler at crit priority (clock included).
Build / run (in guest, as root) β run LAST, the guest does not recover
sh build.sh kldload trm sh run.sh # control(4) returns; DF-2523(5) wedges the guest
Fix validation
fix.diff bounds the walk by pACB->DeviceCnt (the driver's own ring
bookkeeping) and, when the reselection matches no DCB, drops the
connection (STOPDMAXFER + DO_CLRFIFO|DO_HWRESELECT) and returns
instead of spinning. Validated on the rebuilt kernel: go=5 returns
immediately (df2523[T1]: ... RETURNED), no heartbeat fires, the guest
stays healthy; the matching control still completes exactly as before.
DF-2523 VERDICT β REPRODUCED (production handler, stock module; guest wedged)
Status: reproduced (DoS) β the production trm_Reselect() DCB ring
walk (sys/dev/disk/trm/trm.c:2428-2431) has no exit when the
reselecting target/LUN matches no DCB. Dispatching the running kernel's
own trm_Interrupt() with INT_RESELECTED and an unmatchable
RselTarLunId never returned: heartbeat callouts confirmed the thread
inside the loop at 3 s, 6 s and 9 s, and the guest subsequently wedged
completely (ssh dead, serial console dead, no panic printed; recovery
required killing QEMU). The bounded-walk fix returns immediately on a
rebuilt module.
Rig (production code)
Same production-handler rig as DF-2522/24: real /boot/kernel/trm.ko
loaded, fabricated post-attach ACB with a circular 3-DCB ring
(ID/LUN = 1/0, 2/0, 3/2 β DeviceCnt=3, production ring bookkeeping),
TRM-S1040 register file emulated behind the kernel's own bus_space
accessors, dispatch of the module's real trm_Interrupt()
(0xffffffff826001d40).
- Control (
go=4):TRMREG_SCSI_TARGETID = 0x0203packs exactly as*(u_int16_t*)&DCB->TargetIDfor ring[2] (TargetID=3, LUN=2 β the comparison at trm.c:2428 reads TargetID | LUN<<8). Productiontrm_Reselectfound it, armed the unexpected-reselection abort (pActiveDCB=ring[2],pActiveSRB=&TmpSRB,TmpSRB.SRBState=SRB_UNEXPECT_RESEL,MSG_ABORTinMsgOutBuf,DO_DATALATCHwritten) and returned β behavioral proof the production code executed. - DF-2523 (
go=5):TRMREG_SCSI_TARGETID = 0x1000. The driver masks the register with 0x1FFF (trm.c:2426) but a DCB can only pack to(LUN<<8)|ID β€ 0x070F(16 targets, 8 LUNs), so bits 11-12 are unmatchable by construction β a reselection driven by a malicious or glitching SCSI device (the chip's hardware reselect is enabled,DO_HWRESELECT) enters a loop with no exit: the list is circular, there is no iteration bound, no counter, no NULL/identity exit (trm.c:2428-2431).
What the run showed (stock module, run.log)
Dispatch ran in a dedicated kernel thread (df2523_t1) with 3 s
heartbeat callouts:
df2523[T1]: entering production trm_Interrupt (RESELECTED,
RselTarLunId=0x1000 matches NO DCB of the 3-DCB circular list)
df2523[HB]: *** NON-TERMINATION CONFIRMED: 3.-08038 s inside
trm_Interrupt/trm_Reselect *** (Γ3: 3 s, 6 s, 9 s)
The RETURNED marker never printed. Within roughly a minute the whole
guest wedged: further ssh commands produced "Connection timed out during
banner exchange", vm.sh status went down, and shutdown reported
"guest not answering (likely DDB on panic); killing qemu" β an
unkillable kernel thread spinning in production driver code starved
DragonFly's per-CPU user scheduler until userland (sshd, getty, the
serial console) stopped responding. No kernel panic was printed β the
machine was simply dead.
On real hardware the same loop runs in the driver's interrupt handler
(registered via bus_setup_intr, trm_attach), i.e. at interrupt/crit
priority: the loop hard-locks the CPU (clock interrupts included) the
moment it is entered β strictly worse than the rig's thread-context
demonstration. The only recovery is the SCSI bus reset that can never
happen because the CPU that would issue it is the one spinning.
Fix validation (fix.diff, run.patched.log, fix_build.log)
The fix bounds the walk by the driver's own ring bookkeeping
(pACB->DeviceCnt) and, when the reselection matches no DCB, drops the
connection (STOPDMAXFER + DO_CLRFIFO|DO_HWRESELECT β the recovery
the driver's pDCB==NULL exception branch already uses) and returns.
It deliberately does NOT loop on DO_RSTSCSI (that would add a second
unbounded register poll). Validated on the rebuilt module (same
fix.all.diff kernel/module build as DF-2522/24; this scenario touches
only the reselect hunk):
df2523[T1]: entering production trm_Interrupt (RESELECTED,
RselTarLunId=0x1000 matches NO DCB of the 3-DCB circular list)
df2523[T1]: trm_Interrupt RETURNED (unreachable on vulnerable kernel)
No heartbeat fired, uptime answered, guest stayed healthy; the
matching control (go=4) behaved identically to stock
(pActiveDCB=ring[2], TmpSRB abort path).
Things checked and killed
- "The chip would never deliver such a value": the register is 13 bits and the driver masks 0x1FFF while DCB encodings cap at 0x070F β any stray high bit (device-controlled reselection bytes) yields an unmatchable value. Premise stands.
- My rig's spin could be a rig artifact: ruled out by the control dispatch returning through the identical production path with only the register value different (match vs no-match is the sole differential).
- Wedge attribution: heartbeat non-termination precedes the wedge; nothing else runs in T1. The wedge is the OS-level consequence of the non-terminating production loop.
Bottom line
Real bug, demonstrated through the running kernel's own driver code: non-termination proven at 3/6/9 s + total guest lockup requiring QEMU kill; bounded-walk fix returns immediately, verified on a rebuilt module. Gate: machines with Tekram DC395/TRM-S1040 HBAs and a misbehaving/malicious SCSI target (or bus glitch).
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: the unmatchable reselection (0x1000) returns immediately - 'df2523[T1]: trm_Interrupt RETURNED', no heartbeat markers, guest healthy (uptime answered); the matching control behaves exactly as stock (pActiveDCB=ring[2], TmpSRB abort path armed).
run.patched.log: 'df2523[T1]: ... RETURNED (unreachable on vulnerable kernel)' with zero HB markers, vs run.log stock: three NON-TERMINATION markers + total guest wedge requiring QEMU kill.
Confirmed kernel references
Detail
Exploit chain
DoS primitive only (non-terminating interrupt-context loop => unrecoverable machine lockup); no escalation path.
Evidence (decisive lines)
run.log (full serial): df2523[T1] dispatch lines + three 'df2523[HB]: *** NON-TERMINATION CONFIRMED ... no exit ***' markers at 3/6/9s, no RETURNED marker, guest subsequently unresponsive (vm.sh down reported 'guest not answering (likely DDB on panic); killing qemu'); run.patched.log: 'df2523[T1]: trm_Interrupt RETURNED (unreachable on vulnerable kernel)' with no heartbeat, guest healthy.
PoC changes
No seed PoC existed (empty pack). Built from scratch: production-handler dispatch rig (kldload real trm.ko; trm_Interrupt resolved at kldstat base + nm offset 0x1d40); scenario 5 dispatches in a dedicated kernel thread (df2523_t1) with 3s/6s/9s heartbeat callouts so non-termination is provable before the wedge; matching-ID control (go=4) proves the same production path returns.
Verified recommended fix
Bound the reselect DCB walk by pACB->DeviceCnt and bail out with bus recovery (STOPDMAXFER + DO_CLRFIFO|DO_HWRESELECT) when no DCB matches.
Verdict
Reproduced with the production handler: dispatched the real trm_Interrupt() of kldload'd /boot/kernel/trm.ko with INT_RESELECTED and TRMREG_SCSI_TARGETID=0x1000 (the driver masks the register with 0x1FFF at trm.c:2426, but a DCB can only pack to (LUN<<8)|ID <= 0x070F - any reselection with stray high bits is unmatchable by construction). The production DCB ring walk at trm.c:2428-2431 (circular list, no bound, no escape) never returned: a dedicated kernel thread sat inside it through 3s/6s/9s heartbeat checkpoints, and the guest then wedged completely - ssh stopped answering, the serial console went dead, no panic was printed, and recovery required killing QEMU. On real hardware the loop runs in the interrupt handler at crit priority and hard-locks the CPU outright. Control dispatch (matching ID 0x0203) returns through the identical production path, isolating the no-match loop as the sole differential. The bounded-walk fix (DeviceCnt-bounded search + drop-connection recovery on miss) returns immediately on a rebuilt module.
No comments yet.