β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-1228

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-1228 Β· 12 files
FileTypeDescriptionSize
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
README.md readme claim, verdict, runnable-PoC instructions
↓ download 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
VERDICT.md verdict full path:line trace, threat model, fix rationale
↓ download raw

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

  1. sc->commands is allocated as sizeof(struct mpr_command) * sc->num_reqs at sys/dev/raid/mpr/mpr.c:1522, where num_reqs is an int derived from the controller's IOC facts (mpr.c:381 sc->num_reqs = prireqcr + reqcr;). It is typically a few thousand.

  2. The reply descriptor's SMID is a U16 (0..65535) β€” see sys/dev/raid/mpr/mpi/mpi2.h:416,442,456,.... The HBA writes the reply descriptor into sc->post_queue[] (mpr.c:2315); the host CPU reads it back. SMID values are therefore attacker-influenced in the malicious/buggy-HBA threat model.

  3. mpr_intr_locked at sys/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 at sys/dev/raid/mpr/mpr.c:2407-2408: c } else { cm = &sc->commands[le16toh(desc->AddressReply.SMID)]; No bounds check precedes either indexing. A SMID > sc->num_reqs silently produces an out-of-bounds pointer into adjacent kernel heap.

  4. 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 (INVARIANTS is for debug builds) AND only fires AFTER the OOB index has occurred. The OOB pointer is then dereferenced to write cm->cm_state and cm->cm_reply (lines 2339-2340 / 2411-2412) β€” that is itself an OOB write β€” before the KASSERT would trip.

  5. The OOB cm is then passed to mpr_complete_command(sc, cm) at line 2434, which calls cm->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.

  6. Other sites in the same driver DO bounds-check SMID before indexing β€” e.g. mpr_user_pass_thru at sys/dev/raid/mpr/mpr.c:1898: c if (smid == 0 || smid > sc->num_reqs) ... so the fix is consistent with established practice elsewhere in mpr.

Why it is NOT REPRODUCED on this guest

  • pciconf -lv shows the standard QEMU i440BX/PIIX3/virtio device set only. There is no LSI/Broadcom/Avago MPT-Fusion SAS controller.
  • kldstat -v confirms pci/mpr is 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 in mpr_attach when 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.diff applies cleanly with patch -p1 --forward (verified).
  • All 5 audit fixes applied together; make -j6 nativekernel KERNCONF=X86_64_GENERIC returned rc=0 with no errors / warnings under -Werror. mpr.c was compiled cleanly into both the kernel proper and the mpr.ko module.
  • Fix is not_testable at runtime on this guest (no mpr controller), consistent with the DF-1227 precedent.

Fix verification

not_testable

compile 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.