Unbounded firmware NumSlots heap OOB write in the direct-attached enclosure reservation path (_mapping_get_dev_info/_mapping_get_pcie_dev_info)
Summary
The enclosure-table entry's num_slots comes from the Enclosure Status Change event's NumSlots, a firmware u16 whose only validation in the whole file is !=0. On the DIRECT-ATTACHED topo path start_index is pinned to num_rsvd_entries with no relation to num_slots, and the reservation loop then initializes num_slots consecutive mapping_table entries with no check against max_devices. The expander branch is bounded because find_enc_map_space only reports space within the table; the DA branch bypasses it entirely. A malicious/compromised enclosure (SES data surfaced by IOC firmware), malicious IOC firmware, or NVRAM corruption reports NumSlots up to 65535 vs a mapping_table sized max_devices entries (~8-64KB) -> up to ~2MB linear heap overflow at a 32-byte stride with the fully firmware-controlled 64-bit enclosure SAS address. VERIFIED on the guest: object-level harness with canaries - NumSlots=2048 vs max_devices=264 smashed 57,120 bytes past the 8,448-byte allocation. Fix compile-validated -Werror into a full kernel.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3914 Β· 13 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 3.5 KB | β raw | |
| VERDICT.md | β | 4.6 KB | β raw | |
| harness_df3914.c | β | 7.2 KB | view raw | |
| build.sh | β | 118 B | view raw | |
| run.sh | β | 113 B | view raw | |
| build.log | β | 41 B | view raw | |
| run.log | β | 556 B | view raw | |
| run.2.log | β | 1.1 KB | view raw | |
| env.txt | β | 2.7 KB | view raw | |
| fix.diff | β | 2.4 KB | view raw | |
| fix_build.log | β | 11.1 KB | view raw | |
| manifest.json | β | 1.3 KB | view raw | |
| verdict.json | β | 4.4 KB | view raw |
DF-3914 β Unbounded firmware NumSlots heap OOB write in the direct-attached enclosure reservation path
File: sys/dev/raid/mpr/mpr_mapping.c (SAS: :1241-1243 + :1292-1299; PCIe: :1392-1394 + :1443-1450)
Class: CWE-787 Out-of-bounds Write (heap)
Severity: High β pass 2 (GLM 5.3)
Not a duplicate of DF-1282 (DPM-pg0 DeviceIndex write at :2327) or DF-1283
(slot - start_slot underflow at :1637): different entry path (live topology
event, not DPM ingestion) and different missing bound (table size vs NumSlots).
The bug (source-confirmed)
- An Enclosure Status Change event creates an enclosure-table entry with
et_entry->num_slots = le16toh(event_data->NumSlots)(mpr_mapping.c:2843). The only validation ofNumSlotsin the entire file is!= 0(:2760-2765). It is a firmware/u16 value (0..65535). - When devices for that enclosure appear,
_mapping_get_dev_info()(SAS,:1241) /_mapping_get_pcie_dev_info()(PCIe,:1392) map the enclosure: * expander path (exp_handle != 0):_mapping_find_enc_map_space()only returns astart_idxafter findingnum_slotscontiguous free entries belowend_of_table <= max_devices(:979-993) β bounded. * direct-attached path (exp_handle == 0/switch_dev_handle == 0):c map_idx = sc->num_rsvd_entries; /* :1242 */ et_entry->start_index = map_idx; /* :1243 β NO num_slots bound */β¦ and then the reservation loop (:1292-1299, PCIe:1443-1450) initializeset_entry->num_slotsconsecutive entries starting atnum_rsvd_entrieswith no check thatnum_rsvd_entries + num_slots <= sc->max_devices. sc->mapping_tableiskmalloc(max_devices * 32, M_MPR)(:2140-2141),max_devices = facts->MaxTargets + MaxVolumes(u16,:2565, typically ~256-2048).
An enclosure whose reported NumSlots exceeds the free table space (lying or
compromised enclosure/backplane SES data surfaced by IOC firmware, malicious
IOC firmware, or NVRAM corruption) therefore produces a heap OOB write of up
to (0xFFFF - max_devices + num_rsvd) * 32 bytes (~2 MB) past the
mapping_table allocation, writing at a 32-byte stride:
physical_id = enclosure SAS address (fully firmware-controlled 64 bits),
device_info = MPR_DEV_RESERVED, phy_bits = firmware u32,
missing_count = 0.
Reproduce
Hardware-gated: the audit QEMU guest has no LSI/Avago SAS3 HBA (pciconf -l
shows virtio-only β env.txt), so mpr_attach never runs there. The pack
contains an object-level harness that replicates the exact structs and the
exact unguarded loops and proves the write primitive (canary region after the
allocation receives the attacker value at the 32-byte stride):
build.sh # cc -O2 -Wall -o harness_df3914 harness_df3914.c
run.sh # ./harness_df3914
Expected (observed on the guest, run.log):
enclosure NumSlots (fw) : 2048 reservation loop wrote : [1, 2049) table end : 264 -> overflow of 1785 entries (57120 bytes) guard[+0..] physical_id == 0x5141524153534141 at 1785/4096 entries (stride 32) RESULT: OOB WRITE PRIMITIVE PROVEN (canary smash with attacker value, 32-byte stride)
Fix
fix.diff bounds num_slots on both direct-attached paths and reuses the
existing "no space found" handling (mark the event's add entries processed and
stop). Compile-validated: applied to the guest /usr/src copy and compiled
with -Werror in both the static-kernel and KLD_MODULE passes of
make nativekernel (fix_build.log).
DF-3914 β VERDICT
Status: not_reproduced (kernel path hardware-gated on this guest) / OOB-write primitive proven at object level. Confidence: certain (source-level; the missing bound is unconditional).
What was verified and how
-
Root cause, line by line (
sys/dev/raid/mpr/mpr_mapping.c): -:2843et_entry->num_slots = le16toh(event_data->NumSlots);β sole validation of the firmware-supplied slot count is!= 0at:2760. -:1241-1243(SAS) and:1392-1394(PCIe): direct-attached branch setset_entry->start_index = sc->num_rsvd_entrieswith no relation established betweennum_slotsand the table size. -:1292-1299(SAS) /:1443-1450(PCIe): reservation loop writesnum_slotsentries frommap_idxβ the unguarded sink. -:2140-2141allocation ismax_devices * sizeof(dev_mapping_table)(32 B/entry);:2565max_devices = MaxTargets + MaxVolumes(u16). - Bounded contrast: the expander branch goes through_mapping_find_enc_map_space():979-993, which only reports space found withinend_of_table <= max_devices; the DA branch bypasses it. -grep -n num_slotsover the whole file confirms no clamp between:2843and the two loops. -
Distinctness from the known findings (pass-2 mandate): - DF-1282 =
_mapping_process_dpm_pg0mapping_table[dev_idx]write from DPM page0 ingestion (:2327/:2358), including thedev_idx+num_slotsloop at:2328. DF-3914 is the event-driven reservation path β the overflow source isNumSlotsfrom an Enclosure Status Change event, not a DPMDeviceIndex, and it fires on a live topo event with a clean DPM. - DF-1283 =start_index + slot - start_slotunsigned underflow in_mapping_add_new_device/_mapping_add_new_pcie_device(:1637/:1894). DF-3914 is in_mapping_get_dev_info/_mapping_get_pcie_dev_info, no slot subtraction involved. -
Hardware gate (why the kernel path is not runtime-reachable here): guest
pciconf -l(env.txt) lists only i440FX/PIIX/virtio-vga/virtio-net/ virtio-blk devices β novendor 0x1000SAS controller, sompr_attach()never executes. Same gate already proven for DF-1282/DF-3800. -
Object-level proof (guest,
run.log+run.2.log): harness replicatesstruct dev_mapping_table(32 B,mprvar.h:113), allocatesmax_devices=264entries plus a 4096-entry canary guard, then runs the verbatim:1292-1299loop withNumSlots=2048,num_rsvd=1: 1785 entries (57,120 bytes) past the 8,448-byte allocation receivephysical_id = 0x5141524153534141at stride 32 β i.e. a firmware-controlled 8-byte value per 32-byte slot, exactly what the kernel loop writes past its slab object. Second run reproduced identically.
Exploit chain (why this matters beyond a panic)
The written physical_id is the enclosure's 64-bit SAS address β fully chosen
by the (malicious) firmware/enclosure. A ~2 MB linear overflow with a
controlled qword every 32 bytes across adjacent kmalloc zones is a classic
slab-corruption primitive: overflow into a neighboring object with function
pointer / list pointers (e.g. neighboring driver structures) β control flow.
Not developed further: hardware-gated (no HBA to carry the malicious event),
same trust boundary as DF-1282/DF-1283.
Fix validation
fix.diff adds the missing bound on both DA paths, mirroring the existing
"no space" handling (mark add entries processed; stop mapping this event).
- Applied cleanly (patch -p1) to the guest /usr/src copy together with
DF-3915's diff (git apply --check passes on the pristine tree too).
- make nativekernel KERNCONF=X86_64_GENERIC: mpr_mapping.c recompiled
with -Werror in both the static-kernel and KLD_MODULE passes β no
diagnostics (fix_build.log).
- fix_status: not_testable for runtime validation: the fixed path needs an
mpr(4) controller emitting the malicious enclosure event; no such hardware
on the guest.
Bottom line
Real, source-certain, firmware-fed heap OOB write (CWE-787) with a controlled 8-byte-in-32 stride, distinct from DF-1282/DF-1283; primitive proven at object level on the audit guest; kernel path hardware-gated; fix compiles clean.
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_testablefix.diff applied to the guest /usr/src copy (together with DF-3915's diff, git apply --check clean on the pristine tree); make nativekernel KERNCONF=X86_64_GENERIC rebuilt mpr_mapping.c with -Werror in both static-kernel and KLD_MODULE passes and completed the kernel+module link with zero diagnostics (fix_build.log; full log SHA256 55641f6d20f32d6795026dd86d27cd571a9186beb0333830050a386f1fb57368). Runtime fix validation not testable: exercising the path requires an mpr(4) HBA delivering the malicious enclosure event, absent on this guest.
findings/poc/DF-3914/fix.diff, findings/poc/DF-3914/fix_build.log
Confirmed kernel references
- sys/dev/raid/mpr/mpr_mapping.c:2843
- sys/dev/raid/mpr/mpr_mapping.c:2760
- sys/dev/raid/mpr/mpr_mapping.c:1241
- sys/dev/raid/mpr/mpr_mapping.c:1292
- sys/dev/raid/mpr/mpr_mapping.c:1392
- sys/dev/raid/mpr/mpr_mapping.c:1443
- sys/dev/raid/mpr/mpr_mapping.c:979
- sys/dev/raid/mpr/mpr_mapping.c:2140
- sys/dev/raid/mpr/mpr_mapping.c:2565
Detail
Exploit chain
Hardware-gated chain (not executable on this guest): malicious/compromised enclosure SES data or IOC firmware -> Enclosure Status Change event with NumSlots > free space -> SAS/PCIe Topology Change event (direct-attached) -> _mapping_get_dev_info/_mapping_get_pcie_dev_info reservation loop -> linear heap overflow past mapping_table with fw-controlled qword per 32 bytes into adjacent kmalloc zones -> slab metadata / neighboring object corruption -> kernel code execution. Stopped at primitive proof: no HBA.
Evidence (decisive lines)
run.log/run.2.log (guest harness output: 57,120-byte canary smash with 0x5141524153534141 at stride 32), harness_df3914.c (verbatim loop replication), env.txt (hardware gate), VERDICT.md (line-by-line source chain), fix.diff + fix_build.log (patched kernel compiled -Werror clean, completed)
PoC changes
No seed PoC existed (pass-2 finding). Harness authored against the exact struct layout (mprvar.h:113) with the verbatim :1292-1299 loop; built and run on the audit guest with cc 8.3.
Verified recommended fix
Bound et_entry->num_slots by (sc->max_devices - num_rsvd_entries) on both direct-attached paths in _mapping_get_dev_info/_mapping_get_pcie_dev_info and reject the enclosure using the existing no-space handling.
Verdict
Source-certain heap OOB write (CWE-787), pass 2. The firmware-supplied enclosure NumSlots (mpr_mapping.c:2843, only !=0-checked at :2760) is never bounded against the mapping-table size on the DIRECT-ATTACHED reservation path of _mapping_get_dev_info (:1241-1243 -> unbounded loop :1292-1299) and _mapping_get_pcie_dev_info (:1392-1394 -> :1443-1450); the expander path is bounded via _mapping_find_enc_map_space (:979-993), the DA path bypasses it. mapping_table is kmalloc(max_devices*32) (:2140), so an enclosure reporting more slots than free table space produces a linear overflow of up to ~2MB writing the fully firmware-controlled 64-bit enclosure SAS address at a 32-byte stride. Distinct from DF-1282 (DPM-pg0 DeviceIndex write) and DF-1283 (slot-start_slot underflow). Not reproduced at kernel level on the QEMU guest: no LSI/Avago SAS3 HBA present (pciconf in env.txt - virtio-only PCI bus), so mpr_attach never runs; same hardware gate as DF-1282/DF-3800. Primitive proven at object level on the guest: harness replicates the exact structs and verbatim loop; with NumSlots=2048 vs max_devices=264 it smashes 1,785 guard entries (57,120 bytes) past the 8,448-byte allocation with the attacker value at stride 32 (run.log, stable across reruns).
No comments yet.