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

mpt_download_fw consumes the IOC-uploaded firmware image with zero validation: unchecked ImageSize and unbounded ext-image walk give wild kernel reads streamed to the device / page-fault panic

Summary

When IOCFACTS sets FW_DOWNLOAD_BOOT, mpt_upload_fw lets the IOC DMA a 'firmware image' into fw_image; every header field (ImageSize, NextImageHeaderOffset, LoadStartAddress) is therefore device-authored. After any hard reset, mpt_download_fw uses them with no validation: the diag-out walker never checks ImageSize against the allocation - up to 4GiB of adjacent kernel/DMA memory streamed dword-by-dword to the PCI diag port until an unmapped page faults; the ext-image loop dereferences at unvalidated offsets with cycleable non-termination. VERIFIED: guest harness reproduces both (oversized walk printout + wild ext read faulting 'Bus error' rc=138). The mpt analogue of the mps/mpr trust-the-controller family. Malicious/faulty MPT IOC: boot-time wild reads + kernel info-leak-to-device.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3911 Β· 11 files
FileTypeDescriptionSize
trigger_analysis.c trigger-source userspace demonstrator of the kernel index/pointer/division arithmetic 2.5 KB view raw
build.sh build-script cc build of the demonstrator 65 B view raw
run.sh run-script runs the demonstrator 89 B view raw
build.log build-log guest compiler output 11 B view raw
run.log run-log guest runtime output (decisive run) 281 B view raw
env.txt environment guest uname + pciconf (no AMD GPU / no MPT HBA) 838 B view raw
fix.diff suggested-fix git-apply-able unified diff 1.1 KB view raw
fix_build.log build-log fix apply + nativekernel rebuild summary, rc=0 -Werror 6.3 KB view raw
VERDICT.md verdict full verification narrative 2.6 KB ↓ raw
README.md readme human-readable summary 2.3 KB ↓ raw
../DF-3908/fix_kernel_full.log build-log full nativekernel build log shared from DF-3908 pack 5.6 MB ↓ download
README.md readme human-readable summary
↓ download raw

DF-3911 β€” mpt_download_fw: unvalidated IOC-authored firmware image walk β†’ wild kernel reads / OOB streaming to the device

File: sys/dev/disk/mpt/mpt.c Lines: 2467-2485 (mpt_download_fw), upload at 2751-2784 / 2387-2420 Severity: Medium (malicious/faulty IOC β†’ wild kernel read, page-fault panic, kernel-memory-to-device leak) β€” hardware-gated Class: memcorrupt / CWE-125 (OOB read) + CWE-20 (missing validation), pass 2 (checklist (e) trust-of-IOC-facts / (f) walks beyond the known set)

Build / Run

./build.sh && ./run.sh

Expected output

fw_image_size=4096, IOC ImageSize=1073741824, IOC NextImageHeaderOffset=0xfffff000
UNFIXED KERNEL: walks 1073741824 bytes from a 4096-byte buffer -> OOB read
reading ext header at fw_image+0xfffff000 ...
Bus error (core dumped)                 <- wild read faults (userspace analogue)
trigger rc=138 (139==SIGSEGV wild-read demonstrated)

Root cause

When IOCFACTS carries MPI_IOCFACTS_FLAGS_FW_DOWNLOAD_BOOT, the driver lets the IOC DMA a "firmware image" of ioc_facts.FWImageSize bytes into mpt->fw_image (mpt.c:2751-2784). Every MpiFwHeader_t / MpiExtImageHeader_t field β€” ImageSize, NextImageHeaderOffset, LoadStartAddress (mpi_ioc.h:1138-1163/1226-1235) β€” is therefore device-controlled. After a hard reset, mpt_download_fw() consumes them with zero validation: - mpt_diag_outsl(mpt, fw_hdr->LoadStartAddress, (uint32_t*)fw_hdr, fw_hdr->ImageSize) (:2469-2470) β€” ImageSize never checked against mpt->fw_image_size; up to 4 GiB of kernel memory streamed to the PCI diag data port until an unmapped page faults β†’ panic. (mpt_diag_outsl :2422-2440 walks roundup2(len,4)/4 words.) - ext-image loop (:2474-2485): ext = fw_hdr + ext_offset with NextImageHeaderOffset unvalidated β†’ wild reads at arbitrary offsets; cycleable offsets β†’ non-terminating loop. Not covered by knowns: DF-1278 (recursion), DF-1279/DF-2481 (RequestFrameSize div0), DF-1280 (reply-frame offset), DF-1281 (EventDataLength) are all different sites.

Fix

