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

Unbounded csio->cdb_len bcopy into the 32-byte MPI2 CDB field overflows the request frame in the req_frames DMA pool

Summary

mprsas_action_scsiio copies csio->cdb_len bytes (u_int8_t, up to 255) into req->CDB.CDB32[32] with no bound when CAM_CDB_POINTER is set, and only an INVARIANTS-only KASSERT otherwise. A cdb_len of 255 overwrites the frame's SGL area and ~2 adjacent request frames; for the last SMID it runs past the whole DMA allocation with attacker-chosen bytes; the pointer path also reads a raw user cdb_ptr (cam_periph_mapmem never touches it). Root-only (/dev/pass* is root-only): privileged CAM client corrupts kernel DMA memory. ahci's ATAPI path validates 6..16 - the bound is SIM-dependent and mpr lacks one; same class as DF-2422. GUEST RUN PROVED the framework-level precondition (cdb_len=255 + CAM_CDB_POINTER passes CAM untouched, status 0x6 = ahci's own rejection); the mpr manifestation needs an mpr-attached bus, absent on this guest.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3805 Β· 11 files
FileTypeDescriptionSize
README.md β€” 305 B ↓ raw
VERDICT.md β€” 3.5 KB ↓ raw
cdb_overlong.c β€” 2.6 KB view raw
build.sh β€” 102 B view raw
run.sh β€” 109 B view raw
build.log β€” 9 B view raw
run.log β€” 179 B view raw
env.txt β€” 855 B view raw
fix.diff β€” 978 B view raw
verdict.json β€” 2.7 KB view raw
manifest.json β€” 692 B view raw

DF-3805 evidence pack

Unbounded csio->cdb_len bcopy into the 32-byte MPI2 CDB field (mprsas_action_scsiio). cdb_overlong.c proves on the guest that the CAM framework passes an oversized CDB straight to the SIM. Read VERDICT.md. verdict.json: not_reproduced (no mpr SIM on guest; root-gated).

VERDICT.md
↓ download raw

DF-3805 β€” Unbounded csio->cdb_len bcopy overflows the 32-byte MPI2 CDB field

  • File: sys/dev/raid/mpr/mpr_sas.c
  • Class: kernel memory corruption (root-gated: requires CAM passthrough, /dev/pass* is root-only)
  • Severity: Low / Confidence: certain (mechanics)
  • Status on guest: not_reproduced β€” CAM framework imposes no bound (demonstrated on guest); the mpr SIM itself could not be reached (no SAS3 HBA)

Defect

mprsas_action_scsiio copies the CDB with the caller-supplied length and no bound of its own (sys/dev/raid/mpr/mpr_sas.c:2102-2109):

    if (csio->ccb_h.flags & CAM_CDB_POINTER)
        bcopy(csio->cdb_io.cdb_ptr, &req->CDB.CDB32[0], csio->cdb_len);
    else {
        KASSERT(csio->cdb_len <= IOCDBLEN, ...);   /* INVARIANTS-only */
        bcopy(csio->cdb_io.cdb_bytes, &req->CDB.CDB32[0], csio->cdb_len);
    }
  • CDB32 is 32 bytes (sys/dev/raid/mpr/mpi/mpi2_init.h:124, offset 0x40, SGL at 0x60 follows in the same frame).
  • cdb_len is u_int8_t (sys/bus/cam/cam_ccb.h:603) β†’ up to 255.
  • With CAM_CDB_POINTER there is no check at all; without it, the only check is a KASSERT, which is INVARIANTS-only in DragonFly (sys/sys/systm.h:94-118) β€” compiled out in production kernels. For the non-pointer path the source over-reads cdb_bytes[16] inside/past the CCB.
  • Nothing in the CAM framework bounds cdb_len: passioctl/passsendccb (sys/bus/cam/scsi/scsi_pass.c:462-516, 531-615) merges the user CCB via xpt_merge_ccb with no validation, and cam_periph_mapmem never handles cdb_ptr (grep: no reference in sys/bus/cam/cam_periph.c) β€” the SIM also dereferences a raw user pointer without copyin.

A cdb_len of e.g. 255 copies 255 bytes into the request frame (cm->cm_req = sc->req_frames + i * sc->reqframesz, sys/dev/raid/mpr/mpr.c:1530), overwriting the frame's SGL area and ~2 subsequent request frames; for the last SMID (i = num_reqs-1) it runs past the end of the req_frames DMA allocation with fully attacker-chosen bytes.

Contrast: ahci's ATAPI path validates cdb_len 6..16 and returns CAM_CCB_LEN_ERR (sys/dev/disk/ahci/ahci_cam.c:1512-1516) β€” the bound is SIM-dependent and mpr is missing one. (Same class as DF-2422 against ahci.)

Trigger / PoC

cdb_overlong.c submits cdb_len=255 + CAM_CDB_POINTER via CAMIOCOMMAND:

build.sh: cc -O -o /tmp/cdb_overlong /root/cdb_overlong.c   -> BUILD_OK  (build.log)
run.sh:   /tmp/cdb_overlong /dev/pass0
  cdb_len=255 CCB accepted by CAM framework; SIM status = 0x6   (CAM_CCB_LEN_ERR)
  (run.log)

This proves the framework-level fact on the guest: an oversized CDB reaches the SIM layer untouched (the status 0x6 is the ahci SIM's own rejection). On this guest the mpr SIM is absent (pciconf -l: no SAS3 HBA; camcontrol devlist: only ata buses), so the overflowing bcopy itself could not be exercised β†’ not_reproduced for the mpr manifestation. The in-SIM reachability of the mpr path additionally requires root (/dev/pass* is root-only), which caps severity at Low.

Fix

Bound the copy like ahci does (see fix.diff): reject cdb_len outside 6..32 with CAM_CCB_LEN_ERR, and copy through a kernel buffer for the pointer path.

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

fix.diff adds the range check; the mpr SIM cannot be reached on this guest, so no kernel build/validation was performed.

findings/poc/DF-3805/fix.diff
↓ fix.diffper-fix-DF-3805

Confirmed kernel references

Detail

Exploit chain

root user issues CAMIOCOMMAND with cdb_len>32 -> bcopy overflows request frame in req_frames DMA pool with attacker bytes; on the last SMID writes past the DMA allocation

Evidence (decisive lines)

cdb_overlong.c, build.log (BUILD_OK), run.log (status 0x6 = CAM_CCB_LEN_ERR from ahci SIM; framework imposed no bound), env.txt, VERDICT.md, fix.diff

PoC changes

Added the includes DF needs (bus/cam/scsi/scsi_message.h for MSG_SIMPLE_Q_TAG, bus/cam/scsi/scsi_pass.h for CAMIOCOMMAND); DF libcam is a stub so the probe uses the raw CAMIOCOMMAND ioctl on /dev/pass0.

Verified recommended fix

Reject cdb_len outside 6..32 with CAM_CCB_LEN_ERR before the bcopy in mprsas_action_scsiio.

Verdict

mprsas_action_scsiio copies csio->cdb_len bytes (u8, up to 255) into the 32-byte req->CDB.CDB32 field (mpr_sas.c:2102-2109; CDB32 at mpi2_init.h:124, offset 0x40) with no bound when CAM_CDB_POINTER is set and only an INVARIANTS-only KASSERT otherwise (sys/sys/systm.h:94-118). Guest run PROVED the framework-level precondition: a cdb_len=255 CAM_CDB_POINTER CCB submitted via CAMIOCOMMAND passes passioctl/xpt_merge_ccb untouched and reaches the SIM (status 0x6 = CAM_CCB_LEN_ERR came from the ahci SIM's own 6..16 check, ahci_cam.c:1512; run.log). The mpr manifestation (overflow of the request frame in the req_frames DMA pool, mpr.c:1530, incl. past the allocation for the last SMID; also a kernel read of a raw user cdb_ptr since cam_periph_mapmem never maps it) could not be exercised because the guest has no SAS3 HBA and the mpr SIM never attaches (env.txt). Root-gated (/dev/pass*), capping severity at Low.