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

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):

  1. During attach, report IOC facts with MaxTargets=128, MaxVolumes=8 β†’ maxtargets=136, targets[] has 136 entries (indices 0-135).
  2. Post a SAS_TOPOLOGY_CHANGE_LIST event with a PHY entry having PhyStatus=MPI2_EVENT_SAS_TOPO_RC_TARG_ADDED and an AttachedDevHandle (e.g., 0x0012).
  3. When the driver calls mprsas_add_device(sc, 0x0012, ...), respond to the mpr_config_get_sas_device_pg0 request with a page0 whose SASAddress is 0 (or any address that mpr_mapping_get_tid rejects β†’ returns MPR_MAP_BAD_ID), and PhyNum=136.
  4. The check 136 > 136 is false, so id=136. Line 908: targ = &sassc->targets[136] β€” one past the 136-element array.
  5. 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.

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);
  • DF-1473/1474 (sibling): event handler OOB family in same file.
  • DF-1282/1283 (sibling, mpr_mapping): DPM DeviceIndex OOB.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-1475 Β· 10 files
FileTypeDescriptionSize
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
README.md readme human-readable summary
↓ download 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.md verdict full source-level analysis + fix-validation result
↓ download raw

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 in fix_apply.log).
  • Compiled with -Werror as part of make -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 -Werror cflags β€” 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

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.