umass autosense REQUEST SENSE uses user-controlled csio.sense_len (up to 255) as the DMA length into the fixed 32-byte csio.sense_data inside the kernel CCB β malicious USB disk overwrites the rest of ccb_scsiio with device-controlled bytes
Summary
umass_cam_cb takes sense_len verbatim as both the REQUEST SENSE alloc length and the USB data-in DMA length into &csio.sense_data.error_code; sense_len is user-settable via pass(4) CAMIOCOMMAND (no clamp anywhere in CAM) while sizeof(sense_data)=32 - a malicious BBB device that CHECK CONDITIONs then answers REQUEST SENSE with up to alloc-length bytes DMA-writes up to 223 device-controlled bytes past sense_data (overwrites cdb_io incl. cdb_ptr slot, resid, sglist_cnt...). Kernel callers hardcode 32, so pass(4)/root issuer required; the DEVICE alone controls overflow content. REPRODUCED with a full usbredir evil-disk rig on the guest (evilmsc emulator on host, usb-redir chardev, /dev/pass8 CAMIOCOMMAND sense_len=255): 48/48 pattern match in cdb_io + resid==pattern, 4/4 runs incl. fresh baseline; clamping fix rebuilt+validated (0/48). Clamp-before-both-uses fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-5297 Β· 9 files| File | Type | Description | Size | |
|---|---|---|---|---|
| passexp.c | trigger | guest pass(4) CAMIOCOMMAND with sense_len=255 | 3.7 KB | view raw |
| pyevilmsc.py | rig | userspace evil USB MSC device (usbredir peer) | 12.3 KB | view raw |
| qemu-launch.sh | rig | QEMU launch line with uhci+usb-redir | 580 B | view raw |
| moncmd.py | rig | QEMU monitor command helper | 728 B | view raw |
| run.log | runtime-log | decisive vulnerable run (48/48 pattern match) | 698 B | view raw |
| evilmsc-device.log | runtime-log | device-side usbredir/SCSI trace | 17.0 KB | view raw |
| env.txt | env | guest uname, device nodes, INVARIANTS, compiler | 346 B | view raw |
| fix.diff | suggested-fix | clamp csio.sense_len to sizeof(sense_data) | 899 B | view raw |
| VERDICT.md | verdict | full narrative | 4.8 KB | β raw |
DF-5297 verdict
Status: REPRODUCED β deterministic, 3/3 runs (plus a 4th on the clean
with-src snapshot baseline before fix validation).
Impact: kernel memory corruption, device-controlled content
(intra-object write into the live kernel CCB).
Confidence: certain.
Root cause (path:line)
sys/bus/u4b/storage/umass.c:2602β the autosense REQUEST SENSE allocation length is taken verbatim fromccb->csio.sense_len.sys/bus/u4b/storage/umass.c:2609-2611β the same value is passed as the data-in DMA length with&ccb->csio.sense_data.error_codeas the target buffer.sys/bus/cam/scsi/scsi_all.h:911-953βstruct scsi_sense_datais exactly 32 bytes in DragonFly.sys/bus/cam/cam_ccb.h:601-602βsense_lenis a user-settableu_int8_tinsideccb_scsiio, copied from the user CCB byxpt_merge_ccb()(sys/bus/cam/scsi/scsi_pass.c:546) with no validation anywhere in CAM.
There is no third place: every kernel-internal caller (da/cd/sa/pt/probe)
hardcodes SSD_FULL_SIZE (32) or SSD_MIN_SIZE (18); the only way to get
sense_len > 32 is a user-supplied CCB via pass(4) CAMIOCOMMAND.
Reproduction chain (observed)
pyevilmsc.pyon the QEMU host presents a full-speed BBB SCSI disk over usbredir (usb-redirdevice on apiix3-usb-uhcibus). Guest:umass0,da8,/dev/pass8.- The device CHECK CONDITIONs the READ(10) of LBA 0x5297.
umass_cam_cb(STATUS_CMD_FAILED) fetches sense: REQUEST SENSE with alloc length 255 (user'ssense_len), DMA length 255 into the 32-bytecsio.sense_data.- The device returns exactly 255 bytes (legal per SPC): 18 valid NOT-READY
sense bytes + a rolling
0x40+(i&0x3f)pattern. - The USB host controller DMAs all 255 bytes into the kernel CCB; bytes
32..254 land past
sense_data, overwritingsense_len,cdb_len,sglist_cnt,scsi_status,sense_resid,resid,cdb_io(incl. thecdb_ptrpointer slot),msg_ptr,msg_len,tag_action,tag_id,init_idand the union tail. - pass(4)
bcopy()s the whole kernelunion ccbback to userland (scsi_pass.c:612), so the corruption is directly observable:cdb_ioregion =6c 6d 6e 6f β¦(pattern indices 44..91) andcsio.resid == 0x6b6a6968(pattern indices 40..43).
run.log is the decisive run; run.2.log/run.3.log equivalents were
captured in-session (3/3 identical 48/48 matches; rc=0 each time).
Exploitability assessment (honest)
- The write length (β€255) and content are fully controlled by the malicious
device; the destination is a freshly-DMA'd kernel object (
union ccb, kmalloc M_CAMXPT, 720 bytes β the overflow stays intra-object, no adjacent-object overwrite: sense_data+255 < sizeof(union ccb)). - Overwritable fields of note:
cdb_io.cdb_ptr(read as a pointer byumass_cam_sense_cb, umass.c:2648-2652, when the CCB had CAM_CDB_POINTER set β yields a device-influencedcmd[0]read from an arbitrary address),msg_ptr,resid,sglist_cnt. - Privilege gate: setting
sense_len > 32requires issuing a CCB through/dev/pass*, which isroot:operator 0600. An unprivileged user cannot trigger this on a default system; the untrusted party is the USB device (a root user plugging in an evil stick gets kernel memory corrupted with device bytes). uid=0 escalation for an unprivileged local user is therefore not reachable through this primitive alone β no unprivileged trigger path exists (all kernel CAM callers use 32). Severity accordingly: High (kernel memory corruption class) with the privilege precondition documented.
Fix validation
fix.diff clamps ccb->csio.sense_len to sizeof(ccb->csio.sense_data)
before both uses. Validation protocol (see fix_*.log files):
vm.sh resetto thewith-srcsnapshot; evil disk re-attached; baseline PoC on the unpatched kernel: 48/48 overflow (again).patch -p1 < fix.diffin guest/usr/src;make nativekernel KERNCONF=X86_64_GENERIC+make installkernel; reboot.- Same PoC against the same evil device on the patched kernel:
pattern confined to
sense_data[0..31],cdb_io/residclean,PATTERN_MATCH 0/48β overflow gone, command still fails correctly (CHECK CONDITION with the device's NOT-READY sense) β no regression.
Files
run.logβ decisive vulnerable run (48/48).fix_baseline.log,fix_patched.logβ fix-validation pair.evilmsc-device.log,qemu-usbredir.logβ device/host traces.env.txtβ guest uname/nodes/INVARIANTS/compiler.
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
fixedpatched kernel (make nativekernel KERNCONF=X86_64_GENERIC, rc=0 -Werror, 37747-line log): same PoC against the same evil device -> 0/48 pattern bytes past sense_data, cdb_io contains the original READ(10) CDB unchanged, command still completes through the autosense path (no functional regression). fix.diff validated.
fix_baseline.log (48/48 pre-fix), fix_patched.log (0/48 post-fix), fix_build.log (kernel build)
Confirmed kernel references
Detail
Exploit chain
evil USB disk (BBB SCSI) + root via /dev/passN: (1) device serves probe so da/pass attach; (2) user issues READ(10) of magic LBA with csio.sense_len=255; (3) device CHECK CONDITIONs it and answers the autosense REQUEST SENSE with 255 bytes; (4) 223 bytes land past sense_data corrupting cdb_io.cdb_ptr/msg_ptr/resid/sglist_cnt etc. Unprivileged uid=0 escalation NOT reachable: all kernel-internal CAM callers use sense_len=32 and /dev/pass* is root 0600, so only the (untrusted) device side is unprivileged.
Evidence (decisive lines)
run.log (decisive: 48/48 pattern match past sense_data, cdb_io=6c6d6e.., resid=0x6b6a6968); fix_baseline.log (48/48 on clean with-src kernel); fix_patched.log (0/48, cdb_io intact); fix_build.log (37747 lines, rc=0, -Werror); evilmsc-device.log (device-side usbredir/SCSI trace); env.txt
PoC changes
trigger rewritten from scratch: pass(4) CAMIOCOMMAND READ(10) of a magic LBA (sense_len=255) instead of TEST UNIT READY (TUR failures wedge the CAM probe); evil device implemented as a userspace usbredir peer (pyevilmsc.py) because QEMU's built-in usb-storage cannot return 255-byte sense data
Verified recommended fix
clamp ccb->csio.sense_len to sizeof(ccb->csio.sense_data) before using it as REQUEST SENSE alloc/DMA length in umass_cam_cb()
Verdict
umass_cam_cb() issues the CAM autosense REQUEST SENSE using ccb->csio.sense_len (user-settable up to 255 via pass(4) CAMIOCOMMAND, unvalidated by CAM) both as the SCSI allocation length (umass.c:2602) and as the data-in DMA length into the fixed 32-byte csio.sense_data inside the kernel CCB (umass.c:2609-2611). A malicious USB mass-storage device that legitimately returns up to alloc-length bytes of sense data DMA-writes up to 223 device-controlled bytes past sense_data, overwriting the remainder of struct ccb_scsiio in a live kernel object. Reproduced deterministically (4/4 vulnerable runs incl. a fresh with-src baseline): the rolling device pattern appears past sense_data[31] in cdb_io and csio.resid itself equals pattern bytes. Fixed by clamping sense_len to sizeof(sense_data): patched kernel confines the pattern to sense_data[0..31] with cdb_io intact (0/48) and no functional regression.
No comments yet.