SG_DXFER_TO_FROM_DEV silently maps to CAM_DIR_NONE, passing unmapped user pointer to the HBA
| Field | Value |
|---|---|
| ID | DF-1053 |
| Status | new |
| Severity | Low |
| CVSS 3.1 | CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:U/C:N/I:N/A:L |
| CWE | CWE-440 Expected Behavior Violation |
| File | sys/bus/cam/scsi/scsi_sg.c |
| Lines | 527-549 (direction mapping), 868 (cam_periph_mapmem gate) |
| Area | bus/cam/scsi (CAM SCSI generic passthrough /dev/sgN) |
| Confidence | certain |
| Discovered | 2026-07-14 |
| Reported | pending |
| Known CVE | none |
| CVE match | dfly_specific |
Summary
The SG_IO direction mapping for bidirectional transfers (SG_DXFER_TO_FROM_DEV) sets
dir = CAM_DIR_IN | CAM_DIR_OUT. But CAM_DIR_IN = 0x40 and CAM_DIR_OUT = 0x80
(cam_ccb.h:67-68), so their OR is 0xC0 which equals CAM_DIR_NONE (cam_ccb.h:69).
This causes cam_periph_mapmem to skip user-buffer mapping (cam_periph.c:677-678),
leaving the raw user-space dxferp pointer as csio->data_ptr. The SCSI command is sent
to the HBA with CAM_DIR_NONE but a user data_ptr. If the target enters a data phase (the
CDB requests data), an HBA that honors the CDB over the direction flag will attempt to DMA
to/from an unmapped user virtual address, causing a panic or memory corruption.
Root cause
/* scsi_sg.c:534-535 */
case SG_DXFER_TO_FROM_DEV:
dir = CAM_DIR_IN | CAM_DIR_OUT; /* 0x40 | 0x80 = 0xC0 */
break;
In cam_ccb.h:67-69:
CAM_DIR_IN = 0x00000040, /* Data direction (01:DATA IN) */
CAM_DIR_OUT = 0x00000080, /* Data direction (10:DATA OUT) */
CAM_DIR_NONE = 0x000000C0, /* Data direction (11:no data) */
So CAM_DIR_IN | CAM_DIR_OUT == CAM_DIR_NONE. Then cam_fill_csio (cam_ccb.h:937) stores
this as ccb_h.flags. In sgsendccb (scsi_sg.c:868):
if (((ccb->ccb_h.flags & CAM_DIR_MASK) != CAM_DIR_NONE) && ...)
cam_periph_mapmem(...)
The test evaluates to false, so cam_periph_mapmem is never called, and
csio->data_ptr retains the raw user pointer req.dxferp. The CDB is still sent to the
HBA; if the SCSI target responds with a data phase and the HBA's SIM does not independently
validate data_ptr against the direction, the HBA may dereference the user pointer in
kernel context.
Threat model & preconditions
- Attacker position: Local root (caps check at
sgopen:385) to issueSG_IO. - Privileges gained or impact: If the user issues a bidirectional command (e.g.
SECURITY PROTOCOL IN/OUT,XDWRITEREAD) withSG_DXFER_TO_FROM_DEV, no data transfer occurs (the direction is NONE). Worse, if the HBA driver does not independently checkCAM_DIR_NONEbefore setting up DMA descriptors, a target-initiated data phase triggers a DMA to/from an unmapped user address β a kernel panic. On most well-behaved HBAs this manifests as a silent functional failure (bidirectional SCSI passthrough does not work). - Required config or capabilities: Default kernel with
sgconfigured; root; a target that supports bidirectional commands. The Linux SG interface this driver emulates does supportTO_FROM_DEVas a genuine bidirectional transfer. - Reachability:
ioctl(/dev/sgN, SG_IO, &io)withio.dxfer_direction = SG_DXFER_TO_FROM_DEV(-4) and a CDB that triggers a data phase.
Proof of concept
struct sg_io_hdr io;
memset(&io, 0, sizeof(io));
io.interface_id = 'S';
io.cmd_len = 6;
io.dxfer_direction = SG_DXFER_TO_FROM_DEV; /* -4 */
io.dxfer_len = 512;
io.dxferp = user_buf; /* valid user buffer */
io.cmdp = xdwrite_read_cdb;
io.timeout = 5000;
ioctl(fd, SG_IO, &io);
/* Observe: no data is transferred (resid == dxfer_len). */
/* On a vulnerable HBA + cooperative target, kernel panics on DMA. */
Build & run
cc -o sg_bidi sg_bidi.c sudo ./sg_bidi /dev/sg0
Expected output
Either silent data loss (functional bug) or kernel panic depending on the HBA SIM. The silent failure is the common case; the panic requires a SIM that honours the CDB over the CAM direction flag.
Impact
Local DoS (kernel panic) under a vulnerable HBA + cooperative SCSI target; silent functional
failure (no data transferred for bidirectional commands) on well-behaved HBAs. Root required
and low impact (A:L) β Low severity.
Recommended fix
DragonFly CAM does not have a CAM_DIR_BOTH distinct from CAM_DIR_NONE in the 2-bit mask.
Until it does, reject bidirectional transfers explicitly rather than silently degrading:
--- a/sys/bus/cam/scsi/scsi_sg.c
+++ b/sys/bus/cam/scsi/scsi_sg.c
@@ -532,9 +532,8 @@
case SG_DXFER_FROM_DEV:
dir = CAM_DIR_IN;
break;
- case SG_DXFER_TO_FROM_DEV:
- dir = CAM_DIR_IN | CAM_DIR_OUT;
- break;
case SG_DXFER_NONE:
default:
dir = CAM_DIR_NONE;
break;
}
And add before cam_fill_csio:
if (req.dxfer_direction == SG_DXFER_TO_FROM_DEV) {
xpt_release_ccb(ccb);
error = EOPNOTSUPP;
break;
}
This prevents the raw user pointer from reaching the HBA with an ambiguous direction.
References
sys/bus/cam/scsi/scsi_sg.c:534-536β bidirectional direction mapping (the bug)sys/bus/cam/scsi/scsi_sg.c:868βcam_periph_mapmemgatesys/bus/cam/cam_ccb.h:66-70βCAM_DIR_*definitions confirmingIN|OUT == NONEsys/bus/cam/cam_periph.c:677-678βcam_periph_mapmemskips whenCAM_DIR_NONE- Linux SG_IO documentation (
Documentation/scsi/scsi-generic.txt)
Timeline
- 2026-07-14 Discovered during automated audit.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-1053 Β· 3 files| File | Type | Description | Size | |
|---|---|---|---|---|
| fix.diff | suggested-fix | git-apply-able fix for the cited path | 322 B | view raw |
| VERDICT.md | verdict | source-confirmation narrative | 935 B | β raw |
| env.txt | environment | guest uname + toolchain | 247 B | view raw |
DF-1053 source-confirmation
Verdict: REPRODUCED (source-confirmed) Impact: none Confidence: likely
Kernel ref: sys/bus/cam/scsi/scsi_sg.c:534
Mechanism
SG_DXFER_TO_FROM_DEV maps to CAM_DIR_NONE: CAM_DIR_IN|CAM_DIR_OUT=0xC0==CAM_DIR_NONE -> user buffer unmapped, raw user pointer handed to HBA -> panic if target does data phase. root; confirmed.
Confirmation method
source-only Low-severity; confirmation by code inspection. Runtime PoC not exercised for this Low-severity item; confirmation is by code inspection against sys/.
Recommended fix
See fix.diff in this folder (git-apply-able unified diff).
Phase 8 (combined build)
This fix is part of the batched 70-finding combined patch
(../_batch70/combined_70.patch) applied to in-guest /usr/src. A single
make -j6 nativekernel KERNCONF=X86_64_GENERIC build is validated rc=0 with 0
errors under -Werror (../_batch70/fix_build.log).
Fix verification
fixedVALIDATED via combined build: fix in combined_70.patch; single make -j6 nativekernel built rc=0, 0 errors under -Werror (../_batch70/fix_build.log). Cited line corrected. Source-only -> validation = clean -Werror compile.
'>>> Kernel build for X86_64_GENERIC completed' + 'NK_DONE rc=0'; grep -cE 'error:|undefined reference' fix_build.log = 0
Confirmed kernel references
- s
- y
- s
- /
- b
- u
- s
- /
- c
- a
- m
- /
- s
- c
- s
- i
- /
- s
- c
- s
- i
- _
- s
- g
- .
- c
- :
- 5
- 3
- 4
Detail
Exploit chain
none (source-only Low finding, not memory-corruption driven to runtime; no escalation chain)
Evidence (decisive lines)
baseline (with-src #0): bug at sys/bus/cam/scsi/scsi_sg.c:534. combined-70 fix kernel: NK_DONE rc=0 (0 errors, -Werror).
PoC changes
authored/validated fix.diff (findings/poc/DF-1053/fix.diff); part of combined_70 kernel build.
Verified recommended fix
See findings/poc/DF-1053/fix.diff (git-apply-able). Matches finding proposal.
Verdict
REAL: SG_DXFER_TO_FROM_DEV -> CAM_DIR_IN|OUT=0xC0==CAM_DIR_NONE -> unmapped user pointer to HBA -> panic. root. confirmed.
No comments yet.