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

mps_config_get_man_pg10 allocates a fixed 0x5C-byte buffer but DMAs firmware PageLength*4 bytes into it - kernel heap overflow (the file's only fixed-size page allocation)

Summary

The getter sets cm_length from the firmware PageLength*4 (up to 1020) but allocates the page buffer at the fixed 92 bytes - the ONLY fixed-size page allocation in the file; every sibling getter and every getter in the successor mpr_config.c allocates cm_length. The polled DATAIN request maps cm_length bytes over the 92-byte allocation - the IOC DMAs the full Manufacturing Page 10 past the end: up to 928 bytes of fully firmware-controlled heap overflow. Guard-page harness on the guest: PageLength 23 clean, 24 faults (boundary exactly 0x5C), 255 = 928 bytes past; fixed model clean on all inputs. MAX-alloc + floor-guard fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4363 Β· 10 files
FileTypeDescriptionSize
harness.c β€” 6.5 KB view raw
build.sh β€” 96 B view raw
run.sh β€” 124 B view raw
run.log β€” 3.4 KB view raw
env.txt β€” 253 B view raw
fix.diff β€” 1.2 KB view raw
README.md β€” 3.0 KB ↓ raw
VERDICT.md β€” 4.0 KB ↓ raw
manifest.json β€” 1.0 KB view raw
verdict.json β€” 3.6 KB view raw

DF-4363 β€” mps_config_get_man_pg10: firmware-controlled DMA heap overflow

sys/dev/raid/mps/mps_config.c β€” the ONLY getter in the file that allocates a fixed-size page buffer while the DMA length comes from the firmware config page header.

  • mps_config.c:249 cm->cm_length = le16toh(mpi_reply->Header.PageLength) * 4; (PageLength is U8 β†’ up to 255 β†’ cm_length up to 1020 bytes)
  • mps_config.c:254 page = kmalloc(MPS_MAN_PAGE10_SIZE, ...) with MPS_MAN_PAGE10_SIZE 0x5C = 92 bytes (mpsvar.h:105)
  • mps_config.c:260 cm->cm_data = page; β†’ mps_request_polled β†’ mps.c:2099-2101 bus_dmamap_load(..., cm->cm_data, cm->cm_length, ...) β†’ mps_data_cb SGEs cover cm_length bytes; the IOC DMAs the whole Manufacturing Page 10 past the end of the 92-byte heap object.

Every sibling allocation in this file and every one in the newer sys/dev/raid/mpr/mpr_config.c uses kmalloc(cm->cm_length, ...) β€” this is the one divergent site (mp r twin: mpr_config.c:145,277,450,588,724,861,998, 1130,1262,1394,1552).

Reachable at attach/discovery on WarpDrive HBAs (mps_pci.c:143 SSS6200 β†’ MPS_FLAGS_WD_AVAILABLE; mps_sas.c:3320 β†’ mps_wd_config_pages β†’ mps_config.c:368). Same threat model as DF-1556/DF-1557: a buggy or malicious IOC firmware (or a device posing as one, e.g. hotplug/Thunderbolt) corrupts kernel heap memory with fully firmware-chosen bytes, up to 928 bytes past a 92-byte M_MPT2 allocation.

Reproduce (on the DragonFly guest, unprivileged)

cc -O2 -Wall -Wextra -o harness harness.c
./harness 24     # minimal overflow: 96-byte DMA into 92-byte kmalloc -> SIGSEGV
./harness 23     # boundary: 92 bytes exactly -> clean pass
./harness 255    # max: 1020-byte DMA -> 928 bytes past the allocation -> SIGSEGV

The guest has no LSI HBA (see env.txt), so β€” exactly like the DF-1556 and DF-1557 precedent β€” the harness reproduces the kernel arithmetic and the device-write pattern in an unprivileged userland process: the 92-byte "kmalloc" is placed against a PROT_NONE guard page, the firmware DMA is modeled as a memcpy of cm_length bytes of firmware-chosen data. SIGSEGV on the guard page == the heap overflow primitive.

Fix validation

cc -O2 -Wall -Wextra -DFIXED -o harness_fixed harness.c
./harness_fixed 24   # exit 0, no overflow
./harness_fixed 255  # exit 0, no overflow
./harness_fixed 4    # short-page floor guard path (fix.diff hunk 2)

fix.diff (never applied to sys/) sizes the allocation MAX(cm->cm_length, MPS_MAN_PAGE10_SIZE) and rejects pages shorter than the ProductSpecificInfo[5] deref needs (0x1C bytes, mps_config.c:286,297).

Files

  • harness.c β€” reproduction source (baseline + -DFIXED validation modes)
  • run.sh β€” exact build/run commands
  • run.log β€” FULL untrimmed guest output (build + decisive runs + fixed runs)
  • env.txt β€” guest uname / compiler / mps-in-GENERIC / no-HBA proof
  • fix.diff β€” git-apply-able fix vs sys/dev/raid/mps/mps_config.c
  • VERDICT.md, manifest.json, verdict.json
VERDICT.md
↓ download raw

DF-4363 VERDICT

Status: reproduced (harness, guest has no mps HBA β€” same method as DF-1556/DF-1557). Impact class: kernel heap overflow via device DMA (CWE-787). Primitive certain.

Root cause (line-accurate)

mps_config_get_man_pg10() at sys/dev/raid/mps/mps_config.c:181-314:

  1. :248-249 β€” after the PAGE_HEADER round-trip, the firmware-supplied mpi_reply->Header.PageLength (U8, in the reply copied at :221) becomes cm->cm_length = le16toh(PageLength) * 4 β†’ up to 1020.
  2. :254 β€” the page buffer is allocated at the fixed size MPS_MAN_PAGE10_SIZE = 0x5C = 92 bytes (mpsvar.h:105), the only fixed-size page allocation in the file. Every other getter allocates cm->cm_length (mps_config.c:136,607,730,852,970,1089,1209,1354), and every getter in the successor driver does the same (mpr_config.c:145,277,450,588,724,861,998,1130,1262,1394,1552).
  3. :260-261 β€” cm->cm_data = page; mps_request_polled(sc, cm); with MPS_CM_FLAGS_DATAIN: mps_map_command (mps.c:2099-2101) loads bus_dmamap_load(..., cm->cm_data, cm->cm_length, ...). busdma does not know the malloc size β€” it maps cm_length bytes starting at the 92-byte allocation, and mps_data_cb (mps.c:2041ff) builds SGEs covering the whole map. The IOC then DMAs the full Manufacturing Page 10 into it.

Result: firmware-controlled heap overflow of up to 928 bytes past a 92-byte M_MPT2 allocation, with fully controlled content (the page bytes), at kfetch/malloc heap in the config path.

The in-function derefs are NOT the bug: page->ProductSpecificInfo (offset 0x04, :286) and pPS_info[5] (0x18, :297-298) both sit inside 0x5C (Mpi2ManufacturingPagePS_t, mpi2_cnfg.h:722-727). The bug is purely the DMA-length vs allocation-size mismatch β€” which is why this is distinct from the knowns DF-1556 (:1117 missing-MIN bcopy) and DF-1557 (:462 PhysDiskMap index): neither covers this site.

Reachability

  • Attach/discovery on WarpDrive HBAs: mps_pci.c:143 ("LSI SSS6200" β†’ MPS_FLAGS_WD_AVAILABLE) β†’ mps_sas.c:3320 mps_wd_config_pages() β†’ mps_config.c:368 mps_config_get_man_pg10().
  • Threat model identical to DF-1556/DF-1557 (both rated High, this file): malicious/buggy firmware or a device posing as an SSS6200 (PCIe hotplug). Not reachable from an unprivileged process without such hardware β€” hence harness-level reproduction, per the established DF-1556/DF-1557 precedent.

Reproduction (guest, unprivileged user maxx)

Guard-page harness mirrors the kernel arithmetic 1:1; the device DMA is a memcpy of cm_length firmware-chosen bytes into a 92-byte buffer placed at the end of a page followed by PROT_NONE:

  • PageLength=24 β†’ cm_length=96 β†’ SIGSEGV, 4 bytes past the allocation (minimal case) β€” run.log RUN24_EXIT=1
  • PageLength=23 β†’ cm_length=92 β†’ exact fit, clean pass β€” RUN23_EXIT=0 (proves the boundary is exactly 0x5C)
  • PageLength=255 β†’ cm_length=1020 β†’ SIGSEGV, 928 bytes past β€” RUN255_EXIT=1

Fix validation

fix.diff (authored against the read-only sys/ tree; NOT applied there): size the buffer MAX(cm->cm_length, MPS_MAN_PAGE10_SIZE) and reject cm->cm_length < 0x1C before the ProductSpecificInfo[] derefs. Modeled in the harness via -DFIXED:

  • baseline: PageLength 24/255 β†’ SIGSEGV (overflow) β€” reproduced
  • fixed: PageLength 24/255 β†’ exit 0, no overflow; PageLength 4 β†’ EINVAL floor-guard path (protects the fixed allocation's own derefs)
  • fix_status: fixed (harness-level; a real kernel build cannot exercise the driver without an HBA, and the two hunks are mechanical).

Guest left clean (no kernel changes, /tmp artifacts only); no panic.

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 reproduced→ patch + rebuild →patched clean

Harness-level fix validation: baseline PageLength 24/255 fault on the guard page (heap overflow reproduced); with the fix.diff modeled (-DFIXED: MAX(cm_length,0x5C) allocation + short-page floor) the identical runs exit 0 with no overflow and PageLength=4 takes the new EINVAL path. Kernel-build validation not possible on this guest (no LSI HBA); both hunks are mechanical sizing guards.

findings/poc/DF-4363/run.log (FIXED sections: F24=0, F255=0, F4=0 vs baseline RUN24_EXIT=1/RUN255_EXIT=1); fix.diff
↓ fix.diffper-fix-DF-4363

Confirmed kernel references

Detail

Exploit chain

malicious/buggy WarpDrive SSS6200 firmware (or device posing as one via hotplug) returns Manufacturing Page 10 header with PageLength>23 -> IOC DMA writes up to 1020 firmware-chosen bytes into a 92-byte M_MPT2 heap allocation during discovery (mps_sas.c:3320 -> mps_wd_config_pages -> mps_config.c:368) -> kernel heap corruption with fully controlled content; device-driven, not unprivileged-user-driven (same threat model/severity class as the known DF-1556/DF-1557 in this file)

Evidence (decisive lines)

findings/poc/DF-4363/run.log: RUN24_EXIT=1 (SIGSEGV, +4 bytes), RUN23_EXIT=0 (exact 0x5C boundary), RUN255_EXIT=1 (+928 bytes), fixed runs F24/F255/F4=0; harness.c models mps_config.c:249,254,260 + mps.c:2099-2101 one-to-one; env.txt proves mps in GENERIC with no HBA attached

PoC changes

wrote harness from scratch (no seed): guard-page kmalloc(0x5C) model + fw-DMA memcpy model, baseline and -DFIXED validation modes in one file

Verified recommended fix

Size the man-pg10 buffer for the DMA: kmalloc(MAX(cm->cm_length, MPS_MAN_PAGE10_SIZE)) and reject cm_length < 0x1C before the ProductSpecificInfo derefs (fix.diff).

Verdict

REPRODUCED (harness; guest has no mps HBA - same method as DF-1556/DF-1557). mps_config_get_man_pg10 is the only getter in mps_config.c that allocates a fixed-size page buffer (MPS_MAN_PAGE10_SIZE=0x5C=92 bytes, mpsvar.h:105, kmalloc at mps_config.c:254) while the DMA length comes from the firmware page header (cm->cm_length = PageLength*4 up to 1020, mps_config.c:249). mps.c:2099-2101 bus_dmamap_load's cm_length bytes over that 92-byte allocation and the IOC DMAs the full Manufacturing Page 10 into it: firmware-controlled kernel heap overflow of up to 928 bytes. Guard-page harness on the guest: PageLength=24 -> SIGSEGV 4 bytes past; 23 -> exact fit clean; 255 -> SIGSEGV 928 bytes past. Fixed model (fix.diff: kmalloc(MAX(cm_length, 0x5C)) + <0x1C floor guard) exits clean on all inputs.