OsSendCommand bcopy's user-controlled cdb_len (<=255) into UCHAR Cdb[16] - kernel stack overflow
Summary
Every READ/WRITE/VERIFY CCB entering hpt_action has its CDB copied into a 16-byte stack array using the user-supplied cdb_len with no clamp; up to 239 bytes of chosen data smash OsSendCommand's kernel stack frame. cdb_len is copied verbatim from the user CCB (no CAM layer clamps it; mapped_ioctl copyins the full union ccb). Root/operator-with-SYSCAP_RESTRICTEDROOT on a host with an RR1820 controller: kernel stack corruption -> ring-0 code execution. Gated by privilege AND hardware, hence Low despite the clean primitive.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3920 Β· 7 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 2.6 KB | β raw | |
| VERDICT.md | β | 3.2 KB | β raw | |
| df3920.c | β | 3.0 KB | view raw | |
| fix.diff | β | 385 B | view raw | |
| env.txt | β | 1.2 KB | view raw | |
| manifest.json | β | 1.0 KB | view raw | |
| verdict.json | β | 2.5 KB | view raw |
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):
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:
--- 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.)
DF-3920 VERDICT
status: not_reproduced (hardware-gated) | impact observed: none | source-level certainty: certain
Was it reproduced?
No. The trigger requires an attached HighPoint RR1820 (PCI 11ab:5081) controller
with a logical device so that XPT_SCSI_IO CCBs are routed to the hptmv SIM
(hpt_action β OsSendCommand). The audit guest has no such PCI device and no
hptmv module (env.txt: pciconf -l filter empty, no /dev/hptmv, no module in
kldstat). No emulator device model exists for this card, so the path cannot be
exercised at runtime on this guest. The trigger program (df3920.c) documents
the exact CCB construction but was deliberately not run against the guest's
AHCI pass device, because on that SIM the bug does not exist (different driver)
β running it there proves nothing.
Why the bug is certain anyway (path:line trace)
sys/kern/sys_generic.c:674-685β mapped_ioctl() copyin()s the whole user union ccb (CAMIOCOMMAND arg) into a kernel buffer;csio.cdb_len(u_int8_t, cam_ccb.h:603) andcdb_io.cdb_ptrarrive verbatim.sys/bus/cam/scsi/scsi_pass.c:546βxpt_merge_ccb(ccb, inccb)copies user CCB fields into the kernel CCB. No clamp of cdb_len (verified by grep across sys/bus/cam: only scsi_sg.c derives cdb_len itself viascsi_group_lenβ€ 16).sys/bus/cam/scsi/scsi_pass.c:600βcam_periph_runccbβxpt_actionroutes the CCB to the owning SIM:hpt_action(entry.c:2246).- entry.c:2266 β target validated; entry.c:2285
OsSendCommand(_VBUS_P ccb). - entry.c:2777 β
CdbLength = csio->cdb_len;(β€255), entry.c:2782/2793 βbcopy(..., Cdb, CdbLength)intoUCHAR Cdb[16](entry.c:2771). Overflow of up to 239 bytes, contents fully attacker-chosen.
Only the read/write/verify CDB opcodes (0x88/0x8a/0x13/0x2f/READ_6/10/WRITE_6)
enter this block; the copied bytes past Cdb[15] overwrite CdbLength,
pCmd, _VBUS_INST locals and the return frame of OsSendCommand
(non-leaf, called under driver_lock from hpt_action).
Privilege gate
/dev/passN open requires caps_priv_check_self(SYSCAP_RESTRICTEDROOT)
(scsi_pass.c:308-309) and the node is crw------- root operator (0600;
operator group lists only root β env.txt). So the trigger is root-class, not
unprivileged. Combined with the hardware requirement β severity Low.
Exploit chain (if the card were present)
root β open /dev/passN on hptmv bus β CAMIOCOMMAND with cdb_len=200 and a 255-byte CDB β 239-byte kernel stack smash in OsSendCommand (no SMEP/SMAP/ KASLR on the standard audit snapshot) β ROP β ring-0. Not pursued further: the primitive is uninteresting on this guest (no hardware) and the actor is already root-class.
Fix validation
Not performed (requires the controller to observe baseline vs patched behavior). fix.diff in this pack is authored against the read-only sys/ tree and never applied there.
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 authored (entry.c: reject csio->cdb_len > sizeof(Cdb)); validation impossible on this guest - no hptmv controller to observe baseline vs patched behavior
fix.diff in this pack; never applied to sys/ (read-only audit tree)
Confirmed kernel references
Detail
Exploit chain
root -> /dev/passN on hptmv bus -> CAMIOCOMMAND cdb_len=200 -> 239-byte kernel stack smash -> ROP -> ring-0 (no SMEP/SMAP/KASLR on standard snapshot); not pursued: no hardware on guest, actor already root-class
Evidence (decisive lines)
VERDICT.md path:line trace (sys_generic.c:685 copyin -> scsi_pass.c:546 merge -> entry.c:2777/2782); env.txt documents absent 11ab:5081 PCI device; df3920.c is the exact CCB construction
PoC changes
n/a (first evidence pack; trigger authored fresh from the verified CAM path)
Verified recommended fix
Reject cdb_len > sizeof(Cdb) with CAM_REQ_INVALID before the bcopy (or clamp with imin())
Verdict
Kernel stack overflow is certain at source level: csio.cdb_len (u_int8_t, user-supplied via /dev/passN CAMIOCOMMAND) is bcopy'd verbatim into UCHAR Cdb[16] at entry.c:2782/2793 with no clamp anywhere in CAM (scsi_pass.c:546 xpt_merge_ccb copies it verbatim). Not reproduced at runtime because the audit guest has no HighPoint RR1820 controller (env.txt) and the trigger additionally requires SYSCAP_RESTRICTEDROOT (scsi_pass.c:308). Up to 239 bytes of chosen data smash the OsSendCommand stack frame.
No comments yet.