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

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-5297 Β· 9 files
FileTypeDescriptionSize
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
VERDICT.md verdict full narrative
↓ download 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)

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)

  1. pyevilmsc.py on the QEMU host presents a full-speed BBB SCSI disk over usbredir (usb-redir device on a piix3-usb-uhci bus). Guest: umass0, da8, /dev/pass8.
  2. The device CHECK CONDITIONs the READ(10) of LBA 0x5297.
  3. umass_cam_cb (STATUS_CMD_FAILED) fetches sense: REQUEST SENSE with alloc length 255 (user's sense_len), DMA length 255 into the 32-byte csio.sense_data.
  4. The device returns exactly 255 bytes (legal per SPC): 18 valid NOT-READY sense bytes + a rolling 0x40+(i&0x3f) pattern.
  5. The USB host controller DMAs all 255 bytes into the kernel CCB; bytes 32..254 land past sense_data, overwriting sense_len, cdb_len, sglist_cnt, scsi_status, sense_resid, resid, cdb_io (incl. the cdb_ptr pointer slot), msg_ptr, msg_len, tag_action, tag_id, init_id and the union tail.
  6. pass(4) bcopy()s the whole kernel union ccb back to userland (scsi_pass.c:612), so the corruption is directly observable: cdb_io region = 6c 6d 6e 6f … (pattern indices 44..91) and csio.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 by umass_cam_sense_cb, umass.c:2648-2652, when the CCB had CAM_CDB_POINTER set β€” yields a device-influenced cmd[0] read from an arbitrary address), msg_ptr, resid, sglist_cnt.
  • Privilege gate: setting sense_len > 32 requires issuing a CCB through /dev/pass*, which is root: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):

  1. vm.sh reset to the with-src snapshot; evil disk re-attached; baseline PoC on the unpatched kernel: 48/48 overflow (again).
  2. patch -p1 < fix.diff in guest /usr/src; make nativekernel KERNCONF=X86_64_GENERIC + make installkernel; reboot.
  3. Same PoC against the same evil device on the patched kernel: pattern confined to sense_data[0..31], cdb_io/resid clean, 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

fixed
baseline reproduced→ patch + rebuild →patched clean

patched 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)
↓ fix.diffper-fix-DF-5297

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.