Unvalidated firmware DeviceIndex in _mapping_process_dpm_pg0 causes heap OOB write into mapping_table
Summary
_mapping_process_dpm_pg0 at mps_mapping.c:1495: dev_idx=le16toh(dpm_entry->DeviceIndex) from firmware. Only IR path validated (:1497). ENCLOSURE_SLOT_MAPPING (:1544) and DEVICE_PERSISTENCE (:1567) use dev_idx directly into mapping_table[max_devices]. dev_idx>=max_devices -> heap OOB write with attacker-controlled physical_id/device_info. Twin of mpr_mapping.c DF-1282. Malicious/buggy HBA or PCI passthrough. Fix: validate dev_idx<max_devices.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-1374 Β· 12 files| File | Type | Description | Size | |
|---|---|---|---|---|
| harness.c | trigger-source | object-level proof: replays _mapping_process_dpm_pg0 Enc/Slot path with firmware DeviceIndex=max_devices+2 | 4.3 KB | view raw |
| fix.diff | suggested-fix | adds dev_idx/num_slots bounds checks to Enc/Slot and Device-Persistence paths | 1.3 KB | view raw |
| build.sh | repro-script | cc -O2 -o harness harness.c | 125 B | view raw |
| run.sh | repro-script | ./harness | 60 B | view raw |
| build.log | build-log | harness build, full output | 95 B | view raw |
| run.log | run-log | harness decisive run: BUG CONFIRMED + 4/64 guard entries corrupted | 532 B | view raw |
| fix_build.log | fix-build-log | clean mps.ko module build with fix applied, rc=0 (mps.ko 193880B) | 16.0 KB | view raw |
| env.txt | environment | uname + cc version + dev-node check | 520 B | view raw |
| README.md | readme | summary + reproduce | 927 B | β raw |
| VERDICT.md | verdict | full mechanism + reachability + fix | 5.1 KB | β 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 |
DF-1374 β _mapping_process_dpm_pg0 DeviceIndex OOB write (mps)
Summary
_mapping_process_dpm_pg0 (sys/dev/raid/mps/mps_mapping.c:1495) reads
dev_idx = le16toh(dpm_entry->DeviceIndex) from firmware and indexes
mapping_table[dev_idx] on the Enc/Slot (:1544) and Device-Persistence
(:1567) paths without bounds-checking against max_devices. Only the IR
path (:1497) validates it. Twin of DF-1282 (mpr). No SAS HBA on the audit
guest.
Reproduce
./build.sh # cc -O2 -o harness harness.c ./run.sh # ./harness
Expected: BUG CONFIRMED: dev_idx=266 >= max_devices=264 ... 64 bytes PAST the
allocation, guard/slab region corrupted: YES (4 of 64 entries touched).
Object-level proof β mps cannot attach on the QEMU guest (no SAS HBA).
Fix
fix.diff adds dev_idx/num_slots bounds checks before both unbounded
indexings. Validated to apply + compile (mps.ko, clean build rc=0).
DF-1374 β VERDICT
Verdict: REPRODUCED (primitive confirmed at object/harness level; runtime path is hardware-bound and not present on the audit guest).
This is the mps driver's twin of DF-1282 (the identical bug in the mpr
driver's mpr_mapping.c). The DB file column reads mpr_mapping.c but the
cited line numbers (1495/1497/1544/1567) and the bug description match
sys/dev/raid/mps/mps_mapping.c exactly β the mps copy of the code. (In
mpr_mapping.c the same statements are at 2251/2260/2327/2358, already covered
by DF-1282.) The fix is authored against mps_mapping.c where the cited lines
actually reside.
Mechanism (source trace)
_mapping_process_dpm_pg0() (sys/dev/raid/mps/mps_mapping.c:1461) copies the
HBA's persistent Device Persistence Mapping (DPM) entries into the driver's
mapping_table.
-
Attacker-controlled source value β
mps_mapping.c:1495:c dev_idx = le16toh(dpm_entry->DeviceIndex); /* u32, firmware-controlled */dev_idxis declaredu32(:1465) and read directly from the controller's DPM page, whose contents are written by HBA firmware (influenced by attached devices) and/or NVRAM. -
Allocation size β
mapping_tableiskmalloc(sizeof(dev_mapping_table) * sc->max_devices, ...);max_devices = facts->MaxTargets + max_volumes, auint16_t(mpsvar.h);MaxTargetsisU16. Typical ~264. -
Bounded path (IR firmware) β
mps_mapping.c:1497:c if (sc->ir_firmware && (dev_idx >= start_idx) && (dev_idx <= end_idx)) { ... }This is the ONLY path that validatesdev_idx. -
UNBOUNDED sinks β
mps_mapping.c:1544(Enc/Slot) doesmt_entry = &sc->mapping_table[dev_idx];and:1545writesnum_slotsconsecutive entries in a loop (num_slotsalso firmware-derived);:1567(Device Persistence) doesmap_idx = dev_idx; mt_entry = &sc->mapping_table[map_idx];β both with no check thatdev_idx < max_devices.
A DPM entry with DeviceIndex >= max_devices therefore writes attacker-shaped
fields (physical_id, phy_bits, id, dpm_entry_num, device_info) past
the end of the mapping_table heap allocation into the adjacent slab object(s).
Primitive characterization
- Write size:
sizeof(struct dev_mapping_table)= 32 bytes per entry. - Multiplier: Enc/Slot path writes
num_slotsconsecutive entries (up to a firmware-controlled value); Device Persistence writes 1. - Content control: largely attacker-shaped (
physical_id= attacker enclosure WWN;phy_bits;device_info = MPS_DEV_RESERVED). - Target: the
mapping_tablekmalloc slab; overflow corrupts the adjacent slab object.
Harness proof
harness.c allocates mapping_table[MAX_DEVICES] + a canary guard region and
replays the Enc/Slot path with dev_idx = max_devices+2, num_slots = 4.
Output (run.log):
[DF-1374] BUG CONFIRMED: dev_idx=266 >= max_devices=264 -> write at mapping_table[266] is 64 bytes PAST the allocation (into the adjacent slab object) [DF-1374] guard/slab region corrupted: YES -> next heap object(s) overwritten (4 of 64 entries touched)
Why not a live in-kernel reproduction (the valid hard blocker)
The mps driver attaches to LSI/Avago SAS HBAs. The audit guest is a QEMU/KVM
VM with only virtio devices (no SAS/RAID HBA). The driver never attaches, so
_mapping_process_dpm_pg0 is never called at runtime. Phase-6 valid hard
blocker #3: the vulnerable code path is unreachable at runtime on this guest;
the primitive is proven at the object/harness level and the live trigger
conditions (presence of an mps-attached HBA whose DPM table holds a malformed
entry β reachable via a malicious peripheral, malicious firmware, NVRAM
corruption, or a passed-through HBA to a malicious VM) are noted.
Exploit chain / escalation
Write-capable primitive, but escalation requires the primitive to fire inside a
running kernel, which on this guest it cannot (no HBA). On a host with an mps
HBA present, the chain would be: trigger via topology/DPM event β overflow
corrupts adjacent slab object β groom so the victim is a function-pointer-bearing
or ucred-bearing object β redirect β uid=0. That chain cannot be demonstrated
on this guest (hardware absent); the honest reported impact is the corruption
primitive itself.
Fix
fix.diff adds the missing bounds checks (dev_idx >= sc->max_devices and
dev_idx + num_slots > sc->max_devices) before the Enc/Slot indexings and
dev_idx >= sc->max_devices before the Device-Persistence indexing, mirroring
the check the IR path already performs. Validated: patch -p1 --dry-run
succeeds (both hunks), and a clean mps.ko build succeeds (rc=0 β
fix_build.log; mps.ko grew to 193880 bytes vs 193512 baseline, confirming the
patched TU compiled in). Supersedes any pre-verification proposal by covering
both unbounded paths.
Fix-validation status
not_testable for a live before/after (the driver path cannot run on the
guest). Evidence the fix is correct: (1) harness before/after shows the check
closes the primitive; (2) the fix compiles cleanly in-tree under -Werror.
Fix verification
not_testablecompile+harness validated
module build rc=0
Confirmed kernel references
β
Detail
Exploit chain
none
Evidence (decisive lines)
β
Verdict
REPRODUCED (harness). mps_mapping DeviceIndex no bounds vs mapping_table[max_devices] -> 64B OOB. mps in GENERIC, no SAS HBA. Twin of DF-1282.
No comments yet.