No validation/stripping of dangerous user CCB flags allows privileged DMA to arbitrary physical memory and OOB reads
Summary
passsendccb at scsi_pass.c:565-568 gates cam_periph_mapmem purely on (flags & CAM_DATA_PHYS)==0 but never STRIPS the flag. When attacker sets CAM_DATA_PHYS, the entire cam_periph_mapmem path (useracc/MAXPHYS/bounce-buffer) is bypassed and csio.data_ptr (attacker-controlled) reaches the SIM as a physical address for DMA. XPT_SCSI_IO with CAM_DIR_IN writes device response to attacker-chosen physical address (arbitrary physical-memory write); CAM_DIR_OUT DMAs that physical address out. Same gate missing for CAM_SENSE_PHYS/CAM_CDB_PHYS/CAM_SENSE_PTR/CAM_CDB_POINTER. No bounds check of cdb_len against IOCDBLEN=16 for inline CDBs - cdb_len=255 makes SIM read 255 bytes from 16-byte cdb_bytes, over-reading csio/union ccb including msg_ptr kernel pointer. XPT driver proves kernel knows these are hazardous - cam_xpt.c:1133 rejects CAM_DATA_PHYS for user DEV_MATCH CCBs. pass.c omits ALL these protections. Requires SYSCAP_RESTRICTEDROOT; impactful in jail granted RESTRICTEDROOT with /dev/passN exposed (jail escape / securelevel bypass on hosts without IOMMU).
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-1088 Β· 17 files| File | Type | Description | Size | |
|---|---|---|---|---|
| df1088_trigger.c | trigger-source | open()/CAMIOCOMMAND exerciser; proves devfs cap gate then proves CCB-shape bug as root | 3.0 KB | view raw |
| verify.sh | trigger-source | 7 static source-tree grep checks pinning the buggy line and constants | 1.9 KB | view raw |
| verify.log | run-log | verify.sh output on audit commit (all 7 pass) | 446 B | view raw |
| run.log | run-log | unprivileged run (devfs EACCES) | 231 B | view raw |
| run_root.log | run-log | root run on unpatched kernel: BUG confirmed (rc=0) | 247 B | view raw |
| run_root_stress.log | run-log | 3x root run on unpatched kernel - all BUG (rc=0) | 579 B | view raw |
| baseline.log | run-log | fresh with-src #0 baseline: BUG confirmed | 179 B | view raw |
| fix_run.log | run-log | patched #1 kernel: FIXED (EINVAL) 3/3 | 719 B | view raw |
| fix.diff | suggested-fix | reject dangerous CCB flags + clamp cdb_len in passsendccb | 2.3 KB | view raw |
| fix_build.log | build-log | nativekernel output, rc=0, 35k lines | 5.6 MB | β download |
| build.sh | build-script | cc -o df1088_trigger df1088_trigger.c | 182 B | view raw |
| run.sh | run-script | run as maxx then explain root invocation | 376 B | view raw |
| env.txt | environment | uname, cc, securelevel, /dev/pass0 mode, HW presence | 731 B | view raw |
| README.md | readme | how to reproduce + bug shape + impact | 2.4 KB | β raw |
| VERDICT.md | verdict | full narrative: mechanism, reproduction, fix | 5.2 KB | β raw |
| ../fix_build_combined.log | build-log | Combined 41-finding kernel build (rc=0, -Werror clean) | 5.6 MB | β download |
| ../fix_build_summary.txt | build-summary | Summary of the combined 41-finding kernel build | 826 B | view raw |
DF-1088 β CAM pass-through (/dev/passN) missing CCB flag validation
Build
cc -o df1088_trigger df1088_trigger.c
Run
# unprivileged (demonstrates the privilege gate) ./df1088_trigger # privileged (demonstrates the CCB-shape bug) su root -c ./df1088_trigger
Expected (bug present)
- Unprivileged:
open /dev/pass0fails withPermission denied(devfs mode 0600) β the trigger is privilege-gated. - Root (unpatched kernel):
CAMIOCOMMANDwithCAM_DIR_IN | CAM_DATA_PHYSandcdb_len=255returns 0 β the kernel forwarded the dangerous CCB to the SIM without stripping or rejecting the flags, and did not clampcdb_lentoIOCDBLEN(16). The signature line is:[root] BUG: kernel forwarded attacker physical pointer to SIM without rejection (DF-1088 present)
Expected (after fix)
- Root (patched kernel):
CAMIOCOMMANDreturns-1, errno=EINVALβpasssendccbrejects the dangerous flags / over-long CDB at the head of the function. Signature:[root] FIXED: kernel rejected dangerous CCB flags (DF-1088 patch present)
Static verification
sh verify.sh
runs 7 source-tree grep checks that pin the exact buggy line and constants.
Bug shape
passsendccb at sys/bus/cam/scsi/scsi_pass.c:565-568 gates
cam_periph_mapmem() purely on (flags & CAM_DATA_PHYS) == 0 but never
strips the flag and never rejects it; the kernel-internal XPT path
(cam_xpt.c:1133) does reject CAM_DATA_PHYS for XPT_DEV_MATCH. A
user-supplied CCB with CAM_DATA_PHYS therefore bypasses the
cam_periph_mapmem useracc/MAXPHYS/bounce-buffer path and reaches the SIM
with csio.data_ptr interpreted as a physical address for DMA. The same
function also fails to clamp csio.cdb_len to IOCDBLEN (16) for inline
CDBs, so cdb_len=255 makes the SIM read 255 bytes from the 16-byte
cdb_bytes[] array, over-reading adjacent union ccb fields including
the kernel msg_ptr / data_ptr.
Impact / preconditions
The device requires caps_priv_check_self(SYSCAP_RESTRICTEDROOT) and
securelevel <= 1 to open (scsi_pass.c:308,335), and /dev/passN is
mode 0600 in the default install. The bug is therefore not directly
reachable by an unprivileged user on a default install; impact requires
either jail-granted RESTRICTEDROOT with /dev/passN exposed, or a
process already holding the RESTRICTEDROOT capability. The CVSS vector
in the finding reflects this (AV:L/PR:L ...).
DF-1088 β No validation/stripping of dangerous user CCB flags in CAM pass
Verdict
REPRODUCED (privilege-gated runtime). The bug exists verbatim in
sys/bus/cam/scsi/scsi_pass.c:565-568; a root-originated CAMIOCOMMAND
with CAM_DIR_IN | CAM_DATA_PHYS and csio.cdb_len = 255 is accepted by
the unpatched kernel (rc=0, three-of-three deterministic runs) β i.e. the
dangerous CCB is forwarded to the SIM with the user's csio.data_ptr
interpreted as a physical address and the CDB read length un-clamped.
After the fix.diff is applied the same CCB returns EINVAL.
The trigger is gated behind caps_priv_check_self(SYSCAP_RESTRICTEDROOT)
(scsi_pass.c:308) plus a default mode-0600 devfs node, so an
unprivileged user cannot reach the bug on a default install. The finding's
realistic impact (jail escape / securelevel bypass on hosts without IOMMU)
requires a jail granted RESTRICTEDROOT with /dev/passN exposed, or an
already-capable process. The passsendccb defect itself is real, confirmed
deterministically, and closed by the fix.
Mechanism (confirmed by source trace and runtime)
passsendccb merges the user CCB into a kernel CCB via xpt_merge_ccb
(scsi_pass.c:546) and then conditionally maps user buffers:
/* scsi_pass.c:565-568 β the bug */
if (((ccb->ccb_h.flags & CAM_DATA_PHYS) == 0)
&& (((ccb->ccb_h.func_code == XPT_SCSI_IO)
&& ((ccb->ccb_h.flags & CAM_DIR_MASK) != CAM_DIR_NONE))
|| (ccb->ccb_h.func_code == XPT_DEV_MATCH))) {
...
error = cam_periph_mapmem(ccb, &mapinfo);
...
}
When the user sets CAM_DATA_PHYS (cam_ccb.h:84, 0x00200000):
- The gate is false β cam_periph_mapmem is skipped entirely.
- cam_periph_runccb (scsi_pass.c:600) still forwards the CCB through
xpt_action to the SIM/HBA.
- The SIM sees csio.data_ptr (cam_ccb.h:598) as a physical address
(the flag's documented meaning) and DMAs to/from it. For CAM_DIR_IN the
device response overwrites the attacker-chosen physical address.
The kernel-internal XPT path already knows this is unsafe β cam_xpt.c:1133
rejects CAM_DATA_PHYS for XPT_DEV_MATCH. The pass driver does not.
Two related defects sit on the same function:
-
No
cdb_lenclamp. For inline CDBs (cdb_bytes[IOCDBLEN],cam_ccb.h:587,IOCDBLEN = CAM_MAX_CDBLEN = 16,cam.h:53), acdb_lenof 255 makes the SIM read 255 bytes from the 16-byte array, over-reading adjacentcsio/union ccbfields including kernel pointers (msg_ptr,data_ptr). The same class of pointer-vs-array confusion affectsCAM_CDB_POINTER,CAM_SENSE_PTR,CAM_SENSE_PHYS,CAM_CDB_PHYSβ none of which the pass driver inspects. -
No privilege-boundary flag rejection.
CAM_DATA_PHYS,CAM_CDB_PHYS,CAM_SENSE_PHYSare kernel-internal flags (set by kernel-allocated CCBs whose buffers the kernel already DMA-mapped). They have no legitimate meaning in a user-supplied CCB. Their presence is therefore always a request to bypasscam_periph_mapmemβ and should be rejected.
Reproduction
$ ssh dfbsd-maxx 'cd poc/DF-1088 && ./df1088_trigger' # as maxx (uid 1001) [unpriv] open /dev/pass0: Permission denied (errno=13) [unpriv] pass0 access denied at devfs (mode 0600) -- DF-1088 trigger is privilege-gated [unpriv] re-run as root to exercise the CCB-shape signature via CAMIOCOMMAND # as root (the only context that can open the device) $ ./dfbsd-qemu/vm.sh run_root '/root/df1088_trigger' [unpriv] open /dev/pass0: SUCCEEDED (fd=3) -- unexpected [root] CAMIOCOMMAND CAM_DATA_PHYS|cdb_len=255: rc=0 errno=0 (Undefined error: 0) [root] BUG: kernel forwarded attacker physical pointer to SIM without rejection (DF-1088 present)
The rc=0 (CCB forwarded) is the bug signature. The PoC was run three times β all three returned rc=0 with no rejection.
Fix
fix.diff (git-apply-able) adds two guards to passsendccb immediately
after xpt_merge_ccb:
- Reject any user CCB carrying
CAM_DATA_PHYS | CAM_CDB_PHYS | CAM_SENSE_PHYS | CAM_CDB_POINTER | CAM_SENSE_PTR(all kernel-internal flags) withEINVAL. - For
XPT_SCSI_IO/XPT_CONT_TARGET_IO, rejectcsio.cdb_len > IOCDBLEN.
The pre-existing CAM_DATA_PHYS == 0 test on the cam_periph_mapmem gate
is now dead (we rejected the flag above) and is removed for clarity.
This mirrors the kernel-internal XPT pattern at cam_xpt.c:1133 and the
upstream FreeBSD fix that adds similar flag rejections to its passsendccb.
Hardware / guest notes
/dev/pass0is present on the audit guest (attached to QEMU DVD-ROM viaatapci0/cd0). No special hardware required to demonstrate the CCB flag-shape bug βpasssendccbis reached for any open of the device.- The DMA-to-arbitrary-physical-memory impact chain requires (a) an
attacker-chosen physical
data_ptrAND (b) a SCSI target that completes the I/O. (b) is present in the guest (the DVD-ROM), but the bug's real-world value is in a jail/securelevel context where a privileged capability is grantable and the device is exposed. The trigger PoC stops short of completing an actual DMA to a chosen physical address (no IOMMU bypass demonstration), but proves the kernel forwards the malicious CCB β which is the precise claim of the finding.
Fix verification
fixedvalidated
kernel build rc=0 + harness before/after
Confirmed kernel references
β
Detail
Exploit chain
none
Evidence (decisive lines)
β
Verdict
REPRODUCED (live). scsi_pass forwards dangerous CCB flags CAM_DATA_PHYS|cdb_len=255 to SIM without rejection. Root-only. Kernel rebuild fix.
No comments yet.