Unsigned resid underflow on the DATA_UNDERRUN path: csio->resid = cm_length β IOC-supplied TransferCount with no clamp (DF-3675 twin)
Summary
mpssas_scsiio_complete computes csio->resid = cm->cm_length - rep->TransferCount with both operands unsigned and TransferCount an unvalidated IOC-supplied U32 from the DMA'd reply frame; a reply claiming more bytes than requested wraps resid to ~UINT_MAX and corrupts CAM-periph/userland transfer-length accounting (data-integrity confusion, not memory safety). The sibling paths clamp (OVERRUN forces 0; generic-error resid=cm_length). Twin of DF-3675 (mpt), fixed there by the same clamp.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3896 Β· 8 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 1.4 KB | β raw | |
| VERDICT.md | β | 1.9 KB | β raw | |
| run.sh | β | 111 B | view raw | |
| run.log | β | 482 B | view raw | |
| env.txt | β | 274 B | view raw | |
| fix.diff | β | 736 B | view raw | |
| verdict.json | β | 1.9 KB | view raw | |
| manifest.json | β | 557 B | view raw |
DF-3896 β Unsigned resid underflow: csio->resid = cm_length β IOC-supplied TransferCount with no clamp (UNDERRUN path)
File: sys/dev/raid/mps/mps_sas.c (pass 2). Twin of DF-3675 (mpt_cam.c).
What
case MPI2_IOCSTATUS_SCSI_DATA_UNDERRUN:
csio->resid = cm->cm_length - rep->TransferCount; /* mps_sas.c:1943 */
cm->cm_length is u_int (mpsvar.h:231) set from csio->dxfer_len
(mps_sas.c:1744); rep->TransferCount is a firmware-supplied U32 taken
verbatim from the DMA'd reply frame. If the IOC ever reports
TransferCount > cm_length (malformed/compromised controller or firmware
bug during error recovery β precisely the environment UNDERRUN replies
appear in), the subtraction wraps and csio->resid becomes a huge value
(e.g. 0xFFFFFFF0). CAM periphs and userland then believe nearly nothing
was transferred. resid is consumed by da(4)/cd(4) error handling and
exposed via csio β userland (e.g. pass(4) CAM IOCBs); a bogus huge resid
corrupts data-integrity decisions rather than memory.
Sibling reference: DF-3675 fixed the identical pattern in mpt_cam.c by clamping. The DATA_OVERRUN case in the same function sets resid = 0 (mps_sas.c:2083) and the default path sets resid = cm_length (mps_sas.c:2114) β only the UNDERRUN path does unchecked arithmetic.
Build/run
run.sh records the hardware gate.
Fix
Clamp: csio->resid = (rep->TransferCount < cm->cm_length) ?
cm->cm_length - rep->TransferCount : 0; (see fix.diff).
DF-3896 β VERDICT
Status: not_reproduced (hardware-gated) β defect certain from source, exploitation requires a misbehaving IOC. Impact class: data-integrity/logic confusion from a wrapped CAM resid (Low).
Proof chain
sys/dev/raid/mps/mps_sas.c:1943βcsio->resid = cm->cm_length - rep->TransferCount;with no ordering check.cm->cm_lengthisu_int(sys/dev/raid/mps/mpsvar.h:231) set from the CCB (mps_sas.c:1744);rep->TransferCountis a little-endian U32 copied out of the controller's DMA'd reply frame, unvalidated anywhere on this path.csio->residsemantics: bytes not transferred. CAM consumers (da/cd periph error recovery,camcontrol, pass(4) users) treatresid > dxfer_lenas nonsense at best and as "nothing transferred" at worst; a wrapped value near UINT_MAX can drive wrong retry/sense decisions.- Same-file contrast: DATA_OVERRUN forces resid=0 (mps_sas.c:2083) and the generic-error path forces resid=cm_length (mps_sas.c:2114) β both clamped; only UNDERRUN does open arithmetic. Twin: DF-3675 (mpt_cam.c:3132-3133), accepted Low with the same clamp fix.
Why not reproduced
Requires an mps controller (none in guest β env.txt/run.log) and an IOC that reports TransferCount beyond the requested length in an UNDERRUN reply. Cannot be driven by unprivileged input on healthy firmware; hence Low severity / hardware+firmware gated.
Fix validation
Not testable on this guest; fix.diff is a two-line clamp identical in spirit to the DF-3675 fix.
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 adds the clamp; cannot be exercised on this guest (no SAS2 HBA).
findings/poc/DF-3896/fix.diff
Confirmed kernel references
Detail
Evidence (decisive lines)
VERDICT.md (path:line proof incl. clamped sibling paths 2083/2114), env.txt, run.sh + run.log (hardware gate), fix.diff (clamp)
PoC changes
n/a β hardware/firmware-gated.
Verified recommended fix
Clamp: resid = (TransferCount < cm_length) ? cm_length - TransferCount : 0.
Verdict
mpssas_scsiio_complete computes csio->resid = cm->cm_length - rep->TransferCount (mps_sas.c:1943) on the DATA_UNDERRUN path with no ordering check; both operands are unsigned (cm_length is u_int, mpsvar.h:231) and TransferCount is an unvalidated IOC-supplied U32, so a reply claiming more bytes than requested wraps resid to ~UINT_MAX and corrupts CAM-periph/userland transfer-length accounting. Twin of DF-3675 (mpt_cam.c:3132). Not reproduced: no SAS2 HBA in the guest and the trigger additionally needs a misreporting IOC; underflow arithmetic is certain from source.
No comments yet.