mpr _mapping_get_ir_maprange off-by-one: end_idx == max_devices -> OOB reads and two 32-byte OOB write sinks on IR paths (twin of DF-4086)
Summary
end_idx = start_idx + max_volumes - 1 unclamped; with firmware facts MaxTargets==0 + RESERVED_TARGETID_0 + low volume mapping, end_idx == max_devices while the inclusive IR walks read mapping_table[max_devices]. _mapping_get_free_ir_mt_idx can return the OOB index and mpr_mapping_ir_config_change_event writes a full firmware-controlled entry there; independently the _mapping_process_dpm_pg0 volume branch accepts DeviceIndex == end_idx and writes the OOB entry at attach time (a sink the mps harness didn't cover). Harness-proven on guest (facts MaxTargets=0/MaxVolumes=255: OOB read + free-idx 255 + event write fw WWID + DPM volume-branch second write). Clamp fix compile-validated. Not covered by DF-3919.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4093 Β· 13 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 2.1 KB | β raw | |
| VERDICT.md | β | 5.5 KB | β raw | |
| harness_df4093.c | β | 6.4 KB | view raw | |
| build.sh | β | 102 B | view raw | |
| run.sh | β | 97 B | view raw | |
| build.log | β | 16 B | view raw | |
| run.log | β | 787 B | view raw | |
| run.2.log | β | 787 B | view raw | |
| env.txt | β | 1.8 KB | view raw | |
| fix.diff | β | 666 B | view raw | |
| fix_build.log | β | 11.4 KB | view raw | |
| manifest.json | β | 1.5 KB | view raw | |
| verdict.json | β | 4.7 KB | view raw |
DF-4093 β mpr _mapping_get_ir_maprange off-by-one: end_idx == max_devices β OOB reads + two 32-byte OOB write sinks (mpr twin of DF-4086)
File: sys/dev/raid/mpr/mpr_mapping.c (:241-255 range; <= walks :387, :430, :484, :2261, :2708; event write :3082-3088; kmalloc :2140-2141; facts :2564-2565)
Build
cc -O2 -Wall -o harness_df4093 harness_df4093.c
(DragonFly base cc 8.3; any unprivileged user; runs entirely in userland)
Run
./harness_df4093
Expected output (decisive lines)
sizeof(dev_mapping_table) : 32 (expect 32 on amd64)
facts: MaxTargets=0 MaxVolumes=255 -> max_devices=255 (:2565)
valid mapping_table indices: 0..254 (kmalloc 8160 bytes :2140)
IR maprange: start=1 end=255 (end == max_devices => OFF-BY-ONE: `<=` walks hit index 255)
wwid walk :387 read physical_id at mapping_table[255] -> OOB READ
free-idx walk :484 returned 255 (== max_devices, OUT OF BOUNDS)
event write: mapping_table[255] = {wwid 0x57119418badb00b5, handle 0x0b1d, info 0x30000000}
guard[255].physical_id == fw WWID: 32-byte heap OOB WRITE at index max_devices
DPM volume branch :2261 accepted DeviceIndex 255 == end_idx -> second 32-byte OOB WRITE
RESULT: OFF-BY-ONE OOB READS + TWO 32-BYTE OOB WRITES PROVEN
Exit code 0 = primitive proven. run.log / run.2.log hold two identical
guest runs.
What this proves
Object-level proof of the off-by-one and both write sinks: with firmware
facts MaxTargets==0 + pg8 RESERVED_TARGETID_0 + low volume mapping,
end_idx = start_idx + max_volumes - 1 == max_devices; every inclusive
IR walk then touches mapping_table[max_devices] (OOB read),
_mapping_get_free_ir_mt_idx can return the OOB index, and
(a) mpr_mapping_ir_config_change_event writes a full firmware-controlled
entry there on a volume-add event, and (b) the DPM-pg0 volume branch writes
one there for a DeviceIndex == end_idx persistent entry.
Kernel-level status
Not reproducible on the audit guest: no SAS HBA on the PCI bus (env.txt), so
mpr_attach never runs. Same hardware gate as DF-4086 (mps) and
DF-3914/DF-3915 (mpr). Threat actor: malicious/buggy IOC firmware or a
PCI-passthrough/emulated HBA.
DF-4093 VERDICT
Status: not_reproduced (kernel level) β hardware-gated; primitive proven at object level on the audit guest (userland harness, verbatim range function, walks and both write sinks). Confidence: certain.
mpr twin of DF-4086 (mps). Filed separately because the mps pass-2 audit deliberately did not double-file sibling findings.
Source chain (line-by-line)
mpr_mapping.c:2564-2565max_volumes = facts->MaxVolumes(u8),max_devices = facts->MaxTargets + facts->MaxVolumes.mpr_mapping.c:2140-2141mapping_table = kmalloc(32 * max_devices)(sizeof verified 32 on amd64, mprvar.h:113-125).mpr_mapping.c:241-255_mapping_get_ir_maprange: withIRVolumeMappingFlags == LOW_VOLUME_MAPPING(:246-248)start_idx = 0, bumped to 1 whenMPI2_IOCPAGE8_FLAGS_RESERVED_TARGETID_0(:250-251);*end_idx = *start_idx + sc->max_volumes - 1(:254) β unclamped. With firmware factsMaxTargets == 0(βmax_devices == max_volumes),RESERVED_TARGETID_0and low volume mapping β all three firmware- controlled βstart_idx = 1andend_idx = max_volumes = max_devices: one entry PAST the last valid index.- Every IR consumer walks
map_idx <= end_idxand therefore touchesmapping_table[max_devices]: -_mapping_get_ir_mt_idx_from_wwid:387 OOB read (physical_id) -_mapping_get_ir_mt_idx_from_handle:430 OOB read (dev_handle) -_mapping_get_free_ir_mt_idx:484 OOB read (device_info/ missing_count) and :486 returns the OOB index when the adjacent memory lacksMPR_MAP_IN_USE-_mapping_process_dpm_pg0volume branch :2260-2271 acceptsdev_idx == end_idx(DPM pg0DeviceIndex, u16, fw-controlled) and writes a full entry atmapping_table[dev_idx]β unconditional 32-byte OOB write with fwPhysicalIdentifier-mpr_mapping_get_raid_tid:2708 OOB read (CAM-facing volume lookup) - Write sink A (event):
mpr_mapping_ir_config_change_event:3045-3088 β onMPI2_EVENT_IR_CHANGE_RC_ADDED/VOLUME_CREATEDfor a volume whose WWID is not yet mapped,map_idx = _mapping_get_free_ir_mt_idx(sc)(:3071) returnsmax_deviceswhen the in-range slots are busy; :3082-3088 writesphysical_id = wwid_table[i](fw volume WWID),id,dev_handle,device_infoβ full 32-byte fw-controlled entry one past the allocation. Write sink B (boot): the :2260-2271 DPM volume-branch write above β same index, reached at attach time from persistent mapping entries.
Note: _mapping_get_high_missing_mt_idx (:341-370) computes
&mapping_table[end_idx_ir + 1] == &mapping_table[max_devices + 1] in the
same configuration (:358) but its < loop then never dereferences it β
benign pointer arithmetic, noted for completeness.
Distinct from the existing mpr findings
None of DF-3914..DF-3919 touches _mapping_get_ir_maprange or the IR walks;
DF-3919 covers event element-count walks (frame-length mismatch), not the
IR index range.
Verification performed
- Guest
pciconf -l(env.txt): no SAS HBA βmpr_attachnever runs; kernel-level reproduction impossible (same gate as DF-3914/DF-3915/ DF-4086). harness_df4093.cbuilt and run twice as the unprivileged user on the guest (cc 8.3, zero warnings): verbatim range function (:241-255 low+reserved configuration), verbatim<=walks, verbatim event write (:3082-3088) and verbatim DPM volume branch (:2260-2271). Facts MaxTargets=0, MaxVolumes=255 β max_devices=255, start=1, end=255 == max_devices. Decisive results (run.log, stable):- wwid walk reads
physical_idatmapping_table[255]β OOB read; - free-idx walk returns 255 (== max_devices) β the OOB index escapes;
- event write lands the fw WWID
0x57119418BADB00B5at guard[255] β 32-byte heap OOB write, verified programmatically; - DPM volume branch accepts
DeviceIndex 255 == end_idxβ a second, unconditional 32-byte OOB write. - In-kernel equivalent: one 32-byte struct write just past an 8,160-byte kmalloc, contents firmware-chosen (volume WWID at offset 0, dev_handle, device_info). On the no-hardening audit guest this is classic adjacent- slab-object corruption once an HBA is present.
Fix validation
fix.diff(this pack): clamp*end_idxtosc->max_devices - 1at the end of_mapping_get_ir_maprangeβ same shape as the validated DF-4086 fix; fixes every<=consumer at once (:387, :430, :484, :2261, :2708).- Applied together with DF-4092's bound-skip to the guest
/usr/src;makeinsys/dev/raid/mprcompiled all sources with-Werrorand linked mpr.ko (BUILD_EXIT=0) βfix_build.log. - Runtime fix validation not testable (no mpr HBA on the guest).
/usr/srcrestored pristine after the build (md5 match, reverse-patch verified, no leftover artifacts).
Bottom line
Source-certain CWE-125/ CWE-787 off-by-one β OOB reads + two distinct 32-byte OOB write sinks, primitive proven at object level on the audit guest; kernel-level reproduction hardware-gated. Fix compiles clean with -Werror; recommended for upstream together with the mps twin DF-4086.
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 DF-4092's bound-skip; make in sys/dev/raid/mpr compiled all sources with -Werror and linked mpr.ko (BUILD_EXIT=0). Runtime fix validation not testable: the path requires an mpr(4) HBA delivering firmware pages, absent on this guest.
findings/poc/DF-4093/fix.diff, findings/poc/DF-4093/fix_build.log
Confirmed kernel references
- sys/dev/raid/mpr/mpr_mapping.c:241
- sys/dev/raid/mpr/mpr_mapping.c:248
- sys/dev/raid/mpr/mpr_mapping.c:250
- sys/dev/raid/mpr/mpr_mapping.c:254
- sys/dev/raid/mpr/mpr_mapping.c:387
- sys/dev/raid/mpr/mpr_mapping.c:430
- sys/dev/raid/mpr/mpr_mapping.c:484
- sys/dev/raid/mpr/mpr_mapping.c:486
- sys/dev/raid/mpr/mpr_mapping.c:2260
- sys/dev/raid/mpr/mpr_mapping.c:2261
- sys/dev/raid/mpr/mpr_mapping.c:2270
- sys/dev/raid/mpr/mpr_mapping.c:2708
- sys/dev/raid/mpr/mpr_mapping.c:3071
- sys/dev/raid/mpr/mpr_mapping.c:3082
- sys/dev/raid/mpr/mpr_mapping.c:3088
- sys/dev/raid/mpr/mpr_mapping.c:2140
- sys/dev/raid/mpr/mpr_mapping.c:2565
Detail
Exploit chain
Hardware-gated: malicious/buggy IOC facts (MaxTargets=0 + IR + reserved-targetid-0 + low volume mapping) -> volume-add IR config change event -> _mapping_get_free_ir_mt_idx returns max_devices (OOB) when in-range slots are busy -> 32-byte struct write one entry past mapping_table with fw WWID/handle -> adjacent slab-object corruption -> potential kernel code execution; alternatively the same OOB entry is written at boot via a DPM pg0 entry with DeviceIndex == end_idx. Stopped at primitive proof: no HBA in guest.
Evidence (decisive lines)
run.log/run.2.log (guest harness: end==max_devices off-by-one, OOB read at :387, free-idx walk returns 255, event write lands fw WWID at mapping_table[255], DPM volume branch accepts DeviceIndex 255), harness_df4093.c (verbatim range fn + both <= walks + both write sinks), env.txt, VERDICT.md, fix.diff + fix_build.log (-Werror module build)
PoC changes
No seed PoC (fresh twin filing). Harness replicates the mpr range function, the <= walks, the event write :3082-3088 (mpr writes no channel field - struct differs from mps) AND the DPM volume-branch write :2260-2271 (second sink, absent from the mps harness); built/run twice on the guest as an unprivileged user.
Verified recommended fix
Clamp end_idx to sc->max_devices - 1 in _mapping_get_ir_maprange so the inclusive IR walks cannot index past mapping_table.
Verdict
Source-certain off-by-one -> heap OOB reads + two 32-byte heap OOB write sinks (CWE-125/CWE-787), mpr twin of DF-4086, not covered by any existing mpr finding (DF-3919 is event element-count walks, not the IR index range). _mapping_get_ir_maprange (mpr_mapping.c:241-255) computes end_idx = start_idx + max_volumes - 1 (:254) unclamped; with firmware facts MaxTargets==0 (max_devices == max_volumes, :2565) plus pg8 RESERVED_TARGETID_0 (:250) and low volume mapping (:248) - all firmware-controlled - end_idx == max_devices. Every IR walk uses inclusive <= and reads mapping_table[max_devices], one 32-byte entry past the kmalloc(max_devices*32) (:2140): :387 (wwid), :430 (handle), :484 (free-idx), :2708 (get_raid_tid, CAM-facing). _mapping_get_free_ir_mt_idx :486 can RETURN the OOB index, and mpr_mapping_ir_config_change_event :3082-3088 then writes a full firmware-controlled entry there (volume WWID, dev_handle, device_info); independently, the _mapping_process_dpm_pg0 volume branch :2260-2271 accepts a DPM DeviceIndex == end_idx and writes the OOB entry at attach time. Harness proves the range math, the OOB reads, the returned OOB index and both 32-byte OOB writes with the fw WWID (run.log). Not reproduced at kernel level: no SAS HBA on the QEMU guest (env.txt), mpr never attaches - same hardware gate as mps twin DF-4086.
No comments yet.