Unvalidated IOC-pg8 MaxNumPhysicalMappedIDs used as num_slots: heap OOB write in _mapping_process_dpm_pg0 reservation loop
Summary
In ENCLOSURE_SLOT_MAPPING mode, a DPM pg0 entry whose DeviceIndex is inside the direct-attached range sets num_slots = max_num_phy_ids, the firmware u16 IOC-page-8 field never validated anywhere. The reservation loop writes num_slots consecutive 32-byte dev_mapping_table entries starting at mapping_table[dev_idx] with no check that dev_idx+num_slots <= max_devices, while the table is kmalloc(max_devices*32). With an IN-BOUNDS dev_idx and MaxNumPhysicalMappedIDs=0xFFFF this is a linear boot-time heap overflow of up to ~2MB past a ~16KB allocation, 32 bytes per entry, each carrying the firmware-chosen 64-bit PhysicalIdentifier. Distinct from DF-1374 (index OOB; here index in bounds, count unbounded) and DF-1375 (event path). Harness-proven on guest (65,281 entries = 2,088,992 bytes past the table, canary at every slot). LEAD: mpr sibling has the same defect at mpr_mapping.c:2285-2290 (unfiled).
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4085 Β· 13 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 4.6 KB | β raw | |
| VERDICT.md | β | 4.4 KB | β raw | |
| harness_df4085.c | β | 4.5 KB | view raw | |
| build.sh | β | 102 B | view raw | |
| run.sh | β | 97 B | view raw | |
| build.log | β | 75 B | view raw | |
| run.log | β | 597 B | view raw | |
| run.2.log | β | 597 B | view raw | |
| env.txt | β | 1.4 KB | view raw | |
| fix.diff | β | 623 B | view raw | |
| fix_build.log | β | 12.0 KB | view raw | |
| manifest.json | β | 836 B | view raw | |
| verdict.json | β | 3.8 KB | view raw |
DF-4085 β Unvalidated IOC-pg8 MaxNumPhysicalMappedIDs as num_slots β heap OOB write in _mapping_process_dpm_pg0
File: sys/dev/raid/mps/mps_mapping.c (branch :1512-1518, reservation loop :1544-1563)
Class: CWE-787 Out-of-bounds Write (heap) / CWE-20 unvalidated firmware count
Severity: High β pass 2 (GLM 5.3), bucket memcorrupt
Not a duplicate of DF-1374 (that finding is the unvalidated DeviceIndex
index at the same function): here dev_idx is in bounds and the missing
bound is the count β num_slots is taken from IOC page 8's
MaxNumPhysicalMappedIDs with no check that dev_idx + num_slots <=
max_devices. DF-1375's num_slots text covers a different source (live
enclosure-event NumSlots in _mapping_get_dev_info:992), not DPM-pg0
ingestion.
The bug (source-confirmed)
mps_mapping_initialize()(:1719) enables DPM whenioc_pg8.MaxPersistentEntries != 0(:1739-1740) and reads the DPM page (:1780), then calls_mapping_process_dpm_pg0()(:1793).- For every non-empty DPM entry in ENCLOSURE_SLOT_MAPPING mode, if
dev_idx < num_rsvd_entries + max_num_phy_ids(the direct-attached virtual enclosure range) the code setsc num_slots = max_num_phy_ids; /* :1518 */max_num_phy_ids = le16toh(sc->ioc_pg8.MaxNumPhysicalMappedIDs)(:1469) β a firmware-supplied u16 that is never validated anywhere againstmax_devices(=facts->MaxTargets + facts->MaxVolumes,:1734). - The reservation loop
c mt_entry = &sc->mapping_table[dev_idx]; /* :1544 */ for (map_idx = dev_idx; map_idx < (dev_idx + num_slots); map_idx++, mt_entry++) { ... writes ... } /* :1545-1563 */writesnum_slotsconsecutivestruct dev_mapping_tableentries (physical_id= the DPM entry's 64-bit PhysicalIdentifier,phy_bits,channel,id,dpm_entry_num,missing_count,device_info) with no end-of-table check.mapping_tableiskmalloc(max_devices * 32, M_MPT2)(mps_mapping.c:1393-1394).
With MaxNumPhysicalMappedIDs = 0xFFFF (or any value pushing
dev_idx + max_num_phy_ids past max_devices) and an ordinary in-bounds
dev_idx, boot-time DPM ingestion performs a linear heap overflow of up to
~2 MB past a ~16 KB allocation, 32 bytes per entry, each entry carrying the
firmware-chosen 64-bit physical_id at offset 0. The stored
et_entry->num_slots = num_slots (:1539) additionally re-arms the
already-known DF-1375 slot-reservation overflow for every later topology
event.
The (else) branch is safe (num_slots = 4-bit slot field of
MappingInformation, max 0xF); only the direct-attach branch is unbounded.
The enclosure_table growth itself is bounds-checked (:1525-1531), and
dpm_entry_used[]/dpm_flush_entry[] walks are bounded by
max_dpm_entries β the count is the sole missing bound.
Threat model
Same as DF-1374/DF-1375/DF-3914: a malicious or buggy LSI/Avago IOC firmware image, NVRAM corruption of the persistent map, or PCI passthrough of an emulated mps(4) HBA. Everything on this path (IOC facts, IOC pg8, DPM pg0 content) is firmware-controlled DMA'd data; nothing is validated against the allocation sizes derived from the same firmware.
Reproduction (object-level, hardware-gated)
No mps HBA exists in the audit QEMU guest (see env.txt), so the kernel path
cannot be exercised. harness_df4085.c replicates the exact struct
(mpsvar.h:135, sizeof == 32 on amd64) and the verbatim :1544-1563 loop
and drives it with a fully in-bounds dev_idx:
cc -O2 -Wall -o harness harness.c && ./harness
Expected decisive output (see run.log, stable across reruns in run.2.log):
max_devices : 510 (table = 16320 bytes) MaxNumPhysicalMappedIDs : 0xffff (IOC pg8, unvalidated) DPM entry 0 : dev_idx=256 (in bounds!), num_slots=65535 reservation loop wrote : [256, 65791) entries past table end : 65281 kernel equivalent : 2088992-byte linear heap write of fw-controlled data past a 16320-byte kmalloc, attacker qword 0x5141524153534141 at stride 32 RESULT: OOB WRITE PRIMITIVE PROVEN (canary smash, stride 32)
Fix
fix.diff β validate, before storing/looping, that the entry fits the table:
dev_idx < max_devices && num_slots <= max_devices - dev_idx, else skip the
DPM entry (consistent with the existing conflict handling). Compile-validated:
applied (together with DF-4086's clamp and the other mps fixes) to the guest
/usr/src; make in sys/dev/raid/mps rebuilt the whole module with
-Werror and linked mps.ko (BUILD_EXIT=0, fix_build.log).
DF-4085 VERDICT
Status: not_reproduced (kernel level) β hardware-gated; primitive proven at object level on the audit guest (userland harness, verbatim loop). Confidence: certain (source arithmetic) / the OOB write itself is proven by the harness; only the in-kernel execution is gated by absent hardware.
Source chain (line-by-line)
sys/dev/raid/mps/mps.c:1228mps_startup()βmps_mapping_initialize(sc)(return ignored β moot here: withM_INTWAITallocations it cannot fail).mps_mapping.c:1739-1740max_dpm_entries = ioc_pg8.MaxPersistentEntries;is_dpm_enable = !!max_dpm_entriesβ DPM active on any non-zero value.mps_mapping.c:1774-1793DPM pg0 read intosc->dpm_pg0(kmalloc(8 + 20*max_dpm_entries),:1418-1421) and_mapping_process_dpm_pg0()called.mps_mapping.c:1469max_num_phy_ids = le16toh(ioc_pg8.MaxNumPhysicalMappedIDs)β u16, firmware field, grep confirms no validation againstmax_devicesanywhere in the driver.mps_mapping.c:1512-1518ENCLOSURE_SLOT_MAPPING branch:dev_idx < num_rsvd_entries + max_num_phy_idsβnum_slots = max_num_phy_ids(u16, up to 65535). Else-branchnum_slotsβ€ 0xF (4-bit slot field) β bounded.mps_mapping.c:1544-1563mt_entry = &sc->mapping_table[dev_idx];for (map_idx = dev_idx; map_idx < dev_idx + num_slots; map_idx++, mt_entry++)β writesphysical_id(fw 64-bit),phy_bits,channel,id,dpm_entry_num,missing_count,device_infoper entry. No check thatdev_idx + num_slots <= max_devices.mps_mapping.c:1393-1394mapping_table = kmalloc(sizeof(struct dev_mapping_table) * max_devices)= 32 bytes/entry (verified: harness prints sizeof == 32 on amd64).
Overflow condition: dev_idx + max_num_phy_ids > max_devices with dev_idx
itself in bounds (e.g. MaxTargets=508, MaxVolumes=2 β max_devices=510;
MaxNumPhysicalMappedIDs=0xFFFF; dev_idx=0x100). Result: 65,281 entries Γ 32 B
= 2,088,992 bytes written past a 16,320-byte heap allocation at attach
time, fully deterministic, contents firmware-chosen.
Distinct from the known findings:
- DF-1374 = unvalidated DeviceIndex (index OOB) at the same function β
fixed by bounding dev_idx; the count stays unbounded, and with
num_slots == 0 (slot-field 0) DF-1374's loop never even writes. This
finding is the count (MaxNumPhysicalMappedIDs), reachable with a fully
in-bounds index.
- DF-1375 covers (a) slot - start_slot underflow in
_mapping_add_new_device:1165 and (b) the event-NumSlots reservation
loop _mapping_get_dev_info:992-998. Neither is DPM-pg0 ingestion.
Verification performed
- Guest
pciconf -l(env.txt): virtio-only PCI bus, no SAS HBA βmps_attachnever runs; kernel-level reproduction impossible on this target (same gate as DF-1374/DF-1375/DF-3914; the guest is otherwise intentionally exploitation-permissive β no SMAP/SMEP/KASLR). harness_df4085.cbuilt and run as the unprivileged user on the guest (cc 8.3), verbatim struct + loop; decisive output inrun.log; rerun inrun.2.log(identical). Canary 0x5141524153534141 lands at offset 0 of every 32-byte slot past the table end β in-kernel equivalent: fw-controlled qword at 32-byte stride over adjacent kmalloc zones (slab metadata, neighboring objects).fix.diffapplied to the guest/usr/src(with DF-4086's clamp and the other mps fixes of this batch);makein/usr/src/sys/dev/raid/mpscompiledmps_mapping.cwith-Werrorand linkedmps.ko(BUILD_EXIT=0) βfix_build.log. Guest/usr/srcthen restored pristine (reverse-patch verified) and the guest reset with the clean-source snapshot.
Exploitability ceiling (hardware-gated chain)
malicious/compromised IOC firmware or emulated HBA (PCI passthrough) β
DPM pg0 entry + IOC pg8 MaxNumPhysicalMappedIDs β boot-time
_mapping_process_dpm_pg0 β linear 2 MB heap overwrite with fw-chosen
qwords at stride 32 β adjacent slab-object corruption (e.g. function-pointer
-bearing structures) β kernel code execution. Blocked here only by absent
hardware, not by guest hardening.
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
not_testableApplied to the guest /usr/src together with this batch's other mps fixes (DF-4085..DF-4090); make in sys/dev/raid/mps compiled all sources with -Werror and linked mps.ko (BUILD_EXIT=0). Runtime fix validation not testable: the path requires an mps(4) HBA delivering firmware data, absent on this guest.
findings/poc/DF-4085/fix.diff, findings/poc/DF-4085/fix_build.log
Confirmed kernel references
Detail
Exploit chain
Hardware-gated chain (not executable on this guest): malicious/compromised IOC firmware or PCI-passthrough emulated HBA -> IOC pg8 MaxNumPhysicalMappedIDs=0xFFFF + DPM pg0 entry with in-bounds DeviceIndex -> mps_mapping_initialize -> _mapping_process_dpm_pg0 reservation loop -> ~2MB linear heap overwrite with fw-controlled qwords at 32-byte stride past mapping_table into adjacent kmalloc zones -> slab metadata / neighboring object corruption -> kernel code execution. Stopped at primitive proof: no HBA in guest.
Evidence (decisive lines)
run.log/run.2.log (guest harness: 2,088,992-byte canary smash at stride 32, in-bounds dev_idx), harness_df4085.c (verbatim struct mpsvar.h:135 + loop :1544-1563), env.txt (hardware gate), VERDICT.md (line-by-line chain), fix.diff + fix_build.log (-Werror module build with the fix, mps.ko linked)
PoC changes
No seed PoC (pass-2 finding). Harness authored against the exact struct layout with the verbatim :1544-1563 loop; built and run on the audit guest with cc 8.3 as an unprivileged user.
Verified recommended fix
In _mapping_process_dpm_pg0, skip any DPM entry with dev_idx >= max_devices or num_slots > max_devices - dev_idx before storing into enclosure_table or running the reservation loop.
Verdict
Source-certain heap OOB write (CWE-787), pass 2. In _mapping_process_dpm_pg0 (sys/dev/raid/mps/mps_mapping.c:1512-1518), DPM entries whose DeviceIndex falls in the direct-attached range take num_slots = max_num_phy_ids, the firmware u16 ioc_pg8.MaxNumPhysicalMappedIDs, never validated against the mapping-table size; the reservation loop :1544-1563 then writes num_slots consecutive 32-byte struct dev_mapping_table entries with no check that dev_idx+num_slots <= max_devices (kmalloc(max_devices*32) at :1393-1394). With an IN-BOUNDS dev_idx (distinct from DF-1374's unvalidated index; distinct from DF-1375's event-NumSlots path) a malicious/buggy IOC or emulated HBA gets a linear boot-time heap overflow: harness shows 65,281 entries (2,088,992 bytes) past a 16,320-byte allocation, firmware-controlled qword at stride 32. Not reproduced at kernel level: no SAS HBA on the QEMU guest (env.txt), mps never attaches - same hardware gate as DF-1374/1375/3914. Primitive proven at object level on the guest (harness replicates verbatim struct+loop; run.log, stable across reruns).
No comments yet.