# DF-3920 — hptmv `OsSendCommand` copies user-controlled `cdb_len` into `UCHAR Cdb[16]` (kernel stack overflow)

## What
`sys/dev/raid/hptmv/entry.c:2771-2794` (function `OsSendCommand`, invoked from
`hpt_action` XPT_SCSI_IO, entry.c:2676-2899):

```c
UCHAR Cdb[16];                      /* :2771 */
UCHAR CdbLength;
CdbLength = csio->cdb_len;          /* :2777 — u_int8_t, attacker-supplied, never clamped */
if ((ccb->ccb_h.flags & CAM_CDB_POINTER) != 0)
        bcopy(csio->cdb_io.cdb_ptr, Cdb, CdbLength);   /* :2782 */
else
        bcopy(csio->cdb_io.cdb_bytes, Cdb, CdbLength); /* :2793 */
```

`csio.cdb_len` is copied verbatim from the user's CCB by
`passsendccb` → `xpt_merge_ccb` (sys/bus/cam/scsi/scsi_pass.c:546); no layer of
DragonFly CAM clamps it (verified: no `cdb_len` validation in scsi_pass.c,
cam_periph.c or cam_xpt.c for XPT_SCSI_IO). A user-supplied `cdb_len` of 17..255
overflows the 16-byte stack array by up to 239 bytes with fully attacker-chosen
contents (the CDB pointer for `CAM_CDB_POINTER` is the user's kernel-copied
ioctl buffer — mapped_ioctl() copies the whole union ccb into kernel heap,
sys/kern/sys_generic.c:674-676).

## Impact
Kernel stack memory corruption (CWE-787/121) → potential ring-0 code execution.
Gated twice: requires (1) an attached hptmv controller with a present target
(the audit guest has none — see env.txt), and (2) root / operator-with-
SYSCAP_RESTRICTEDROOT, because /dev/passN is 0600 root:operator and
`passopen` checks `caps_priv_check_self(SYSCAP_RESTRICTEDROOT)`
(scsi_pass.c:308). Hence severity **Low** per the project rubric
("already-privileged user + specific hardware"), though the primitive itself
is a clean kernel stack smash.

## Reproduce (on hardware that has an RR1820)
```
cc -Wall -o df3920 df3920.c          # trigger sketch in this directory
sudo ./df3920 <pass-unit-of-hptmv-disk> 200
# expected on vulnerable driver: kernel panic / stack corruption in OsSendCommand
```
Cannot run on the audit guest (no 11ab:5081 device) — verdict `not_reproduced`
with source-level certainty.

## Fix
Clamp the copy (and reject oversized CDBs) before the bcopy:

```diff
--- a/sys/dev/raid/hptmv/entry.c
+++ b/sys/dev/raid/hptmv/entry.c
@@ -2774,6 +2774,11 @@ OsSendCommand(_VBUS_ARG union ccb *ccb)
 			PCommand pCmd = AllocateCommand(_VBUS_P0);
 			HPT_ASSERT(pCmd);
 
+			if (csio->cdb_len > sizeof(Cdb)) {
+				ccb->ccb_h.status = CAM_REQ_INVALID;
+				xpt_done(ccb);
+				break;
+			}
 			CdbLength = csio->cdb_len;
```
(An `imin(csio->cdb_len, sizeof(Cdb))` clamp also works; rejecting is safer
because READ_6/10/16 decoding never needs more than 16 bytes.)