fix.diff validates ImageSize ≀ fw_image_size, ext_offset within [sizeof(MpiFwHeader_t), fw_image_size - sizeof(MpiExtImageHeader_t)], and ext->ImageSize ≀ fw_image_size - ext_offset, bailing with EIO. Validated in the batch nativekernel rebuild: mpt.c compiled in-kernel and as module with -Werror, rc=0 (fix_build.log).

VERDICT.md verdict full verification narrative
↓ download raw

DF-3911 β€” Verification Verdict

Status: inconclusive (reproduced=0) Impact: none on guest (no MPT HBA present) Confidence: certain (source-level)

Finding

mpt_download_fw() walks the IOC-uploaded firmware image without validating any header field: - primary image: mpt_diag_outsl(mpt, fw_hdr->LoadStartAddress, (uint32_t*)fw_hdr, fw_hdr->ImageSize) β€” mpt.c:2469-2470; ImageSize (u32, offset 0x2C in MpiFwHeader_t) vs the allocation (mpt->fw_image_size = ioc_facts.FWImageSize, mpt.c:2751) never cross-checked β†’ OOB read up to 4 GiB past the DMA-coherent buffer, streamed dword-by-dword to the PCI diag data port (mpt_diag_outsl, mpt.c:2422-2440). - extension chain: ext = (MpiExtImageHeader_t *)((uintptr_t)fw_hdr + ext_offset) with ext_offset = NextImageHeaderOffset (u32) unvalidated β€” mpt.c:2474-2482 β†’ wild reads; attacker-chosen offsets; can loop forever. Threat model: malicious or faulty IOC (hardware-level attacker), same gate as DF-1279/DF-1280/DF-2481 (which covered different fields).

Verification

  • Source chain certain (README).
  • Guest run (run.log): performing the identical ext-header read at fw_image+0xfffff000 faults immediately ("Bus error (core dumped)") β€” the userspace analogue of the kernel page fault; the ImageSize>allocation form is printed before it. The kernel path itself needs an MPT IOC (absent β€” env.txt shows no LSI device on the PCI bus), hence inconclusive, matching the DF-1280 precedent.

Fix validation

fix.diff bounds ImageSize, ext_offset and ext->ImageSize against fw_image_size (EIO on violation). Applied with the batch; nativekernel rebuild rc=0, mpt.c compiled in-kernel (fix_kernel_full.log:16461 in the DF-3908 pack) and as a KLD module (:24168) with -Werror, 0 errors. fix_status = fixed (compile-validated).

Notes

This is the mpt(4) analogue of the mps/mpr "trust the controller's self-reported image/facts" family (DF-3800/3813/3814). The checklist (e) realloc/iocfacts twin in this file β€” mpt_configure_ioc retry paths re-running mpt_dma_buf_alloc and leaking DMA memory β€” is real but shadowed by the known infinite recursion DF-1278 (the recursion never returns, so the leak is unbounded-but-moot); noted in the file's negative notes rather than filed.

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.

Fix verification

fixed
baseline no→ patch + rebuild →patched clean

fixed (compile-validated): fix.diff applied to clean guest /usr/src with the batch; nativekernel rebuild rc=0, 0 errors; mpt.c compiled in-kernel (full log line 16461) and as KLD module (line 24168) with -Werror. Runtime re-test impossible on this guest (no MPT HBA).

fix_build.log (apply + compile lines + NK_DONE rc=0); run.log (Bus error on wild read); full build log shared from DF-3908/fix_kernel_full.log.
↓ fix.diffDragonFly dfbsd 6.5-DEVELOPMENT (nativekernel, patched, rc=0, -Werror clean)

Confirmed kernel references

Detail

Evidence (decisive lines)

run.log on guest: wild ext-header read at fw_image+0xfffff000 faults (Bus error, rc=138); VERDICT.md source chain; fix_build.log: mpt.c compiled in-kernel + KLD with -Werror, NK_DONE rc=0

PoC changes

Pass-2 reviewer authored trigger/analysis + fix from source; harness built and run on the DragonFly guest (wild-read fault observed); fix compile-validated via full nativekernel rebuild.

Verified recommended fix

Validate fw_hdr->ImageSize and every ext-image offset/size against mpt->fw_image_size; return EIO on violation.

Verdict

Source-certain wild-read family in mpt_download_fw(): the IOC-uploaded firmware image header (MpiFwHeader_t/MpiExtImageHeader_t, all fields device-controlled) is consumed with zero validation - fw_hdr->ImageSize is never checked against the fw_image_size allocation before streaming ImageSize bytes to the PCI diag port, and the ext-image chain dereferences fw_hdr+NextImageHeaderOffset unbounded (wild reads, non-terminating loops). Malicious/faulty IOC -> kernel page-fault panic and kernel-memory-to-device streaming. Guest harness reproduces the wild-read fault (Bus error/SIGSEGV); kernel path needs an MPT HBA (absent).