Type-confused FCPARAM() write in RQCS_RESET_OCCURRED handler: on dual-bus SCSI cards a routine bus reset performs a 32-bit RMW ~21 KB past the 408-byte isp_param heap allocation
Summary
isp_parse_status()'s RQCS_RESET_OCCURRED case loops 'for (chan = 0; chan < isp->isp_nchan; chan++) FCPARAM(isp, chan)->sendmarker = 1;'. FCPARAM() casts isp->isp_param to (fcparam *), but SCSI cards allocate nchan*sizeof(sdparam). With the real kernel headers on x86-64: sizeof(sdparam)=204, sizeof(fcparam)=21576, so on dual-bus SCSI cards (ISP1080/1240/1280/12160, nchan=2) the chan=1 iteration writes at isp_param+21576 - 21,168 bytes past the 408-byte kmalloc. RQCS_RESET_OCCURRED is the status firmware returns for commands destroyed by a SCSI bus reset, so any reset with in-flight I/O triggers it. Every other sendmarker write in the file goes through the IS_FC()-selecting ISP_SET_SENDMARKER macro - this one does not. Driver is in GENERIC, isp_1080.ko/isp_12160.ko ship in /boot/kernel. Local attacker or merely operational reality: every bus reset that destroys an in-flight command corrupts an unrelated kernel heap object ~21 KB away (RMW setting bit 3 of one word) - silent data corruption or panic; a malicious SCSI target can force resets at will. PROOF: write-arithmetic harness on the guest against verbatim kernel headers - 'chan=1: FAULT offset 21576 from allocation start (21168 past end of 408-byte alloc)' observed twice. In-kernel repro impossible (no QLogic HBA) -> inconclusive. Fix: one-line ISP_SET_SENDMARKER substitution.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3582 Β· 9 files| File | Type | Description | Size | |
|---|---|---|---|---|
| trigger_df3582.c | β | 4.0 KB | view raw | |
| build.sh | β | 214 B | view raw | |
| run.sh | β | 97 B | view raw | |
| build.log | β | 1.2 KB | view raw | |
| run.log | β | 2.5 KB | view raw | |
| VERDICT.md | β | 3.7 KB | β raw | |
| verdict.json | β | 3.8 KB | view raw | |
| fix.diff | β | 457 B | view raw | |
| manifest.json | β | 1.1 KB | view raw |
DF-3582 β Type-confused FCPARAM() write in RQCS_RESET_OCCURRED handler (isp.c)
WHAT sys/dev/disk/isp/isp.c:6105-6110, inside isp_parse_status():
case RQCS_RESET_OCCURRED:
{
int chan;
isp_xs_prt(isp, xs, ISP_LOGWARN, "Bus Reset destroyed command");
for (chan = 0; chan < isp->isp_nchan; chan++) {
FCPARAM(isp, chan)->sendmarker = 1; /* <-- BUG */
}
FCPARAM() (ispvar.h:644) casts isp->isp_param to (fcparam *). On SCSI
cards isp_param is allocated as nchan * sizeof(sdparam) (isp_pci.c:827-832);
fcparam and sdparam are unrelated layouts:
sizeof(sdparam) = 204 (x86-64, real kernel headers, guest cc 8.3)
sizeof(fcparam) = 21576 (portdb[512] x 40 dominates)
sendmarker is bit 3 of the first 32-bit word of fcparam, so the write is
a read-modify-write at isp_param + chan*21576.
REACHABILITY * isp_parse_status() runs for every non-24XX completion β i.e. for ALL parallel SCSI cards (isp_intr, isp.c:5280). * RQCS_RESET_OCCURRED (0x0004, ispmbox.h:728) is the completion status the firmware returns for in-flight commands destroyed by a SCSI bus reset β a routine operational event (power glitch, device hot-plug, watchdog RESET_BUS, malicious target asserting RST). * Single-bus SCSI card (nchan=1): chan=0 write lands in sdparam's unnamed 8-bit pad β type confusion but benign. * DUAL-BUS SCSI card (ISP1080/1240/1280/12160, isp_pci.c:732/749 set nchan=2): chan=1 write lands 21576 bytes into a 408-byte kmalloc β 21,168 bytes PAST the end of the heap allocation.
PROOF (this pack) geom2.c / trigger_df3582.c include the REAL kernel headers (dev/disk/isp/isp_stds.h, ispmbox.h, ispvar.h copied verbatim from the guest's /usr/src) and execute the identical bitfield RMW against a 408-byte buffer placed before a PROT_NONE guard region:
chan=0: write at +0 OK (in-allocation)
chan=1: FAULT (SIGSEGV/SEGV) touching 0x8004a5448 =>
offset 21576 from allocation start
(21168 past end of 408-byte alloc)
run.log holds the full untrimmed guest session (build + 2 runs + env).
WHY "inconclusive" (not reproduced) The guest has NO QLogic HBA (pciconf shows none, /dev/isp* absent), so the in-kernel path cannot be triggered end-to-end on this machine. What IS proven, with certainty, on the guest with the real kernel structs: - the allocation geometry (204/21576/408), - the write displacement (+21576 for chan=1), - that the write faults/corrupts far outside the allocation. The driver ships in the GENERIC kernel (kldstat -v: pci/isp) and dual-bus firmware modules (isp_1080.ko, isp_12160.ko) ship in /boot/kernel, so the code is live on any machine with such a card.
UPSTREAM Modern FreeBSD deleted this entire legacy parse path (only isp_parse_status_24xx remains, where FCPARAM use is type-correct because 24xx parts are FC-only). The bug is in the ancient FreeBSD revision (isp.c,v 1.168, 2011) that DragonFly carries.
FIX fix.diff β use the existing ISP_SET_SENDMARKER() macro (ispvar.h:646-652) which selects FCPARAM/SDPARAM by IS_FC(), exactly like every other sendmarker write in the driver.
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 replaces the raw FCPARAM write with the driver's own ISP_SET_SENDMARKER(isp, chan, 1) macro which selects FCPARAM/SDPARAM by IS_FC() β type-correct on both card families and consistent with every other sendmarker write in the file (e.g. isp.c:6120). Baseline could not be reproduced on this guest (no QLogic HBA), so a patched-kernel A/B run was impossible; the fix is a mechanical type-correctness correction with no behavioral change on FC cards.
fix.diff (git-apply-able against sys/dev/disk/isp/isp.c hunk at line 6110)
Confirmed kernel references
Detail
Evidence (decisive lines)
run.log (full guest session: build, 2 runs, env) β decisive lines 'chan=1: FAULT (SIGSEGV/SEGV) ... offset 21576 ... 21168 past end of 408-byte alloc'; trigger_df3582.c (includes real dev/disk/isp/ispvar.h etc.); VERDICT.md; fix.diff (one-line fix via existing ISP_SET_SENDMARKER macro, ispvar.h:646)
PoC changes
No seed existed (pass-2 new finding). Wrote trigger from scratch; it #includes the verbatim kernel headers copied from the guest's /usr/src so struct sizes/offsets are the audited kernel's, and places the 408-byte allocation stand-in against a PROT_NONE reserve so the wild write faults deterministically. First variant used an 8KB mapping and the write silently landed in an adjacent mapping (itself an accurate demo of silent heap corruption); restructured to a 1MB PROT_NONE reserve with a single RW page for a deterministic fault.
Verified recommended fix
Replace FCPARAM(isp, chan)->sendmarker = 1 with ISP_SET_SENDMARKER(isp, chan, 1) in isp_parse_status()'s RQCS_RESET_OCCURRED handler.
Verdict
Type-confusion confirmed at source level with arithmetic proven on the guest against the real kernel headers: isp_parse_status() RQCS_RESET_OCCURRED (sys/dev/disk/isp/isp.c:6110) writes FCPARAM(isp, chan)->sendmarker on cards whose isp_param is a sdparam array (isp_pci.c:827-832); on dual-bus SCSI cards (nchan=2) the chan=1 iteration is a 32-bit read-modify-write at isp_param+21576, i.e. 21,168 bytes past the 408-byte kmalloc. The extracted-logic PoC faults at exactly +21576 (run.log). Guest has no QLogic HBA (pciconf empty, /dev/isp* absent) so the in-kernel path could not be triggered end-to-end; driver is compiled into GENERIC (kldstat -v: pci/isp) and dual-bus firmware modules ship in /boot/kernel, so the path is live on real hardware. Modern FreeBSD removed this legacy parse path entirely; the bug comes from the 2011 FreeBSD revision DragonFly carries.
No comments yet.