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

mfi_stp_cmd unbounded SGE loop corrupts mfi_softc via size-[2] DMA arrays and STP frame SGL

Summary

mfi_stp_cmd at mfi.c:2747: for(i=0;i<ioc->mfi_sge_count;i++) where i is uint8_t (wraps if count>255). Each iter writes sc->mfi_kbuff_arr_dmat[i]/dmamap[i]/busaddr[i] (sized [2] per mfivar.h:196-198), kern_sge[i], cm->cm_frame->stp.sgl.sg64[i]/sg32[i] (sized [2] per mfireg.h:595-598). sge_count>=3 -> OOB write into mfi_softc and frame. Native MFI_CMD has NO MAX_IOCTL_SGE cap (unlike Linux shim at :3207). kern_sge from unchecked mfi_sgl_off at :2734. Operator group. Fix: cap loop to i<2, change i to int, validate mfi_sgl_off.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-1259 Β· 11 files
FileTypeDescriptionSize
mfi_stp_oob.c trigger-source trigger / documentation PoC 2.4 KB view raw
fix.diff suggested-fix git-apply-able patch closing the cited path 1.1 KB view raw
VERDICT.md verdict full source trace + reachability + fix analysis 3.2 KB ↓ raw
README.md readme build/run/expected 1.4 KB ↓ raw
build.sh build-script cc -O2 -Wall -o mfi_stp_oob mfi_stp_oob.c 105 B view raw
run.sh run-script ./mfi_stp_oob 45 B view raw
run.log run-log decisive run, full output 366 B view raw
fix_build.log build-log nativekernel compile-validation excerpt, rc=0 32.5 KB view raw
env.txt environment uname, cc version, device/module state 583 B 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 build/run/expected
↓ download raw

DF-1259 PoC β€” mfi_stp_cmd unbounded SGE loop corrupts mfi_softc

Status

INCONCLUSIVE on the audit guest: bug confirmed real by source trace at sys/dev/raid/mfi/mfi.c:2747, but hardware-gated and not triggerable here. fix.diff authored and validated to apply + compile (nativekernel rc=0, -Werror). See VERDICT.md for the full trace and fix.diff for the patch.

Build

./build.sh

(equivalent: cc -O2 -Wall -o mfi_stp_oob mfi_stp_oob.c)

Run (as unprivileged user)

./run.sh

Expected on the audit guest

The PoC runs and reports that the kernel trigger surface is absent on this QEMU/KVM guest (no LSI MegaRAID controller / no AMD Tonga GPU / no DP MST hardware; mfi is in-kernel but /dev/mfi0 is never created; drm/amdgpu are loadable modules that are not loaded and would not attach). It exits 0 without exercising the sink. The bug is therefore a latent, hardware-dependent defect, not a false positive.

Files

  • mfi_stp_oob.c β€” trigger / documentation PoC
  • fix.diff β€” standalone git apply -p1 patch closing the cited path
  • VERDICT.md β€” full source-level mechanism + reachability + fix analysis
  • build.log / run.log β€” captured build/run output
  • fix_build.log β€” nativekernel compile-validation excerpt (rc=0)
  • env.txt β€” guest environment
  • manifest.json β€” machine-readable artifact catalog
VERDICT.md verdict full source trace + reachability + fix analysis
↓ download raw

DF-1259 β€” mfi_stp_cmd unbounded SGE loop corrupts mfi_softc

Verdict

INCONCLUSIVE (hardware-gated latent bug, not triggerable on this guest). The OOB write is confirmed real by source tracing; not reachable on the audit QEMU guest because /dev/mfi%d only exists once mfi attaches to an LSI MegaRAID SAS controller (absent). fix.diff authored and validated to apply + compile (nativekernel rc=0, -Werror).

Mechanism (source trace)

mfi_stp_cmd (sys/dev/raid/mfi/mfi.c:2725):

uint8_t i;                                   /* loop var β€” also wraps at 256 */
...
cm->cm_frame->header.sg_count = ioc->mfi_sge_count;
cm->cm_total_frame_size += (sge_size * ioc->mfi_sge_count);
for (i = 0; i < ioc->mfi_sge_count; i++) {
    bus_dma_tag_create(..., &sc->mfi_kbuff_arr_dmat[i]);      /* [2] */
    bus_dmamem_alloc(sc->mfi_kbuff_arr_dmat[i], ...&sc->kbuff_arr[i]);
    bus_dmamap_load(..., &sc->mfi_kbuff_arr_busaddr[i]);      /* [2] */
    kern_sge[i].phys_addr = ...;                              /* sized [2]  */
    cm->cm_frame->stp.sgl.sg64[i].addr = ...;                 /* sized [2]  */
    copyin(ioc->mfi_sgl[i].iov_base, sc->kbuff_arr[i], ioc->mfi_sgl[i].iov_len);
}

Array sizes: - mfi_kbuff_arr_dmat[2], mfi_kbuff_arr_dmamap[2], mfi_kbuff_arr_busaddr[2] (mfivar.h:196-198). - struct mfi_stp_frame { ...; struct mfi_sg32 sg32[2]; struct mfi_sg64 sg64[2]; } (mfireg.h:591-598).

The native MFI_CMD path calls mfi_stp_cmd (mfi.c:3007-3008) with no cap on ioc->mfi_sge_count (unlike the Linux shim's MAX_LINUX_IOCTL_SGE). With mfi_sge_count >= 3, index i=2 writes past every [2] array into the adjacent mfi_softc fields (mfi_comms, frame structs) and past the STP SGL into the next frame fields. (The uint8_t i additionally wraps at 256, which is secondary but real.) This is a kernel-heap / softc OOB write driven by a user-controlled count.

Reachability on the audit guest

  • mfi is in X86_64_GENERIC, but the STP path requires (a) /dev/mfi0 to exist (mfi attach β‡’ needs LSI MegaRAID HW) and (b) a user frame with header.cmd == MFI_CMD_STP. The guest has no LSI RAID PCI device, so /dev/mfi0 does not exist (open() β‡’ ENOENT, see run.log). The sink is unreachable here; latent on MegaRAID-equipped hosts (root/operator only).

Exploit chain

None developed β€” not exercisable on this guest (no /dev/mfi, no LSI HW). The primitive is a controlled OOB write into mfi_softc/frame fields; reachable only with hardware + root/operator creds. No unprivileged, hardware-free path exists.

PoC changes

Authored mfi_stp_oob.c β€” reports /dev/mfi0 absence for an unambiguous negative result.

Fix validation

  • fix.diff applies cleanly: git apply --check -p1 β‡’ OK (1 hunk).
  • Compiles: make nativekernel β‡’ NK_DONE rc=0, mfi.c built with -Werror.
  • Functional test: not_testable (no HW β†’ no device node on either kernel).

Cap ioc->mfi_sge_count to the bounce-buffer array bound (sizeof(sc->mfi_kbuff_arr_dmat)/sizeof(...[0]) = 2) and return EINVAL if exceeded; widen the loop index from uint8_t to int (the wrap is moot once capped, but removes the footgun). See fix.diff.

Fix verification

not_testable

compile validated -Werror

module/kernel build rc=0

Confirmed kernel references

β€”

Detail

Exploit chain

none

Evidence (decisive lines)

β€”

Verdict

Source-confirmed. mfi_stp_cmd uint8_t i loop no cap vs mfi_kbuff_arr[2] -> OOB. mfi in GENERIC, no LSI RAID.