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)
PoC verification
Evidence pack
findings/poc/DF-4363 Β· 10 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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:249cm->cm_length = le16toh(mpi_reply->Header.PageLength) * 4;(PageLength is U8 β up to 255 β cm_length up to 1020 bytes)mps_config.c:254page = kmalloc(MPS_MAN_PAGE10_SIZE, ...)withMPS_MAN_PAGE10_SIZE 0x5C= 92 bytes (mpsvar.h:105)mps_config.c:260cm->cm_data = page;βmps_request_polledβmps.c:2099-2101bus_dmamap_load(..., cm->cm_data, cm->cm_length, ...)βmps_data_cbSGEs 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 +-DFIXEDvalidation modes)run.shβ exact build/run commandsrun.logβ FULL untrimmed guest output (build + decisive runs + fixed runs)env.txtβ guest uname / compiler / mps-in-GENERIC / no-HBA prooffix.diffβ git-apply-able fix vssys/dev/raid/mps/mps_config.cVERDICT.md,manifest.json,verdict.json
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:
:248-249β after the PAGE_HEADER round-trip, the firmware-suppliedmpi_reply->Header.PageLength(U8, in the reply copied at:221) becomescm->cm_length = le16toh(PageLength) * 4β up to 1020.:254β the page buffer is allocated at the fixed sizeMPS_MAN_PAGE10_SIZE = 0x5C = 92bytes (mpsvar.h:105), the only fixed-size page allocation in the file. Every other getter allocatescm->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).:260-261βcm->cm_data = page; mps_request_polled(sc, cm);withMPS_CM_FLAGS_DATAIN:mps_map_command(mps.c:2099-2101) loadsbus_dmamap_load(..., cm->cm_data, cm->cm_length, ...). busdma does not know the malloc size β it mapscm_lengthbytes starting at the 92-byte allocation, andmps_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:3320mps_wd_config_pages()βmps_config.c:368mps_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
fixedHarness-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
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.
No comments yet.