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

Unsigned resid underflow: ccb->csio.resid = dxfer_len - IOC-supplied TransferCount with no clamp

Summary

mpt_scsi_reply_frame_handler computes resid from the IOC's U32 TransferCount without clamping; an over-reporting IOC produces resid near 2^32, which flows to CAM and pass(4) userland and breaks the resid<=dxfer_len invariant relied on by cd/da residual and dump bookkeeping. Only TransferCount==0 under RESIDUAL_MISMATCH is special-cased. Shared with upstream FreeBSD. IOC/firmware trust class; wrong data-accounting visible to userland, mis-driven retry/dump paths; no driver memory-safety impact. Fix: clamp before subtracting.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3675 Β· 6 files
FileTypeDescriptionSize
README.md β€” 435 B ↓ raw
VERDICT.md β€” 2.6 KB ↓ raw
fix.diff β€” 643 B view raw
env.txt β€” 281 B view raw
verdict.json β€” 1.6 KB view raw
manifest.json β€” 467 B view raw

DF-3675 β€” unsigned resid underflow from IOC-supplied TransferCount

Severity: Low (logic/integrity; IOC-driven). ccb->csio.resid is computed as dxfer_len - TransferCount with no clamp (mpt_cam.c:3132-3133): an IOC that over-reports produces resid ~= 4G, visible to pass(4) users and violating CAM invariants for cd/da residual handling. No PoC possible without MPT hardware (env.txt). Shared with upstream FreeBSD. See VERDICT.md.

VERDICT.md
↓ download raw

DF-3675 β€” VERDICT

status: inconclusive (no MPT hardware on guest; source-certain arithmetic)

Root cause

/* sys/dev/disk/mpt/mpt_cam.c:3131-3133 */
    ccb = req->ccb;
    ccb->csio.resid =
        ccb->csio.dxfer_len - le32toh(scsi_io_reply->TransferCount);

TransferCount is a U32 field of the IOC's DMA'd SCSI_IO_REPLY frame with no clamping: an IOC reporting more bytes transferred than the command's dxfer_len underflows the unsigned subtraction, so CAM and userland see resid near 4 G (dxfer_len - TransferCount mod 2^32). The driver only special-cases TransferCount == 0 under MPI_IOCSTATUS_SCSI_RESIDUAL_MISMATCH (mpt_cam.c:3163-3176); for DATA_OVERRUN (:3198) and every other status the bogus resid flows out untouched.

Consumers: - pass(4) copies the full CCB back to the caller β€” the huge resid is directly visible to the pass user (correctness/integrity of upper-layer software that trusts resid); - cd(4)/da(4) use resid for residual handling and error reporting; a resid greater than dxfer_len violates CAM's invariant and can mis-drive retry / dump accounting (e.g. da dump residual bookkeeping).

Threat: requires an IOC/firmware reporting a wrong TransferCount (or real overrun reporting) β€” hardware-trust class. No memory-safety consequence in the driver itself; impact ceiling is wrong data-accounting visible to userland and possibly a wedged dump/retry path. Severity Low.

Fix (also matches the defensive style used for sense data)

--- a/sys/dev/disk/mpt/mpt_cam.c
+++ b/sys/dev/disk/mpt/mpt_cam.c
@@ -3129,7 +3129,13 @@ mpt_scsi_reply_frame_handler(struct mpt_softc *mpt, request_t *req,
    sstate = scsi_io_reply->SCSIState;

    ccb = req->ccb;
-   ccb->csio.resid =
-       ccb->csio.dxfer_len - le32toh(scsi_io_reply->TransferCount);
+   {
+       uint32_t xferred = le32toh(scsi_io_reply->TransferCount);
+       if (xferred > ccb->csio.dxfer_len)
+           ccb->csio.resid = 0;    /* IOC over-reported */
+       else
+           ccb->csio.resid = ccb->csio.dxfer_len - xferred;
+   }

Guest verdict

inconclusive β€” no MPT hardware; the underflow is proven by inspection (unsigned U32 arithmetic, no clamp) and matches upstream FreeBSD (mpt_cam.c ccb->csio.resid = ccb->csio.dxfer_len - le32toh(scsi_io_reply->TransferCount); β€” shared bug).

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_testable
baseline no→ patch + rebuild →patched clean

Clamp diff authored (fix.diff); not exercisable without hardware.

fix.diff; env.txt
↓ fix.diffper-fix-DF-3675

Confirmed kernel references

Detail

Evidence (decisive lines)

VERDICT.md (arithmetic proof + consumer analysis); fix.diff (clamp); env.txt (no hardware)

PoC changes

n/a

Verified recommended fix

Clamp: resid = (xferred > dxfer_len) ? 0 : dxfer_len - xferred

Verdict

Source-certain unsigned underflow: mpt_cam.c:3132-3133 computes ccb->csio.resid = dxfer_len - le32toh(TransferCount) with the IOC-supplied U32 unclamped; an over-reporting IOC yields resid near 2^32 which propagates to CAM and pass(4) userland and breaks the resid<=dxfer_len invariant relied on by cd/da residual and dump bookkeeping. Only TransferCount==0 under RESIDUAL_MISMATCH is special-cased (3163-3176). Hardware-trust threat class, no memory-safety consequence in the driver; ceiling is wrong data-accounting / wedged retry-dump paths. Not executable on this guest (no MPT HBA). Shared with upstream FreeBSD.