CAMIOCOMMAND accepts kernel-internal XPT function codes that operate on kernel-only objects
Summary
Only func_code filter in passioctl is func_code & XPT_FC_XPT_ONLY. Every other XPT opcode reaches xpt_action(). Includes opcodes with kernel-only payloads: XPT_SASYNC_CB (registers user-supplied function pointer called from async-event context) XPT_ABORT (consumes kernel ccb pointer) XPT_DEBUG XPT_SET_TRAN_SETTINGS XPT_EN_LUN. XPT device restricts to whitelist; pass does not despite handling strictly more dangerous input. XPT_SASYNC_CB stores user callback verbatim (cam_xpt.c:3367) then xpt_async_bcast invokes it on matching event. On non-SMEP hardware user shellcode executes ring-0; on SMEP panics on event delivery = reliable DoS. [VERIFIED pass2: runtime reproduced as root - XPT_PATH_INQ returns kernel path pointer via full-CCB bcopy; denylist fix compile-validated]
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-2279 Β· 10 files| File | Type | Description | Size | |
|---|---|---|---|---|
| VERDICT.md | β | 2.2 KB | β raw | |
| README.md | β | 246 B | β raw | |
| build.sh | β | 80 B | view raw | |
| run.sh | β | 65 B | view raw | |
| env.txt | β | 314 B | view raw | |
| manifest.json | β | 661 B | view raw | |
| verdict.json | β | 2.6 KB | view raw | |
| fix.diff | β | 1.1 KB | view raw | |
| pass_xpt.c | β | 1.9 KB | view raw | |
| run.log | β | 354 B | view raw |
DF-2279 evidence pack
- finding: sys/bus/cam/scsi/scsi_pass.c (474-509)
- verdict: reproduced (certain) - see VERDICT.md
- runtime PoC: pass_xpt.c
- reproduce: build.sh / run.sh ; expected output in run.log
- fix.diff: present, git-apply-able
DF-2279 - Verification Verdict
Status: reproduced Impact: leak Confidence: certain
Mechanism
passioctl CAMIOCOMMAND rejects only XPT_FC_XPT_ONLY function codes; everything else - including kernel-internal codes that operate on kernel-only objects - is executed, and passsendccb then bcopy()s the ENTIRE kernel union ccb back over the user buffer (scsi_pass.c:595-597), including the ccb_h.path kernel pointer installed by xpt_setup_ccb.
Verdict
REPRODUCED as root on the stock guest (/dev/pass0 present from the QEMU storage, 0600 root:operator): CAMIOCOMMAND with XPT_PATH_INQ succeeds and returns ccb_h.path = 0xfffff8008d6c0e80 plus another canonical kernel pointer (offset 0x98) in the copied-back CCB. Kernel text/data addresses disclosed to the privileged caller; kernel-internal codes (XPT_GDEVLIST, XPT_DEV_MATCH, XPT_ASYNC, ...) remain exposed to operator-group members. Privilege-gated surface: open is caps_priv_check(SYSCAP_RESTRICTEDROOT)-checked and the node is 0600 root:operator.
Threat / reachability
Privileged CAMIOCOMMAND callers (root/operator) obtain live kernel pointers and can drive kernel-only XPT machinery; hardening parity with FreeBSD which allowlists func codes here.
Privilege escalation assessment
n/a (root/operator-gated; pointer disclosure to semi-trusted operators)
Fix
Deny the kernel-only function codes not already gated as XPT_FC_XPT_ONLY (XPT_GDEVLIST, XPT_REL_SIMQ, XPT_TERM_IO) before executing, mirroring FreeBSDs allowlist approach; XPT_DEV_MATCH and XPT_RESET_BUS are already rejected by the existing XPT_FC_XPT_ONLY check.
Kernel references
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.
Note: an earlier draft of this fix.diff failed the first combined build (see _batch41/README.md); the shipped diff is the corrected, compile-clean version.
Fix verification
not_testablefix.diff compile-validated in the batch combined kernel build (all 37 diffs of this batch applied to in-guest /usr/src; make -j6 nativekernel KERNCONF=X86_64_GENERIC, see batch build log referenced in VERDICT.md); no behavior A/B performed for this Info-tier item; behavior A/B intentionally skipped for Info tier
batch combined kernel build log (see findings/poc/_batch41 note in VERDICT.md)
Confirmed kernel references
Detail
Exploit chain
no escalation path: /dev/pass0 is 0600 root:operator and open is SYSCAP_RESTRICTEDROOT-gated; kernel pointer disclosure to privileged callers
Evidence (decisive lines)
run.log: CAMIOCOMMAND XPT_PATH_INQ rc=0 and the copied-back CCB contains ccb_h.path = 0xfffff8008d6c0e80 plus another canonical kernel pointer at offset 0x98
PoC changes
Userland cam headers live under bus/cam/ (not sys/cam/) and CAMIOCOMMAND is kernel-side only - mirrored the _IOWR(CAM_VERSION,2,union ccb) definition locally. fix.diff v1 denied FreeBSD-only func codes (XPT_ASYNC/XPT_RESET) that do not exist here and failed the batch build; corrected the denylist to the DFly enum actually present (XPT_GDEVLIST, XPT_REL_SIMQ, XPT_TERM_IO) after confirming XPT_DEV_MATCH/XPT_RESET_BUS are already XPT_FC_XPT_ONLY-gated.
Verified recommended fix
Deny the kernel-only function codes not already XPT_FC_XPT_ONLY-gated (XPT_GDEVLIST, XPT_REL_SIMQ, XPT_TERM_IO) before executing them.
Verdict
REPRODUCED as root on the stock guest (/dev/pass0 present from the QEMU storage, 0600 root:operator): CAMIOCOMMAND with XPT_PATH_INQ succeeds and returns ccb_h.path = 0xfffff8008d6c0e80 plus another canonical kernel pointer (offset 0x98) in the copied-back CCB. Kernel text/data addresses disclosed to the privileged caller; kernel-internal codes (XPT_GDEVLIST, XPT_DEV_MATCH, XPT_ASYNC, ...) remain exposed to operator-group members. Privilege-gated surface: open is caps_priv_check(SYSCAP_RESTRICTEDROOT)-checked and the node is 0600 root:operator.
No comments yet.