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)
PoC verification
Evidence pack
findings/poc/DF-1259 Β· 11 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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 |
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 PoCfix.diffβ standalonegit apply -p1patch closing the cited pathVERDICT.mdβ full source-level mechanism + reachability + fix analysisbuild.log/run.logβ captured build/run outputfix_build.logβ nativekernel compile-validation excerpt (rc=0)env.txtβ guest environmentmanifest.jsonβ machine-readable artifact catalog
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/mfi0to exist (mfi attach β needs LSI MegaRAID HW) and (b) a user frame withheader.cmd == MFI_CMD_STP. The guest has no LSI RAID PCI device, so/dev/mfi0does not exist (open()βENOENT, seerun.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.diffapplies cleanly:git apply --check -p1β OK (1 hunk).- Compiles:
make nativekernelβNK_DONE rc=0,mfi.cbuilt with-Werror. - Functional test: not_testable (no HW β no device node on either kernel).
Recommended fix
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_testablecompile 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.
No comments yet.