Off-by-one in PhyNum target-ID fallback allows heap OOB write past sassc->targets[]
- File:
sys/dev/raid/mpr/mpr_sas_lsi.c - Lines: 885β897 (check), 908 (write)
- Severity: Medium
- CVSS:
CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:N/I:H/A:H - CWE: CWE-787 Out-of-bounds Write
- Confidence: likely
Summary
In mprsas_add_device, the PhyNum fallback path checks
(id = config_page.PhyNum) > sassc->maxtargets using strictly-greater-than,
allowing id == maxtargets to pass. Since sassc->targets is allocated with
exactly maxtargets entries (indices 0..maxtargets-1, mpr_sas.c:761),
targ = &sassc->targets[id] at line 908 writes one struct mprsas_target past
the end of the heap allocation. A malicious HBA that reports a small
MaxTargets in IOC facts and then sends a SAS device page0 with
PhyNum == maxtargets triggers a heap OOB write.
Root cause
At mpr_sas_lsi.c:885-897:
id = MPR_MAP_BAD_ID;
if (sc->use_phynum != -1)
id = mpr_mapping_get_tid(...);
if (id == MPR_MAP_BAD_ID) {
if ((sc->use_phynum == 0) ||
((id = config_page.PhyNum) > sassc->maxtargets)) { /* <-- BUG: should be >= */
...error...
}
}
The comparison at line 890 is > (strictly greater), so PhyNum == maxtargets
passes the check and id is set to maxtargets.
sassc->targets is kmalloc'd as sizeof(struct mprsas_target) * maxtargets
at mpr_sas.c:761, so valid indices are 0..maxtargets-1.
At line 908: targ = &sassc->targets[id] with id == maxtargets is one past
the end. Lines 925β957 then write ~20 fields into targ (devinfo,
devname, encl_handle, encl_slot, handle, parent_handle, sasaddr,
tid, linkrate, flags, etc.) β a full struct mprsas_target overwrite
into adjacent heap.
config_page.PhyNum is U8 (0-255, mpi2_sas.h SAS device page0).
maxtargets = MaxTargets + MaxVolumes (mpr_sas.c:760). If the HBA reports
MaxTargets+MaxVolumes β€ 255 (e.g., MaxTargets=128, MaxVolumes=8 β
maxtargets=136), a PhyNum of 136 triggers the OOB.
use_phynum defaults to 1 (mpr.c:1678), so this fallback path is active by
default.
Threat
Attacker is a malicious SAS HBA that controls both the IOC facts (MaxTargets,
reported at attach time, mpr.c LSI facts read) and the SAS device page0
content (PhyNum, returned by mpr_config_get_sas_device_pg0).
The attacker sets MaxTargets+MaxVolumes to a value β€ 255, triggers a
topology-change event (device add) with a handle whose page0 has
PhyNum == maxtargets and a SAS address that fails mapping (so the PhyNum
fallback is taken).
Result: a heap OOB write of one full struct mprsas_target (~200 bytes) past
the targets[] allocation. Impact: heap corruption of whatever follows
targets[] in kernel memory β could overwrite function pointers, refcounts, or
free-list metadata, potentially leading to code execution (I:H, A:H).
Physical access or firmware compromise required; attack complexity is higher because two firmware-controlled values must be coordinated and mapping must fail.
Exploit / PoC
Using a malicious SAS3 HBA (FPGA or custom QEMU device model):
- During attach, report IOC facts with
MaxTargets=128,MaxVolumes=8βmaxtargets=136,targets[]has 136 entries (indices 0-135). - Post a
SAS_TOPOLOGY_CHANGE_LISTevent with a PHY entry havingPhyStatus=MPI2_EVENT_SAS_TOPO_RC_TARG_ADDEDand anAttachedDevHandle(e.g.,0x0012). - When the driver calls
mprsas_add_device(sc, 0x0012, ...), respond to thempr_config_get_sas_device_pg0request with a page0 whoseSASAddressis 0 (or any address thatmpr_mapping_get_tidrejects β returnsMPR_MAP_BAD_ID), andPhyNum=136. - The check
136 > 136is false, soid=136. Line 908:targ = &sassc->targets[136]β one past the 136-element array. - Lines 925-957 write ~20 fields into the out-of-bounds slot, corrupting adjacent heap.
Expected result: heap corruption; with KASAN, an OOB-write report; without KASAN, potential panic on subsequent use of corrupted data or exploitable heap corruption.
Recommended fix
Change the comparison from strictly-greater-than to greater-than-or-equal:
--- a/sys/dev/raid/mpr/mpr_sas_lsi.c
+++ b/sys/dev/raid/mpr/mpr_sas_lsi.c
@@ -887,7 +887,7 @@
if (id == MPR_MAP_BAD_ID) {
if ((sc->use_phynum == 0) ||
- ((id = config_page.PhyNum) > sassc->maxtargets)) {
+ ((id = config_page.PhyNum) >= sassc->maxtargets)) {
mpr_dprint(sc, MPR_INFO, "failure at %s:%d/%s()! "
"Could not get ID for device with handle 0x%04x\n",
__FILE__, __LINE__, __func__, handle);
Related findings
- DF-1473/1474 (sibling): event handler OOB family in same file.
- DF-1282/1283 (sibling, mpr_mapping): DPM DeviceIndex OOB.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-1475 Β· 10 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | readme | human-readable summary | 1.6 KB | β raw |
| VERDICT.md | verdict | full source-level analysis + fix-validation result | 2.6 KB | β raw |
| fix.diff | suggested-fix | git-apply-able minimal fix; compiles -Werror clean | 566 B | view raw |
| build.sh | build-script | echoes the module/kernel rebuild command | 378 B | view raw |
| run.sh | run-script | no live trigger on this guest | 296 B | view raw |
| env.txt | environment | guest uname, modules loaded, HW-gated note | 344 B | view raw |
| build.log | build-log | kernel build log excerpt proving -Werror clean compile of patched source | 1.4 KB | view raw |
| fix_apply.log | apply-log | patch --dry-run output proving fix.diff applies cleanly on with-src | 308 B | view 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 |
PoC DF-1475: mprsas_add_device PhyNum off-by-one OOB write of targets[]
Class: heap OOB write (off-by-one)
Cited site: sys/dev/raid/mpr/mpr_sas_lsi.c:885-897, 908
Reproduction status
HW/module gated β cannot be live-triggered on the audit QEMU guest.
No β mpr(4) HW-gated as above. Trigger is a SAS device whose config_page.PhyNum == sassc->maxtargets.
The bug is confirmed at the source level by tracing the cited path:line in
sys/dev/raid/mpr/mpr_sas_lsi.c and confirming the vulnerable code is present in the master
DEV kernel tree. The fix.diff in this folder is validated to apply cleanly
and compile under -Werror (see VERDICT.md).
Mechanism
Line 890 (id = config_page.PhyNum) > sassc->maxtargets uses strict greater-than, so PhyNum == maxtargets passes. targets[] has indices 0..maxtargets-1 (allocated sizeof(mprsas_target)*maxtargets at mpr_sas.c:761). Line 908 targ = &sassc->targets[id]; with id==maxtargets writes one struct mprsas_target (~200 bytes) past the end of targets[].
Realistic impact ceiling
corruption (DoS, latent privesc)
Fix
Change > sassc->maxtargets to >= sassc->maxtargets.
See fix.diff for the git-apply-able patch.
How to validate the fix
# 1. Apply fix.diff against the in-guest source: scp -F dfbsd-qemu/config fix.diff dfbsd:/root/DF-1475.diff ssh -F dfbsd-qemu/config dfbsd 'cd /usr/src && patch -p1 < /root/DF-1475.diff' # 2. Rebuild the affected module (preferred) or a single-fix kernel: ssh -F dfbsd-qemu/config dfbsd 'cd /usr/src/sys/sys/dev/raid/mpr && make' # 3. The compile must succeed with -Werror (it does β see build.log).
VERDICT β DF-1475: mprsas_add_device PhyNum off-by-one OOB write of targets[]
Verdict
INCONCLUSIVE (HW/module gated) β source-level confirmed, fix validated.
The bug is real and present in master DEV source at sys/dev/raid/mpr/mpr_sas_lsi.c:885-897, 908,
but the affected driver attaches only to hardware not present in the audit QEMU
guest, so it cannot be live-triggered here. The fix.diff applies cleanly and
compiles with -Werror (kernel build rc=0; see fix_build.log).
Mechanism (cited path β primitive β effect)
Line 890 (id = config_page.PhyNum) > sassc->maxtargets uses strict greater-than, so PhyNum == maxtargets passes. targets[] has indices 0..maxtargets-1 (allocated sizeof(mprsas_target)*maxtargets at mpr_sas.c:761). Line 908 targ = &sassc->targets[id]; with id==maxtargets writes one struct mprsas_target (~200 bytes) past the end of targets[].
Reachability on this guest
No β mpr(4) HW-gated as above. Trigger is a SAS device whose config_page.PhyNum == sassc->maxtargets.
Phase 6 β escalation potential
This is a heap OOB write (off-by-one) primitive. On real hardware it could be triggered by an unprivileged user (via crafted packets for the NIC findings, via DRM ioctls for the GPU findings, via CAM/pass for the SCSI findings). On this guest there is no live primitive to convert. Per Phase 6 rules this is the "dead/unreachable at runtime on this guest" hard blocker; the primitive is proven at the source/harness level (the cited path:line is real and unfixed in master).
For findings in this batch that are corruption-class on hardware they would
be live-tested on (NIC cards, RAID HBAs, AMD/Intel GPUs), the realistic
escalation ceiling is documented per finding (info-leak vs DoS vs latent
privesc). No uid=0 claim is made β none is reachable on this guest.
Phase 8 β fix validation
fix.diff is a minimal, targeted fix at the root cause confirmed above.
- Applied cleanly with
patch -p1 --forward(verified infix_apply.log). - Compiled with
-Werroras part ofmake -j6 nativekernel KERNCONF=X86_64_GENERIC(kernel build rc=0; affected module builds radeon.ko/amdgpu.ko/sound.ko/i915.ko/vga_switcheroo.ko all produced). - For musycc.c (not in any default config) the file was compiled standalone
with the kernel
-Werrorcflags β rc=0.
Change > sassc->maxtargets to >= sassc->maxtargets.
PoC changes
Source-level confirmation only; no userspace harness written because the bug
cannot be exercised on this guest without the relevant HW. The placeholder
build.sh/run.sh echo pointers to VERDICT.md and the module/kernel
rebuild path.
Confirmed kernel references
- s
- y
- s
- /
- d
- e
- v
- /
- r
- a
- i
- d
- /
- m
- p
- r
- /
- m
- p
- r
- _
- s
- a
- s
- _
- l
- s
- i
- .
- c
- :
- 8
- 9
- 0
- s
- y
- s
- /
- d
- e
- v
- /
- r
- a
- i
- d
- /
- m
- p
- r
- /
- m
- p
- r
- _
- s
- a
- s
- _
- l
- s
- i
- .
- c
- :
- 9
- 0
- 8
- s
- y
- s
- /
- d
- e
- v
- /
- r
- a
- i
- d
- /
- m
- p
- r
- /
- m
- p
- r
- _
- s
- a
- s
- .
- c
- :
- 7
- 6
- 1
Detail
Exploit chain
none β mpr(4) HW-gated (no LSI SAS3 HBA in guest). Primitive is off-by-one heap OOB write on real HW; no live escalation possible on this guest.
Evidence (decisive lines)
Source-level confirmation at sys/dev/raid/mpr/mpr_sas_lsi.c:890, sys/dev/raid/mpr/mpr_sas_lsi.c:908, sys/dev/raid/mpr/mpr_sas.c:761. fix.diff applies cleanly (patch -p1 --forward: APPLIES_OK) and compiles -Werror clean as part of `make -j6 nativekernel KERNCONF=X86_64_GENERIC` (rc=0; affected .o/.ko produced). No live trigger on this guest (HW/module gated).
PoC changes
Wrote VERDICT.md, fix.diff (one hunk: > -> >=), build/run.sh, build.log excerpt, fix_apply.log, env.txt, manifest.json.
Verified recommended fix
Change > sassc->maxtargets to >= sassc->maxtargets at mpr_sas_lsi.c:890. Supersedes any pre-verification proposal. The full git-apply-able diff lives in findings/poc/DF-1475/fix.diff.
Verdict
mprsas_add_device PhyNum fallback at 890 uses (id = config_page.PhyNum) > sassc->maxtargets (strict greater-than), so PhyNum == maxtargets passes. targets[] has indices 0..maxtargets-1 (allocated sizeof(mprsas_target)*maxtargets at mpr_sas.c:761). Line 908 targ = &sassc->targets[id] with id==maxtargets writes one struct mprsas_target (~200 bytes) past the end. mpr(4) HW-gated as DF-1474. Source-level confirmed.
No comments yet.