Untrusted SMID used directly as sc->commands[] index in mpr_intr_locked (OOB array access)
Summary
mpr_intr_locked() at mpr.c:2336: cm=&sc->commands[le16toh(desc->SCSIIOSuccess.SMID)] and at :2407-2408 cm=&sc->commands[le16toh(desc->AddressReply.SMID)]. SMID is u16 (0-65535), sc->commands[] sized num_reqs (~2k). KASSERT only checks INVARIANTS (no-op in production), and AFTER OOB read already happened. OOB cm pointer then: cm_state written, cm_reply written, mpr_complete_command calls cm->cm_complete function pointer from garbage heap -> arbitrary code execution with grooming. Malicious/buggy HBA (PCIe passthrough). Fix: validate SMID>0 && SMID<num_reqs before indexing.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-1228 Β· 12 files| File | Type | Description | Size | |
|---|---|---|---|---|
| VERDICT.md | verdict | full path:line trace, threat model, fix rationale | 5.3 KB | β raw |
| README.md | readme | claim, verdict, runnable-PoC instructions | 2.3 KB | β raw |
| mpr_smid_oob.c | trigger-source | PoC: reachability check for /dev/mprN | 3.0 KB | view raw |
| build.sh | build-script | cc -O -Wall -o mpr_smid_oob mpr_smid_oob.c | 196 B | view raw |
| run.sh | run-script | ./mpr_smid_oob | 117 B | view raw |
| build.log | build-log | PoC build, full output | 115 B | view raw |
| run.log | run-log | PoC run on this guest (open /dev/mprN ENOENT) | 477 B | view raw |
| fix.diff | suggested-fix | bounds-check SMID in both SCSIIOSuccess and AddressReply branches of mpr_intr_locked | 1.1 KB | view raw |
| fix_build.log | build-log | kernel build rc=0 with all 5 fixes applied; mpr.c compiled clean under -Werror | 2.1 KB | view raw |
| env.txt | environment | guest uname, cc version, PCI topology | 2.0 KB | view raw |
| ../fix_build_combined.log | build-log | Combined 41-finding kernel build (rc=0, -Werror clean) | 5.6 MB | β download |
| ../fix_build_summary.txt | build-summary | Summary of the combined 41-finding kernel build | 826 B | view raw |
DF-1228 β README
Finding
mpr_intr_locked() in sys/dev/raid/mpr/mpr.c uses the HBA-supplied
SMID directly as an index into sc->commands[] at lines 2336 and
2407-2408, with no bounds check. SMID is a U16 (0..65535);
sc->commands[] is sized sc->num_reqs (typically a few thousand).
A malicious/buggy HBA returning SMID >= num_reqs causes an out-of-bounds
pointer; subsequent cm->cm_state / cm->cm_reply writes are OOB writes,
and mpr_complete_command() eventually calls cm->cm_complete(sc, cm) β
a hijackable control-flow transfer from heap residue on INVARIANTS-OFF
kernels.
Verdict
NOT REPRODUCED on this guest (latent): the audit guest has no LSI
MPT-Fusion 3 controller in pciconf -lv, so the interrupt handler never
runs. The driver is statically linked into X86_64_GENERIC
(kldstat -v shows pci/mpr), so the bug path is in the running kernel
and is exercised on real DragonFly installs that have the matching HBA.
Confidence (bug is real): certain β traced line-by-line in sys/.
Impact ceiling: kernel heap OOB read/write + control-flow hijack by a
malicious/buggy PCIe HBA. Not an unprivileged-user-to-root vector.
How to reproduce
./build.sh && ./run.sh
Expected on this guest: PoC builds clean, prints "No /dev/mprN found: No
such file or directory" and the reachability analysis. On a host with an
mpr HBA plus a malicious/buggy firmware, an interrupt with SMID >=
num_reqs triggers the OOB index.
Files
| Path | Purpose |
|---|---|
mpr_smid_oob.c |
PoC: reachability check + explains the trigger |
build.sh / run.sh |
exact build/run commands |
fix.diff |
SMID bounds check in both branches of mpr_intr_locked |
VERDICT.md |
full path:line trace, threat model, fix rationale |
build.log |
full cc output of the PoC build |
run.log |
full output of the PoC run on this guest |
fix_build.log |
kernel-build compile validation of fix.diff |
env.txt |
guest uname / cc / device topology |
DF-1228 β VERDICT
Finding: mpr_intr_locked() uses the untrusted HBA-supplied SMID
directly as an array index into sc->commands[] at
sys/dev/raid/mpr/mpr.c:2336 and :2407-2408, with no bounds check.
Status: NOT REPRODUCED (latent β no LSI MPT-Fusion 3 controller on this guest).
Confidence (bug is real): certain (traced line-by-line in sys/).
Impact ceiling: kernel heap OOB-read / control-flow hijack via
cm->cm_complete function pointer, when triggered by a malicious/buggy PCIe
HBA (PCIe passthrough, firmware compromise, DMA attack, or buggy controller).
Mechanism (confirmed line-by-line in sys/)
-
sc->commandsis allocated assizeof(struct mpr_command) * sc->num_reqsatsys/dev/raid/mpr/mpr.c:1522, wherenum_reqsis anintderived from the controller's IOC facts (mpr.c:381sc->num_reqs = prireqcr + reqcr;). It is typically a few thousand. -
The reply descriptor's
SMIDis aU16(0..65535) β seesys/dev/raid/mpr/mpi/mpi2.h:416,442,456,.... The HBA writes the reply descriptor intosc->post_queue[](mpr.c:2315); the host CPU reads it back. SMID values are therefore attacker-influenced in the malicious/buggy-HBA threat model. -
mpr_intr_lockedatsys/dev/raid/mpr/mpr.c:2336:c case MPI2_RPY_DESCRIPT_FLAGS_SCSI_IO_SUCCESS: case MPI25_RPY_DESCRIPT_FLAGS_FAST_PATH_SCSI_IO_SUCCESS: case MPI26_RPY_DESCRIPT_FLAGS_PCIE_ENCAPSULATED_SUCCESS: cm = &sc->commands[le16toh(desc->SCSIIOSuccess.SMID)];and atsys/dev/raid/mpr/mpr.c:2407-2408:c } else { cm = &sc->commands[le16toh(desc->AddressReply.SMID)];No bounds check precedes either indexing. ASMID > sc->num_reqssilently produces an out-of-bounds pointer into adjacent kernel heap. -
The only guard is
KASSERT(cm->cm_state == MPR_CM_STATE_INQUEUE, ...)at lines 2337 and 2409. This is a no-op on production kernels (INVARIANTSis for debug builds) AND only fires AFTER the OOB index has occurred. The OOB pointer is then dereferenced to writecm->cm_stateandcm->cm_reply(lines 2339-2340 / 2411-2412) β that is itself an OOB write β before the KASSERT would trip. -
The OOB
cmis then passed tompr_complete_command(sc, cm)at line 2434, which callscm->cm_complete(sc, cm)(the function pointer inside the OOB struct). On INVARIANTS-OFF kernels this is a hijackable control-flow transfer from attacker-influenced heap residue. -
Other sites in the same driver DO bounds-check SMID before indexing β e.g.
mpr_user_pass_thruatsys/dev/raid/mpr/mpr.c:1898:c if (smid == 0 || smid > sc->num_reqs) ...so the fix is consistent with established practice elsewhere inmpr.
Why it is NOT REPRODUCED on this guest
pciconf -lvshows the standard QEMU i440BX/PIIX3/virtio device set only. There is no LSI/Broadcom/Avago MPT-Fusion SAS controller.kldstat -vconfirmspci/mpris statically linked into X86_64_GENERIC (the driver code IS in the running kernel) β the bug path exists, but the interrupt handler never fires without matching hardware.ls /dev/mpr*returns no device nodes (the cdev is created only inmpr_attachwhen matching HW probes successfully).
PoC mpr_smid_oob.c confirms the no-/dev/mprN state at runtime.
Threat model & privilege boundary
This is a driver-vs-peripheral bug. The SMID is written by the HBA into host memory via DMA; the host CPU trusts it. Realistic trigger scenarios:
- Compromised HBA firmware β many RAID HBAs run their own microcode, updatable from host tools or via flash. A backdoored/buggy firmware can emit any SMID in its reply descriptors.
- PCIe passthrough of a hostile device β VMs that pass through a physical HBA to a guest have the guest trust the HBA blindly; this is a known PCIe-trust problem.
- Buggy controller β even without malice, a firmware bug returning a stale or wrapped SMID causes a kernel OOB write/panic (DoS) on a real production host.
An unprivileged user cannot trigger this from userspace (the cdev is
0640 root:operator, and the SMID is on the HBA-reply path, not a direct
ioctl input). So this is not an unprivileged-to-root escalation vector
β it is a kernel-integrity / availability issue against a malicious or
buggy HBA.
Fix (authored in fix.diff, applied + compile-validated)
Reject SMID 0 (reserved by MPI2 spec for unsolicited events) and
SMID >= sc->num_reqs (out of array) before indexing sc->commands[],
in both the SCSIIOSuccess (line 2336) and AddressReply (line 2407)
branches of mpr_intr_locked. The fix mirrors the bounds check already
used in mpr_user_pass_thru at mpr.c:1898. The KASSERT is left in
place as a debug aid.
This brings the interrupt path up to the same level of input validation already applied on the user-ioctl path.
Validation
fix.diffapplies cleanly withpatch -p1 --forward(verified).- All 5 audit fixes applied together;
make -j6 nativekernel KERNCONF=X86_64_GENERICreturned rc=0 with no errors / warnings under-Werror.mpr.cwas compiled cleanly into both the kernel proper and thempr.komodule. - Fix is not_testable at runtime on this guest (no mpr controller), consistent with the DF-1227 precedent.
Fix verification
not_testablecompile validated
nativekernel rc=0
Confirmed kernel references
β
Detail
Exploit chain
none
Evidence (decisive lines)
β
Verdict
Source-confirmed. mpr_intr SMID no bounds -> sc->commands OOB. mpr in GENERIC, no SAS HW.
No comments yet.