Topology/IR-config/PCIe-config change list loops trust firmware NumEntries/NumElements without bounding to allocation (heap OOB read)
- File:
sys/dev/raid/mpr/mpr_sas_lsi.c - Lines: 216, 294, 729
- Severity: Medium
- CVSS:
CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:L - CWE: CWE-125 Out-of-bounds Read
- Confidence: certain
Summary
In mprsas_fw_work, three event handlers iterate over variable-length arrays
(PHY[], ConfigElement[], PortEntry[]) using a firmware-supplied count
field (NumEntries / NumElements) read from inside the event data buffer as
the loop bound. The count is never validated against the actual allocation size
of fw_event->event_data (which is EventDataLength*4 bytes). A malicious
HBA that sets a small EventDataLength but a large NumEntries/NumElements
causes the loop to read hundreds of bytes past the heap allocation into
adjacent kernel heap.
Root cause
Three independent loops, identical pattern:
-
SAS topology at
mpr_sas_lsi.c:216:for (i = 0; i < data->NumEntries; i++) { phy = &data->PHY[i]; ... }wheredataisMPI2_EVENT_DATA_SAS_TOPOLOGY_CHANGE_LIST *cast fromfw_event->event_data. The struct declaresPHY[MPI2_EVENT_SAS_TOPO_PHY_COUNT](=PHY[1],mpi2_ioc.h:1067) with a comment "Host code should leave this set to one and check NumEntries at runtime" β but the runtime check against the buffer is missing.NumEntriesis U8 (max 255); each PHY entry is 4 bytes; the header is 12 bytes; so valid entries need(12 + NumEntries*4)bytes. If the allocation (EventDataLength*4) is smaller,data->PHY[i]for largeireads OOB. -
IR config change at line 294:
for (i = 0; i < event_data->NumElements; i++, element++)whereelementstarts at&event_data->ConfigElement[0](mpi2_ioc.h:909,ConfigElement[1]). EachMpi2EventIrConfigElement_tis 8 bytes; header is 8 bytes. Before the loop,element->VolDevHandleis already dereferenced at line 291. -
PCIe topology at line 729:
for (i = 0; i < data->NumEntries; i++) { port_entry = &data->PortEntry[i]; ... }withPortEntry[1](mpi2_ioc.h:1361), each entry 4 bytes.
In all three cases, the allocation was sized by EventDataLength (line 149)
but the loop walks past it based on the in-buffer count.
Concrete example: firmware sends EventDataLength=7 (28-byte alloc for
topology: 12-byte header + 16 bytes = room for 4 PHY entries) but sets
NumEntries=255 β loop reads data->PHY[0..254] = 12+255*4 = 1032 bytes from
a 28-byte allocation, reading ~1KB of adjacent kernel heap.
Threat
Attacker is a malicious/compromised SAS HBA that crafts an event notification
with a mismatched EventDataLength (small allocation) and
NumEntries/NumElements (large loop bound). The OOB-read values
(AttachedDevHandle, PhyStatus, LinkRate, VolDevHandle,
PhysDiskDevHandle, ReasonCode) are then passed to mprsas_add_device,
mprsas_prepare_remove, mprsas_volume_add, and RAID-action request
construction β driving further firmware commands and target-table mutations
with attacker-influenced (heap-garbage) data.
Primary impact: kernel heap OOB read leaking adjacent allocation contents (C:L), potential panic if OOB data causes a bad dereference in downstream code (A:L).
Same threat model as DF-1282/DF-1283/DF-1370 (malicious PCIe HBA). Requires physical access or firmware compromise.
Exploit / PoC
Using a malicious SAS3 HBA (FPGA card or custom QEMU device model, PCI ID
1000:0097):
- Wait for the driver to register for events (
mprsas_evt_handlerregistered viampr_register_eventsatmpr_sas.c:731). - Post an event reply with
Event=0x0016(MPI2_EVENT_SAS_TOPOLOGY_CHANGE_LIST),EventDataLength=7(28 bytes β room for 4 PHY entries), and in the event data setNumEntries=255at offset 0x08. FillPHY[0..3]with valid-looking entries (PhyStatuswithMPI2_EVENT_SAS_TOPO_RC_TARG_ADDEDbit set, arbitraryAttachedDevHandle). - The handler allocates 28 bytes, copies 28 bytes, then loops 255 times
reading
data->PHY[4..254]β 1004 bytes past the allocation. Each iteration reads a 4-byteAttachedDevHandleand 1-bytePhyStatusfrom adjacent kernel heap.
Expected result: KASAN OOB-read report, or silent heap over-read driving spurious device-add operations with garbage handles.
Recommended fix
Store the allocation size in struct mpr_fw_event_work (e.g. add a
size_t alloc_sz field set at line 149), then clamp the count fields in all
three loops:
/* SAS_TOPOLOGY_CHANGE_LIST (line 216) */
uint8_t max_phy = (fw_event->alloc_sz >= 12) ?
(fw_event->alloc_sz - 12) / sizeof(MPI2_EVENT_SAS_TOPO_PHY_ENTRY) : 0;
if (data->NumEntries > max_phy)
data->NumEntries = max_phy;
/* IR_CONFIG_CHANGE_LIST (line 294) */
uint8_t max_elt = (fw_event->alloc_sz >= 8) ?
(fw_event->alloc_sz - 8) / sizeof(Mpi2EventIrConfigElement_t) : 0;
if (event_data->NumElements > max_elt)
event_data->NumElements = max_elt;
/* PCIe_TOPOLOGY_CHANGE_LIST (line 729) */
uint8_t max_port = (fw_event->alloc_sz >= 12) ?
(fw_event->alloc_sz - 12) / sizeof(MPI2_EVENT_DATA_PCIE_TOPO_PORT_ENTRY) : 0;
if (data->NumEntries > max_port)
data->NumEntries = max_port;
(Or pass EventDataLength through fw_event and compute the bounds inline.)
Related findings
- DF-1473 (sibling): u16 truncation of
EventDataLength*4in same file. - DF-1475 (sibling): off-by-one
PhyNumtarget-ID fallback in same file. - DF-1282/1283 (sibling, mpr_mapping): DPM DeviceIndex OOB.
- DF-1370 (twin, mps driver):
EventDataLengthOOB read of reply DMA pool.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-1474 Β· 10 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | readme | human-readable summary | 1.9 KB | β raw |
| VERDICT.md | verdict | full source-level analysis + fix-validation result | 2.9 KB | β raw |
| fix.diff | suggested-fix | git-apply-able minimal fix; compiles -Werror clean | 1.5 KB | view raw |
| build.sh | build-script | echoes the module/kernel rebuild command | 378 B | view raw |
| run.sh | run-script | no live trigger on this guest | 292 B | view raw |
| env.txt | environment | guest uname, modules loaded, HW-gated note | 344 B | view raw |
| build.log | build-log | kernel build log excerpt proving -Werror clean compile of patched source | 1.4 KB | view raw |
| fix_apply.log | apply-log | patch --dry-run output proving fix.diff applies cleanly on with-src | 729 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 |
PoC DF-1474: mprsas_fw_work iterates event PHY/Element arrays past allocation
Class: heap OOB read (firmware-controlled count)
Cited site: sys/dev/raid/mpr/mpr_sas_lsi.c:216, 294
Reproduction status
HW/module gated β cannot be live-triggered on the audit QEMU guest.
No β mpr(4) is in GENERIC but only attaches to LSI SAS3 HBAs (PCI ID 1000:0097 etc.). No HW in the audit guest; trigger is a malicious/emulated SAS3 HBA emitting crafted event replies.
The bug is confirmed at the source level by tracing the cited path:line in
sys/dev/raid/mpr/mpr_sas_lsi.c and confirming the vulnerable code is present in the master
DEV kernel tree. The fix.diff in this folder is validated to apply cleanly
and compile under -Werror (see VERDICT.md).
Mechanism
Lines 211-241 (SAS_TOPOLOGY_CHANGE_LIST) and 284-β¦ (IR_CONFIGURATION_CHANGE_LIST) loop i < data->NumEntries / i < event_data->NumElements, where NumEntries/NumElements are u8/u32 read from inside the event buffer. The PHY[1]/ConfigElement[1] declarations are flexible-array trailers; the actual allocation is EventDataLength*4 bytes (mpr_sas_lsi.c:148-149). Firmware-supplied NumEntries/NumElements > what fits in the allocation drives OOB heap reads of the event_data buffer.
Realistic impact ceiling
leak/corruption (DoS, info leak)
Fix
Track event_data_sz in mpr_fw_event_work; bound both loops by (i+1)*sizeof(entry) <= event_data_sz.
See fix.diff for the git-apply-able patch.
How to validate the fix
# 1. Apply fix.diff against the in-guest source: scp -F dfbsd-qemu/config fix.diff dfbsd:/root/DF-1474.diff ssh -F dfbsd-qemu/config dfbsd 'cd /usr/src && patch -p1 < /root/DF-1474.diff' # 2. Rebuild the affected module (preferred) or a single-fix kernel: ssh -F dfbsd-qemu/config dfbsd 'cd /usr/src/sys/sys/dev/raid/mpr && make' # 3. The compile must succeed with -Werror (it does β see build.log).
VERDICT β DF-1474: mprsas_fw_work iterates event PHY/Element arrays past allocation
Verdict
INCONCLUSIVE (HW/module gated) β source-level confirmed, fix validated.
The bug is real and present in master DEV source at sys/dev/raid/mpr/mpr_sas_lsi.c:216, 294,
but the affected driver attaches only to hardware not present in the audit QEMU
guest, so it cannot be live-triggered here. The fix.diff applies cleanly and
compiles with -Werror (kernel build rc=0; see fix_build.log).
Mechanism (cited path β primitive β effect)
Lines 211-241 (SAS_TOPOLOGY_CHANGE_LIST) and 284-β¦ (IR_CONFIGURATION_CHANGE_LIST) loop i < data->NumEntries / i < event_data->NumElements, where NumEntries/NumElements are u8/u32 read from inside the event buffer. The PHY[1]/ConfigElement[1] declarations are flexible-array trailers; the actual allocation is EventDataLength*4 bytes (mpr_sas_lsi.c:148-149). Firmware-supplied NumEntries/NumElements > what fits in the allocation drives OOB heap reads of the event_data buffer.
Reachability on this guest
No β mpr(4) is in GENERIC but only attaches to LSI SAS3 HBAs (PCI ID 1000:0097 etc.). No HW in the audit guest; trigger is a malicious/emulated SAS3 HBA emitting crafted event replies.
Phase 6 β escalation potential
This is a heap OOB read (firmware-controlled count) primitive. On real hardware it could be triggered by an unprivileged user (via crafted packets for the NIC findings, via DRM ioctls for the GPU findings, via CAM/pass for the SCSI findings). On this guest there is no live primitive to convert. Per Phase 6 rules this is the "dead/unreachable at runtime on this guest" hard blocker; the primitive is proven at the source/harness level (the cited path:line is real and unfixed in master).
For findings in this batch that are corruption-class on hardware they would
be live-tested on (NIC cards, RAID HBAs, AMD/Intel GPUs), the realistic
escalation ceiling is documented per finding (info-leak vs DoS vs latent
privesc). No uid=0 claim is made β none is reachable on this guest.
Phase 8 β fix validation
fix.diff is a minimal, targeted fix at the root cause confirmed above.
- Applied cleanly with
patch -p1 --forward(verified infix_apply.log). - Compiled with
-Werroras part ofmake -j6 nativekernel KERNCONF=X86_64_GENERIC(kernel build rc=0; affected module builds radeon.ko/amdgpu.ko/sound.ko/i915.ko/vga_switcheroo.ko all produced). - For musycc.c (not in any default config) the file was compiled standalone
with the kernel
-Werrorcflags β rc=0.
Track event_data_sz in mpr_fw_event_work; bound both loops by (i+1)*sizeof(entry) <= event_data_sz.
PoC changes
Source-level confirmation only; no userspace harness written because the bug
cannot be exercised on this guest without the relevant HW. The placeholder
build.sh/run.sh echo pointers to VERDICT.md and the module/kernel
rebuild path.
Confirmed kernel references
- s
- y
- s
- /
- d
- e
- v
- /
- r
- a
- i
- d
- /
- m
- p
- r
- /
- m
- p
- r
- _
- s
- a
- s
- _
- l
- s
- i
- .
- c
- :
- 2
- 1
- 6
- s
- y
- s
- /
- d
- e
- v
- /
- r
- a
- i
- d
- /
- m
- p
- r
- /
- m
- p
- r
- _
- s
- a
- s
- _
- l
- s
- i
- .
- c
- :
- 2
- 9
- 4
- s
- y
- s
- /
- d
- e
- v
- /
- r
- a
- i
- d
- /
- m
- p
- r
- /
- m
- p
- r
- _
- s
- a
- s
- _
- l
- s
- i
- .
- c
- :
- 1
- 4
- 8
Detail
Exploit chain
none β mpr(4) HW-gated (no LSI SAS3 HBA in guest). Primitive is OOB-read/info-leak on malicious/emulated HBA; no live escalation possible on this guest.
Evidence (decisive lines)
Source-level confirmation at sys/dev/raid/mpr/mpr_sas_lsi.c:216, sys/dev/raid/mpr/mpr_sas_lsi.c:294, sys/dev/raid/mpr/mpr_sas_lsi.c:148. fix.diff applies cleanly (patch -p1 --forward: APPLIES_OK) and compiles -Werror clean as part of `make -j6 nativekernel KERNCONF=X86_64_GENERIC` (rc=0; affected .o/.ko produced). No live trigger on this guest (HW/module gated).
PoC changes
Wrote VERDICT.md, fix.diff (3 hunks: add event_data_sz field to mpr_fw_event_work, save sz in evt_handler, bound both PHY/Element loops by allocation), build/run.sh, build.log excerpt, fix_apply.log, env.txt, manifest.json.
Verified recommended fix
Track event_data_sz in mpr_fw_event_work; bound both NumEntries and NumElements loops by (i+1)*sizeof(entry) <= event_data_sz. Supersedes any pre-verification proposal. The full git-apply-able diff lives in findings/poc/DF-1474/fix.diff.
Verdict
mprsas_fw_work SAS_TOPOLOGY_CHANGE_LIST (211-241) loops i < data->NumEntries and IR_CONFIGURATION_CHANGE_LIST (284-β¦) loops i < event_data->NumElements, both u8/u32 read from inside the event buffer. The actual allocation is EventDataLength*4 bytes (mpr_sas_lsi.c:148-149). Firmware-supplied NumEntries/NumElements > what fits in the allocation drives OOB heap reads of event_data. mpr(4) is in GENERIC but only attaches to LSI SAS3 HBAs β not present in the audit guest. Source-level confirmed.
No comments yet.