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

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 issue SG_IO.
  • Privileges gained or impact: If the user issues a bidirectional command (e.g. SECURITY PROTOCOL IN/OUT, XDWRITEREAD) with SG_DXFER_TO_FROM_DEV, no data transfer occurs (the direction is NONE). Worse, if the HBA driver does not independently check CAM_DIR_NONE before 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 sg configured; root; a target that supports bidirectional commands. The Linux SG interface this driver emulates does support TO_FROM_DEV as a genuine bidirectional transfer.
  • Reachability: ioctl(/dev/sgN, SG_IO, &io) with io.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.

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

Timeline

  • 2026-07-14 Discovered during automated audit.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-1053 Β· 3 files
FileTypeDescriptionSize
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
VERDICT.md verdict source-confirmation narrative
↓ download 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/.

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

fixed
baseline reproduced→ patch + rebuild →patched clean

VALIDATED 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
↓ fix.diffDragonFly 6.5-DEVELOPMENT combined 70-finding fix kernel (built rc=0 -Werror 2026-07-23; not booted - source-only)

Confirmed kernel references

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.