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

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-1374 Β· 12 files
FileTypeDescriptionSize
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
README.md readme summary + reproduce
↓ download 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).

VERDICT.md verdict full mechanism + reachability + fix
↓ download raw

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.

  1. Attacker-controlled source value β€” mps_mapping.c:1495: c dev_idx = le16toh(dpm_entry->DeviceIndex); /* u32, firmware-controlled */ dev_idx is declared u32 (:1465) and read directly from the controller's DPM page, whose contents are written by HBA firmware (influenced by attached devices) and/or NVRAM.

  2. Allocation size β€” mapping_table is kmalloc(sizeof(dev_mapping_table) * sc->max_devices, ...); max_devices = facts->MaxTargets + max_volumes, a uint16_t (mpsvar.h); MaxTargets is U16. Typical ~264.

  3. 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 validates dev_idx.

  4. UNBOUNDED sinks β€” mps_mapping.c:1544 (Enc/Slot) does mt_entry = &sc->mapping_table[dev_idx]; and :1545 writes num_slots consecutive entries in a loop (num_slots also firmware-derived); :1567 (Device Persistence) does map_idx = dev_idx; mt_entry = &sc->mapping_table[map_idx]; β€” both with no check that dev_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_slots consecutive 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_table kmalloc 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_testable

compile+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.