mpr_print_sgl walks the request-frame SGL unbounded, dereferences a possibly-NULL chain, and hexdumps a fixed 128 bytes β unconditional console path on firmware MPI2_IOCSTATUS_INVALID_SGL
Summary
Fired unconditionally (no debug gating) from scsiio_complete on INVALID_SGL - the status that declares the SGL malformed. The walkers advance with no per-frame bound (OOB heap read to console), the chain transition derefs chain->chain unchecked (NULL+0x10 panic), and the hexdump is fixed 128 bytes. Same trust boundary as pass-1 DF-3816/3817. Cap+NULL-check fix. MPT3 hw-gated.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4789 Β· 6 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 2.7 KB | β raw | |
| VERDICT.md | β | 3.1 KB | β raw | |
| env.txt | β | 920 B | view raw | |
| fix.diff | β | 1.7 KB | view raw | |
| manifest.json | β | 533 B | view raw | |
| verdict.json | β | 2.3 KB | view raw |
DF-4789 β mpr_print_sgl: unbounded SGE walk, NULL chain deref, unconditional 128-byte hexdump on firmware INVALID_SGL
What
sys/dev/raid/mpr/mpr_table.c:530-584 (mpr_print_sgl), reached unconditionally
(not gated on sc->mpr_debug) from mpr_sas.c:2814 whenever the controller returns
MPI2_IOCSTATUS_INVALID_SGL for a completed SCSI IO:
mpr_table.c:547βhexdump(frame, 128, NULL, 0)assumes a β₯128-byte request frame;cm->cm_reqpoints into the DMA request pool sized from IOC-factsIOCRequestFrameSize(firmware-reported). A smaller reported frame size makes the hexdump read past the frame into adjacent DMA heap and print it to console.mpr_table.c:548-572(IEEE walk) βieee_sge++with no per-frame bound: the walk continues until an SGE happens to carry END_OF_LIST. If the (driver-built) SGL is malformed β exactly the condition INVALID_SGL announces β the walk runs off the end of the request frame, reading and printing heap past it.mpr_table.c:557-568β on a CHAIN_ELEMENT flag the walker takesTAILQ_FIRST/TAILQ_NEXT(&cm->cm_chain_list)and then dereferenceschain->chainat:568without checkingchain != NULL. If the frame claims more chain elements than the command actually has (or the command has none), this is a NULL pointer dereference β panic.mpr_table.c:573-583(simple walk) β same unboundedsge++pattern.
Sibling context: pass-1 already filed the adjacent debug-path issues DF-3816 (dump_reqs sysctl unlocked walk) and DF-3817 (unbounded hexdump of event data) as Low; this is the same class in the INVALID_SGL printer, which pass 1 did not cover.
Trigger / threat
Triggered by the controller firmware returning MPI2_IOCSTATUS_INVALID_SGL on any CAM
SCSI IO completion (an unprivileged user generating disk I/O reaches this completion
path; the IOC status itself is firmware-produced). With a malicious/buggy IOC (or
corrupted DMA), this is an OOB heap read printed to console (info disclosure to anyone
who can read the console/syslog) and/or a NULL-deref panic (local DoS). No write
primitive. The walk data comes from the request the driver built, so on a healthy
controller+driver pair this never fires β firmware-gated, hence Low.
Reproduce (requires LSI Fusion-MPT3 hardware β NOT available in the audit QEMU guest)
Not runnable on the audit guest (virtio-only; no vendor 0x1000 MPT3 device β see
env.txt). On hardware, a fault-injected IOC reply with IOCStatus=INVALID_SGL on a
command whose frame SGL has a chain flag but an empty cm_chain_list panics at
mpr_table.c:568.
Fix
fix.diff β break out when the chain list is exhausted, and cap the per-frame SGE walk
so it can never run past a request frame.
DF-4789 VERDICT
- Status: untested (hardware-gated)
- Confidence: likely
- Severity filed: Low (CWE-125 OOB read / CWE-476 NULL deref, firmware-gated)
Line-accurate trace
- Entry:
mpr_sas.c:2813-2816c case MPI2_IOCSTATUS_INVALID_SGL: mpr_print_scsiio_cmd(sc, cm); /* unconditional β no mpr_debug gating */ mprsas_set_ccbstatus(ccb, CAM_UNREC_HBA_ERROR);Any CAM SCSI IO completion path (mprsas_scsiio_complete) funnels here; the IOC status byte is produced by the controller. Unprivileged users issue disk IO through /dev/da constantly, so the caller context is unprivileged β the status* is firmware-controlled. mpr_table.c:587-593mpr_print_scsiio_cmdβmpr_print_sgl(sc, cm, req->SGLOffset0).mpr_table.c:541-547:frame = cm->cm_req(request-frame pool, DMA-coherent, per-frame sizesc->req_frameszfrom IOC facts);hexdump(frame, 128, ...)fixed 128.- IEEE walk
:548-572: readsieee_sge->Flags/Length/Address, prints them; the ONLY exits are END_OF_LIST flag or theflags & CHAIN_ELEMENTtransition. No counter boundsieee_sgeto the frame β malformed SGL (precisely what INVALID_SGL denotes) walks past the frame and prints adjacent DMA-pool memory to the console. - Chain transition
:557-568: first chain viaTAILQ_FIRST, later viaTAILQ_NEXT; thenframe = (char *)chain->chain;at:568β if the TAILQ is empty/exhausted,chain == NULLβ read ofNULL->chainat offset 0x10 (struct mpr_chain:chain_linkTAILQ_ENTRY = 16 bytes, thenvoid *chain, mprvar.h:191-195) β clean NULL-page panic, not an exploitable primitive). - Simple walk
:573-583: identical unboundedsge++(no chain transition at all).
Impact ceiling
- Console/syslog disclosure of request-pool heap contents (OOB read) β modest.
- NULL-deref panic β local DoS attributed to disk IO storms.
- No attacker-controlled write; the printer only reads.
Why Low and not higher
Every value the walker trusts (SGE flags, chain offsets) lives in the request frame the driver itself built; on a correctly operating controller the walk is well-formed. The bug fires only when the firmware flags the SGL invalid (malicious/buggy IOC, corrupted DMA, or a driver bug elsewhere) β same trust boundary as pass-1's DF-3816/DF-3817 (Low).
Why untested on this run
Audit guest is KVM/virtio-only: no LSI (vendor 0x1000) storage controller exists
(env.txt), so mpr never attaches and no INVALID_SGL completion can be synthesized
from inside the guest. Code-read verification only.
Recommended fix summary
Guard the chain-list exhaustion (if (chain == NULL) break; before chain->chain) and
cap the per-frame SGE walk (fix.diff).
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_testablemechanical guards (NULL break + iteration caps); cannot be boot-validated without mpr hardware in the guest
findings/poc/DF-4789/fix.diff
Confirmed kernel references
Detail
Evidence (decisive lines)
README.md (trigger analysis), VERDICT.md (full trace), env.txt (guest has no vendor 0x1000 device), fix.diff (chain NULL guard + walk caps)
PoC changes
no runnable PoC produced: requires Fusion-MPT3 hardware returning INVALID_SGL; audit guest has none
Verified recommended fix
Break the walk when the chain list is exhausted and cap SGEs walked per frame (see fix.diff)
Verdict
mpr_print_sgl(), reached unconditionally from mprsas_scsiio_complete on any MPI2_IOCSTATUS_INVALID_SGL completion (mpr_sas.c:2814), walks the request-frame SGL with no per-frame bound (ieee_sge++/sge++, mpr_table.c:555/581), dereferences chain->chain without a NULL check when the chain TAILQ is exhausted (mpr_table.c:568), and hexdumps a fixed 128 bytes regardless of the IOC-facts request-frame size (mpr_table.c:547). With a buggy or malicious controller this is an OOB heap read printed to console plus a NULL-deref panic (local DoS); no write primitive. Not exercised on the audit guest: it is a virtio-only KVM guest with no LSI MPT3 controller (env.txt), so the mpr driver never attaches and the IOC status cannot be produced. Verified by line-accurate code tracing.
No comments yet.