# 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`):

```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)

1. **System PD bios (no privileges beyond disk access):**
   `mfi_build_syspdio()` (`sys/dev/raid/mfi/mfi.c:1992-2001`) sets
   `pass->header.cmd = MFI_CMD_PD_SCSI_IO` and `pass->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()`
   when `MFA_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 carry `MFI_FLAGS_SKINNY` (mfi_pci.c:119-149) and
   `mfi_startup()` runs `mfi_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.
2. **Crash dump:** `mfi_dump_syspd_blocks()` (mfi.c:2453-2461) builds the
   identical frame (PD_SCSI_IO + WRITE_10) with `MFI_CMD_POLLED`.
3. **Management ioctl:** `mfi_user_command()` (mfi.c:2838) `bcopy`s the
   entire user-supplied `struct mfi_dcmd_frame` — header included — into
   the command frame. `dcmd.mbox[4]` (frame offset 32) aliases
   `pass.cdb[0]` (offset 32; MFI_MBOX_SIZE = 12, mfireg.h:462,556-570).
   `MFIIO_PASSTHRU` with `cmd=0x02, mbox[4]=0x28` and `buf_size=0`
   (cm_data == NULL → direct `mfi_tbolt_send_frame()`, mfi.c:2167-2169)
   panics. `/dev/mfi0` is `root: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 -l` shows only virtio devices — no vendor-0x1000 device, so no
  `mfi_attach`, no `/dev/mfi0` (see env.txt).
* QEMU's `megasas` device model emulates 1078/Gen2 controllers (non-TBOLT
  path `mfi_send_frame()`); it cannot emulate the 2108/2208 Thunderbolt
  interface, so no guest configuration can reach `mfi_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.
