NULL request-descriptor dereference in mfi_tbolt_send_frame for PD_SCSI_IO with READ_10/WRITE_10 CDBs - any I/O to a Thunderbolt system PD panics the kernel
Summary
The 'DJA NA XXX SYSPDIO' else-branch only prints, leaving req_desc==NULL, then unconditionally posts through req_desc->words - faulting on address 0. mfi_build_syspdio builds exactly this frame for EVERY read/write bio to a JBOD system PD, and all TBOLT PCI IDs are also SKINNY so syspd probe attaches on every Thunderbolt card - mounting/fsck/swapping/dumping to a JBOD drive panics on first I/O (boot-time automatic); MFIIO_PASSTHRU reaches it with a fully user-controlled frame (mbox[4] aliases pass.cdb[0]). Verified on the guest to the hardware limit (trigger compiles/runs, ENOENT - QEMU megasas emulates only the non-TBOLT path). Build-the-descriptor fix (also makes syspd I/O and PD crash dumps work).
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4390 Β· 11 files| File | Type | Description | Size | |
|---|---|---|---|---|
| df4390_trigger.c | β | 4.2 KB | view raw | |
| build.sh | β | 93 B | view raw | |
| run.sh | β | 99 B | view raw | |
| build.log | β | 66 B | view raw | |
| run.log | β | 529 B | view raw | |
| env.txt | β | 477 B | view raw | |
| README.md | β | 2.3 KB | β raw | |
| VERDICT.md | β | 4.9 KB | β raw | |
| fix.diff | β | 1.4 KB | view raw | |
| manifest.json | β | 1.3 KB | view raw | |
| verdict.json | β | 3.9 KB | view raw |
DF-4390 β NULL request-descriptor dereference in mfi_tbolt_send_frame()
File: sys/dev/raid/mfi/mfi_tbolt.c:1156-1168 (fall-through), deref at :1181-1182
Severity: Medium Β· Confidence: certain (static) Β· runtime test requires TBOLT hardware
Build
cc -O -Wall -o df4390 df4390_trigger.c # verified on the DF guest
Run
./df4390 /dev/mfi0 # or let it try the
# /dev/megaraid_sas_ioctl_node alias
Expected
On a system with a Thunderbolt (LSI 2108/2208 family, MFI_FLAGS_TBOLT,
so MFA_enabled == 1) megaraid controller:
- via this ioctl (root or operator group,
/dev/mfi0is 0640 root:operator):MFIIO_PASSTHRUwithheader.cmd = MFI_CMD_PD_SCSI_IO(0x02) andmbox[4] = 0x28(aliasespass.cdb[0] = READ_10) makesmfi_user_command()(mfi.c:2838 bcopy of the whole user frame) hand the frame tomfi_tbolt_send_frame(), which takes thecdb[0] == 0x28else-branch, printsDJA NA XXX SYSPDIO, leavesreq_desc == NULL, and then executesMFI_WRITE4(sc, MFI_ILQP, (req_desc->words & 0xFFFFFFFF));β immediate kernel panic (read fault at ~address 0). - without any ioctl at all: every read/write bio to a system PD
(JBOD drive exposed to the host) builds exactly this frame
(
mfi_build_syspdio(), mfi.c:1992-2001:pass->header.cmd = MFI_CMD_PD_SCSI_IO; pass->cdb[0] = READ_10/WRITE_10), so mounting or fsck'ing a JBOD disk on a TBOLT card panics instantly. Crash dumps to a system PD hit the same path (mfi_dump_syspd_blocks(), mfi.c:2453-2461).
Observed on this audit's QEMU guest (honest negative)
The guest kernel (X86_64_GENERIC, DragonFly 6.5-DEVELOPMENT) has the mfi
driver compiled in, but the guest has no LSI PCI device at all (virtio
only), so no /dev/mfi0 exists and mfi_tbolt_send_frame() can never run:
$ kldload mfi kldload: can't load mfi: module already loaded or in kernel $ ls /dev/mfi* ls: /dev/mfi*: No such file or directory $ ./df4390 [-] open(/dev/mfi0): No such file or directory ...
The defect itself is proven statically end-to-end (see VERDICT.md).
QEMU's megasas emulation models the 1078/Gen2 chips β which take the
non-TBOLT mfi_send_frame() path β not the Thunderbolt 2108/2208, so no
QEMU configuration can reach this code.
DF-4390 VERDICT β NULL req_desc deref in mfi_tbolt_send_frame()
Bottom line
Static verdict: CONFIRMED kernel NULL-pointer dereference (panic/DoS) on all Thunderbolt megaraid controllers, reachable (a) by ordinary I/O to a system PD, (b) by crash dumps to a system PD, and (c) by a single management ioctl from root/operator.
Runtime verdict: not_reproduced on this QEMU guest β the guest has zero
LSI PCI devices; the mfi driver is in-kernel but never attaches, so
/dev/mfi0 does not exist and mfi_tbolt_send_frame() cannot execute.
This is a hardware-absence limitation, not evidence against the finding.
The bug, traced line by line
mfi_tbolt_send_frame() (sys/dev/raid/mfi/mfi_tbolt.c):
1143: hdr = &cm->cm_frame->header;
1144: cdb = cm->cm_frame->pass.cdb;
...
1156: if (hdr->cmd == MFI_CMD_PD_SCSI_IO) {
1157: /* check for inquiry commands coming from CLI */
1158: if (cdb[0] != 0x28 && cdb[0] != 0x2A) {
1159: if ((req_desc = mfi_tbolt_build_mpt_cmd(sc, cm)) ==
1160: NULL) {
... ... return 1; /* handled */
1164: }
1165: }
1166: else
1167: device_printf(sc->mfi_dev, "DJA NA XXX SYSPDIO\n");
1168: } /* req_desc still NULL here */
...
1181: MFI_WRITE4(sc, MFI_ILQP, (req_desc->words & 0xFFFFFFFF));
1182: MFI_WRITE4(sc, MFI_IHQP, (req_desc->words >>0x20));
req_desc is initialized NULL (line 1140) and the PD_SCSI_IO +
READ_10/WRITE_10 case assigns nothing; req_desc->words then reads from
address 0 β page fault β panic. No other guard exists between 1168 and
1181 (sc->adpreset was checked at 1145 and is 0 in normal operation).
Reachability (three independent triggers)
- System PD bios (no privileges beyond disk access):
mfi_build_syspdio()(sys/dev/raid/mfi/mfi.c:1992-2001) setspass->header.cmd = MFI_CMD_PD_SCSI_IOandpass->cdb[0] = READ_10 (0x28)/WRITE_10 (0x2A)for every bio on a JBOD system PD.mfi_mapcmd()βmfi_data_cb()(mfi.c:2266-2267) βmfi_tbolt_send_frame()whenMFA_enabled(set for all TBOLT cards after IOC init, mfi_tbolt.c:430-432). System PDs are probed on every TBOLT card because all TBOLT PCI IDs also carryMFI_FLAGS_SKINNY(mfi_pci.c:119-149) andmfi_startup()runsmfi_syspdprobe()for SKINNY (mfi.c:1205-1206). β mounting, fsck'ing, swapping, or dumping to a JBOD drive on a TBOLT controller panics on first I/O. - Crash dump:
mfi_dump_syspd_blocks()(mfi.c:2453-2461) builds the identical frame (PD_SCSI_IO + WRITE_10) withMFI_CMD_POLLED. - Management ioctl:
mfi_user_command()(mfi.c:2838)bcopys the entire user-suppliedstruct mfi_dcmd_frameβ header included β into the command frame.dcmd.mbox[4](frame offset 32) aliasespass.cdb[0](offset 32; MFI_MBOX_SIZE = 12, mfireg.h:462,556-570).MFIIO_PASSTHRUwithcmd=0x02, mbox[4]=0x28andbuf_size=0(cm_data == NULL β directmfi_tbolt_send_frame(), mfi.c:2167-2169) panics./dev/mfi0isroot:operator 0640(mfi.c:721-724).
Exploitability
This is a NULL-deref at a fixed offset (read of req_desc->words at
address 0): on this code base it is a denial of service (panic), not a
control-flow hijack. There is no attacker-controlled index or write
through the NULL pointer (the read value is only written to the card's
doorbell). Impact ceiling: reliable local DoS with the triggers above β
including automatic boot-time panics on JBOD configurations.
Why not reproduced at runtime (and why that is conclusive neither way)
- Guest: DragonFly 6.5-DEVELOPMENT
X86_64_GENERIC(Jul 2 2026 build), mfi compiled in (kldload mfiβ "already loaded or in kernel"). pciconf -lshows only virtio devices β no vendor-0x1000 device, so nomfi_attach, no/dev/mfi0(see env.txt).- QEMU's
megasasdevice model emulates 1078/Gen2 controllers (non-TBOLT pathmfi_send_frame()); it cannot emulate the 2108/2208 Thunderbolt interface, so no guest configuration can reachmfi_tbolt_send_frame().
Attempts: 1 (decisive β the trigger's precondition, the device node, does not exist; see run.log).
Fix
fix.diff in this directory routes the PD_SCSI_IO + READ_10/WRITE_10 case
through mfi_tbolt_build_mpt_cmd() like every other PD passthru (this also
makes system-PD I/O and PD dumps work, which the "DJA NA XXX" placeholder
never did), and removes the now-unused cdb local. Fix validation
(fix_status: not_testable): the patched kernel cannot be exercised
without TBOLT hardware for the same reason as the baseline.
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 authored and git-apply-verified against sys/dev/raid/mfi/mfi_tbolt.c, but baseline and patched kernels cannot be exercised on this guest (no TBOLT HBA), so behavioral fix validation was not possible.
fix.diff in this pack; scratch-clone git-apply check passed; rationale in VERDICT.md
Confirmed kernel references
Detail
Evidence (decisive lines)
VERDICT.md (full line-by-line trace); df4390_trigger.c (trigger, with kernel-ABI mirror structs); run.log + env.txt (guest: mfi in-kernel, no LSI PCI device, ENOENT); fix.diff (git-apply verified on a scratch clone)
PoC changes
Seed sketch (none existed - finding is new in pass 2): authored trigger from scratch with layout-accurate packed structs mirroring mfireg.h/mfi_ioctl.h (mfi_dcmd_frame=56B, mfi_ioc_passthru=68B, mbox[4] aliases pass.cdb[0] at frame offset 32); verified it compiles clean with the guest cc and runs.
Verified recommended fix
Always build the MPT descriptor for MFI_CMD_PD_SCSI_IO (drop the cdb[0]!=0x28/0x2A exclusion in mfi_tbolt_send_frame) so req_desc can never be NULL at the doorbell write.
Verdict
Statically confirmed NULL-dereference in mfi_tbolt_send_frame(): for MFI_CMD_PD_SCSI_IO frames whose CDB[0] is READ_10(0x28)/WRITE_10(0x2A) the function prints 'DJA NA XXX SYSPDIO' (mfi_tbolt.c:1166-1167), leaves req_desc == NULL (init at 1140), and dereferences req_desc->words when posting (1181-1182) - guaranteed kernel panic on every Thunderbolt controller. Three live triggers: (1) every read/write bio to a system PD (mfi_build_syspdio, mfi.c:1992-2001, builds exactly this frame; TBOLT cards are all SKINNY so mfi_syspdprobe attaches JBOD devices, mfi_pci.c:119-149 + mfi.c:1205-1206), (2) crash dumps to a system PD (mfi_dump_syspd_blocks, mfi.c:2453-2461), (3) a single MFIIO_PASSTHRU ioctl with cmd=0x02 and mbox[4]=0x28 (aliases pass.cdb[0]; user frame copied verbatim at mfi.c:2838; /dev/mfi0 is root:operator 0640). Runtime reproduction was impossible on this audit guest: DragonFly 6.5-DEVELOPMENT X86_64_GENERIC has mfi compiled in but the QEMU guest has zero vendor-0x1000 PCI devices, so no /dev/mfi0 exists and mfi_tbolt_send_frame() never executes; QEMU's megasas model (1078/Gen2) cannot emulate the Thunderbolt 2108/2208 path. The trigger compiled and ran on the guest, exiting ENOENT as the hardware-absent proof (run.log, env.txt). Impact field reflects runtime observation only; on real hardware the impact is a reliable local kernel panic (DoS), including automatic boot-time panics on JBOD configurations. Not an exploitation primitive beyond DoS: the only NULL-sourced value is written to the card doorbell, no attacker-controlled index or write.
No comments yet.