Wild OOB read (and NVRAM garbage write) in _mapping_commit_enc_entry via unvalidated enclosure start_index ingested from DPM pg0 (num_slots==0 bypasses DF-1282)
Summary
_mapping_process_dpm_pg0 stores the unchecked DPM DeviceIndex into et_entry->start_index alongside a valid dpm_entry_num. When the entry's 7-bit slot field is zero the reservation loop executes zero iterations, so DF-1282's OOB write never fires and the corrupt entry survives ingestion quietly. check_devices then passes it to commit_enc_entry, which computes &mapping_table[start_index] and reads mt_entry->id - a read up to 2,096,640 bytes past the allocation. VERIFIED on the guest: (a) the reservation loop runs 0 iterations for the crafted entry (DF-1282 bypass demonstrated), (b) the indexing resolves +2MB past an 8,448-byte allocation (guard-page SIGSEGV variant). Corrupt/malicious NVRAM DPM entry: wild read (panic/DoS) or silent garbage persisted into the controller's NVRAM DeviceIndex.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3915 Β· 12 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 3.4 KB | β raw | |
| VERDICT.md | β | 4.3 KB | β raw | |
| harness_df3915.c | β | 6.5 KB | view raw | |
| build.sh | β | 118 B | view raw | |
| run.sh | β | 113 B | view raw | |
| build.log | β | 71 B | view raw | |
| run.log | β | 461 B | view raw | |
| env.txt | β | 2.7 KB | view raw | |
| fix.diff | β | 1.1 KB | view raw | |
| fix_build.log | β | 11.1 KB | view raw | |
| manifest.json | β | 1.3 KB | view raw | |
| verdict.json | β | 4.2 KB | view raw |
DF-3915 β Wild OOB read (and NVRAM garbage write) in _mapping_commit_enc_entry via unvalidated enclosure start_index
File: sys/dev/raid/mpr/mpr_mapping.c:129-130 (sink); ingestion :2310-2311; trigger :2481-2493
Class: CWE-125 Out-of-bounds Read (wild pointer)
Severity: Medium β pass 2 (GLM 5.3)
Not a duplicate of DF-1282: same DPM DeviceIndex source, but a
different sink reached through a state DF-1282's own write cannot produce
(num_slots == 0 bypasses the reservation loop entirely).
The bug (source-confirmed)
_mapping_process_dpm_pg0()ingests each DPM enclosure entry with: -dev_idx = le16toh(dpm_entry->DeviceIndex)(:2251) β unchecked u16; -num_slots = MappingInformation & 0x07F0 >> 4(:2293-2295) β can be 0; -et_entry->start_index = dev_idx(:2310) andet_entry->dpm_entry_num = entry_num(:2311) β stored unvalidated; - the reservation loop:2328-2347(for (map_idx = dev_idx; map_idx < dev_idx + num_slots; β¦)) executes zero iterations whennum_slots == 0, so DF-1282's OOB write in this same function never fires β the corrupt entry survives ingestion quietly (init_completestays 0).mpr_mapping_check_devices()(:2481-2493) later walks the enclosure table; for any!init_completeentry it bumpsmissing_countand, whendpm_entry_num != MPR_DPM_BAD_IDX, calls_mapping_commit_enc_entry(sc, et_entry)(:2491)._mapping_commit_enc_entry()dereferencesmt_entry = &sc->mapping_table[et_entry->start_index](:129) and readsmt_entry->idintodpm_entry->DeviceIndex(:130) β withstart_indexup to0xFFF0+ this is a read up to65535 * 32 = 2,097,120bytes past themax_devices * 32-bytemapping_tableallocation (:2140).
Consequences: wild read β panic on unmapped page (local DoS from the HBA's
perspective), or silent garbage persisted into the controller's NVRAM DPM
entry (DeviceIndex), which then feeds _mapping_process_dpm_pg0 on the next
boot. Preconditions: Enc/Slot mapping mode, a DPM enclosure entry with slot
bits == 0 and out-of-range DeviceIndex (malicious/corrupt NVRAM or IOC
firmware), and β₯1 other enclosure that completed init (:2438-2446 gates the
walk).
Reproduce
Hardware-gated (no SAS3 HBA on the audit guest β env.txt). The harness
replicates the exact structs/flow: proves (a) the num_slots == 0 entry
executes the :2328 loop zero times (DF-1282 bypassed) and (b) the :129
indexing lands 0xFFF0 * 32 = 2,096,640 bytes past an 8,448-byte allocation
(guard-page variant included).
build.sh # cc -O2 -Wall -o harness_df3915 harness_df3915.c
run.sh # ./harness_df3915
Expected (observed on the guest, run.log):
DPM entry: dev_idx=0xfff0 num_slots=0 dpm_entry_num=3 reservation loop (:2328) iterations: 0 (DF-1282 write BYPASSED) mapping_table size : 8448 bytes (max_devices=264) start_index*32 : 2096640 bytes past base read mt_entry->id : 0x5f36 (silent garbage; kernel feeds it to NVRAM) RESULT: WILD READ PRIMITIVE PROVEN - index 0xfff0 * 32 = 2096640 bytes past a 8448-byte allocation
Fix
fix.diff validates et_entry->start_index (BAD or >= max_devices) at
the top of _mapping_commit_enc_entry and refuses the DPM commit otherwise.
Compile-validated with -Werror on the guest (fix_build.log).
DF-3915 β VERDICT
Status: not_reproduced (kernel path hardware-gated on this guest) / wild-read primitive proven at object level. Confidence: certain (source-level; the sink has no bound).
Verification narrative
- Ingestion path (
sys/dev/raid/mpr/mpr_mapping.c): -:2251dev_idx = le16toh(dpm_entry->DeviceIndex);(u16 from the controller's persistent DPM page read atmpr_mapping_initialize:2614). -:2293-2295num_slots= 7-bit slot field (0x07F0 >> 4) β zero is a legal, reachable value (an entry that only ever carried missing-count bits). -:2310-2311start_index = dev_idx,dpm_entry_num = entry_numβ both stored without any table-size validation. -:2328-2347reservation loop: withnum_slots == 0the body never runs, so DF-1282's OOB write is not triggered β this is what makes the corrupt state observable later instead of panicking at boot. (Withnum_slots >= 1anddev_idx >= max_devices, DF-1282 fires first and that state is its finding;num_slots == 0is the new, distinct window.) - Trigger path:
mpr_mapping_check_devices():2481-2493β the only caller of_mapping_commit_enc_entry()in the file (verified by grep). Entry requirements met by the ingested state:init_complete == 0(never set in_mapping_process_dpm_pg0),dpm_entry_num != BAD(:2489). Walk-level precondition: at least one enclosure entry withinit_complete == 1sostop_device_checksis set (:2438-2446) β any enclosure that arrived via an Enclosure Status Change event satisfies it (:2847).MPR_MISSING_CHECK_DELAYschedules the callout after discovery. - Sink:
:129mt_entry = &sc->mapping_table[et_entry->start_index];:130readsmt_entry->idβdpm_entry->DeviceIndex(persisted via the DPM write path:142-143). Withstart_indexup to0xFFFFthe read is up to ~2 MB past a ~8 KB allocation: wild pointer. - Hardware gate: guest
pciconf -l(env.txt) β virtio-only PCI bus, no vendor-0x1000 SAS controller;mpr_attach()never runs. Same gate as DF-1282/DF-3800 (both previously proven). - Object-level proof (guest,
run.log, stable across reruns): - the:2328loop runs 0 iterations for the crafted entry β the DF-1282 bypass is demonstrated, not assumed; - the:129indexing resolves to +2,096,640 bytes past the 8,448-byte table; the harness read returns silent garbage (0x5f36) β in the kernel this either faults (panic) or persists garbage to NVRAM. The harness also installs aPROT_NONEguard-page variant (SIGSEGV handler) to show the access is genuinely wild when the offset lands on an unmapped page.
Why Medium and not High
Read-only primitive; the corrupted value's destination is the controller's
NVRAM DeviceIndex (fed back into driver state), not a userspace info leak.
Primary impact: kernel panic (DoS) from malformed persistent mapping state β
same threat actor as DF-1282 (malicious IOC firmware / NVRAM corruption /
malicious enclosure persisting mapping entries), but strictly weaker than the
write primitives already filed for this file.
Fix validation
fix.diff: reject start_index == MPR_MAPTABLE_BAD_IDX ||
start_index >= sc->max_devices at the top of _mapping_commit_enc_entry
(before the :129 deref), log, return -1.
- Applied to the guest /usr/src copy together with DF-3914's diff (both
git apply --check clean on the pristine tree).
- make nativekernel: mpr_mapping.c compiled with -Werror in both
passes (fix_build.log).
- fix_status: not_testable for runtime: exercising the fixed path needs the
HBA to serve a malformed DPM page.
Bottom line
Source-certain wild OOB read (CWE-125) reachable through a DPM-ingested enclosure entry whose slot count is zero β a state DF-1282 cannot cover because its own write loop never runs for it. Primitive proven at object level; kernel path hardware-gated; guard-clause 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 (guard clause at the top of _mapping_commit_enc_entry) applied to the guest /usr/src copy together with DF-3914's diff; make nativekernel rebuilt mpr_mapping.c with -Werror in both passes and completed the kernel+module link with zero diagnostics (fix_build.log; full log SHA256 55641f6d20f32d6795026dd86d27cd571a9186beb0333830050a386f1fb57368). Runtime fix validation not testable without an mpr(4) HBA serving a malformed DPM page.
findings/poc/DF-3915/fix.diff, findings/poc/DF-3915/fix_build.log
Confirmed kernel references
Detail
Exploit chain
Hardware-gated (not executable on this guest): corrupt/malicious DPM pg0 enclosure entry (slot bits 0, DeviceIndex 0xFFF0) -> quiet ingestion via _mapping_process_dpm_pg0 -> device-check callout after MPR_MISSING_CHECK_DELAY -> _mapping_commit_enc_entry wild read -> panic (DoS) or garbage DeviceIndex persisted to controller NVRAM. Read-only primitive; escalation ceiling is DoS + persistent mapping corruption.
Evidence (decisive lines)
run.log (guest harness: 0 reservation-loop iterations + read at +2,096,640 bytes), harness_df3915.c (verbatim struct/index replication incl. PROT_NONE guard variant), env.txt (hardware gate), VERDICT.md (line-by-line chain incl. single-caller grep proof), fix.diff + fix_build.log (patched kernel compiled -Werror clean, completed)
PoC changes
No seed PoC existed (pass-2 finding). Harness authored against exact struct layout; built and run on the audit guest with cc 8.3.
Verified recommended fix
Validate et_entry->start_index (!= MPR_MAPTABLE_BAD_IDX and < sc->max_devices) at the top of _mapping_commit_enc_entry before indexing sc->mapping_table.
Verdict
Source-certain wild OOB read (CWE-125), pass 2. _mapping_process_dpm_pg0 stores the unchecked DPM DeviceIndex into et_entry->start_index (mpr_mapping.c:2310) together with a valid dpm_entry_num (:2311); when the entry's slot count is zero (:2293-2295 - legal value of the 7-bit slot field) the reservation loop :2328-2347 runs zero iterations, so DF-1282's OOB write never fires and the corrupt entry survives ingestion quietly (init_complete stays 0). mpr_mapping_check_devices (:2481-2493, the only caller) then passes it to _mapping_commit_enc_entry, which dereferences &sc->mapping_table[et_entry->start_index] (:129) and reads mt_entry->id (:130) - a read up to 6553532 = 2,096,640 bytes past the max_devices32-byte mapping_table allocation (:2140): kernel panic on unmapped page, or silent garbage persisted into the controller's NVRAM DeviceIndex which re-enters driver state on next boot. Preconditions: Enc/Slot mapping mode, DPM enclosure entry with slot bits==0 and out-of-range DeviceIndex (malicious IOC firmware / NVRAM corruption), and >=1 other enclosure with init_complete (:2438-2446). Not reproduced at kernel level on the QEMU guest: no SAS3 HBA (env.txt); same gate as DF-1282/DF-3800. Primitive proven at object level on the guest: harness demonstrates the zero-iteration reservation loop (DF-1282 bypass) and the :129 indexing resolving +2,096,640 bytes past an 8,448-byte allocation (run.log).
No comments yet.