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

```c
	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.
