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

SG_IO sense-data copyout reads past csio->sense_data when sense_resid exceeds mx_sb_len

Field Value
ID DF-1052
Status new
Severity Low
CVSS 3.1 CVSS:3.1/AV:L/AC:H/PR:H/UI:N/S:U/C:L/I:N/A:N
CWE CWE-125 Out-of-bounds Read; CWE-190 Integer Overflow or Wraparound
File sys/bus/cam/scsi/scsi_sg.c
Lines 570-575 (sb_len_wr arithmetic and copyout)
Area bus/cam/scsi (CAM SCSI generic passthrough /dev/sgN)
Confidence likely
Discovered 2026-07-14
Reported pending
Known CVE none
CVE match dfly_specific

Summary

In the SG_IO ioctl, the number of sense bytes copied to userspace is computed as req.sb_len_wr = req.mx_sb_len - csio->sense_resid with no validation that sense_resid <= mx_sb_len. Both fields are 8-bit unsigned. If sense_resid > mx_sb_len (e.g. sense_resid=17, mx_sb_len=16), the subtraction wraps to a large u_char (255), and copyout reads up to 255 bytes from csio->sense_data, which is only 32 bytes (struct scsi_sense_data, scsi_all.h:911-954). The over-read leaks adjacent csio fields (cdb_io, data_ptr, msg_ptr β€” kernel pointers) to userspace.

Root cause

/* scsi_sg.c:570-575 */
error = copyout(&req, ap->a_data, sizeof(req));
if ((error == 0) && (csio->ccb_h.status & CAM_AUTOSNS_VALID)
    && (req.sbp != NULL)) {
    req.sb_len_wr = req.mx_sb_len - csio->sense_resid;     /* unsigned wrap */
    error = copyout(&csio->sense_data, req.sbp,
                    req.sb_len_wr);                          /* over-read source */
}

req.mx_sb_len is u_char (user-controlled, 0-255). csio->sense_resid is u_int8_t set by the SIM (HBA driver) during autosense completion. The subtraction is computed in int (after promotion), but req.sb_len_wr is u_char, so a negative result wraps modulo 256. At line 573-574, copyout(&csio->sense_data, req.sbp, req.sb_len_wr) uses the wrapped value as the byte count. sense_data is a 32-byte struct embedded in ccb_scsiio (cam_ccb.h:601). Reading 255 bytes starting at sense_data copies 223 bytes past it, into sense_len, cdb_len, sglist_cnt, scsi_status, sense_resid, resid, cdb_io, msg_ptr (a kernel pointer), etc.

The sense_resid field is annotated "2's comp" in cam_ccb.h:606, suggesting historical ambiguity in its interpretation that increases the chance of a SIM setting it to a value larger than sense_len.

Threat model & preconditions

  • Attacker position: Local root (caps check at sgopen:385) to issue SG_IO.
  • Privileges gained or impact: Info leak of adjacent csio fields when the wrap triggers. The leaked kernel pointers (msg_ptr, data_ptr from adjacent csio fields) aid KASLR bypass for follow-on exploitation.
  • Required config or capabilities: Default kernel with sg configured. Root to issue SG_IO. A malicious or buggy SCSI target (USB mass-storage gadget, iSCSI target, virtual SCSI device in a VM) combined with a SIM that miscalculates sense_resid can trigger the wrap.
  • Reachability: ioctl(/dev/sgN, SG_IO, &io) with io.mx_sb_len set to a value less than the sense_resid the SIM ends up populating.

Proof of concept

#include <sys/ioctl.h>
#include <fcntl.h>
#include <string.h>
#include <stdint.h>
#include "scsi_sg.h"
int main(void) {
    int fd = open("/dev/sg0", O_RDWR);
    struct sg_io_hdr io;
    uint8_t cdb[6] = {0x12, 0, 0, 0, 16, 0};    /* INQUIRY, alloc=16 */
    uint8_t buf[256];
    uint8_t sense[255];
    memset(&io, 0, sizeof(io));
    io.interface_id = 'S';
    io.cmd_len = 6;
    io.mx_sb_len = 16;                            /* small sense buffer */
    io.dxfer_direction = SG_DXFER_FROM_DEV;
    io.dxfer_len = 256;
    io.dxferp = buf;
    io.cmdp = cdb;
    io.sbp = sense;
    io.timeout = 5000;
    ioctl(fd, SG_IO, &io);
    /* If target+SIM cause sense_resid=17, io.sb_len_wr wraps to 255 */
    /* sense[] now contains 223 bytes past sense_data: kernel pointers */
    printf("sb_len_wr = %u\n", io.sb_len_wr);
    return 0;
}

Build & run

cc -o sg_sense_oob sg_sense_oob.c
sudo ./sg_sense_oob /dev/sg0

Expected output

Success = sb_len_wr > 32 with the sense buffer containing non-zero bytes beyond offset 32 (kernel heap data). Even without a malicious target, the unguarded arithmetic is a latent vulnerability for any future SIM regression.

Impact

Info leak of csio fields beyond sense_data when a SIM sets sense_resid greater than the requested mx_sb_len. High-privilege prerequisite (root), high complexity (need a SIM to set the bad resid), and only C:L impact (limited kernel memory read for KASLR bypass) keep this at Low severity. The driver should defensively clamp the value regardless of SIM behavior.

Clamp sb_len_wr to not exceed the sense_data struct size and ensure it is non-negative:

--- a/sys/bus/cam/scsi/scsi_sg.c
+++ b/sys/bus/cam/scsi/scsi_sg.c
@@ -569,8 +569,12 @@
        error = copyout(&req, ap->a_data, sizeof(req));
        if ((error == 0) && (csio->ccb_h.status & CAM_AUTOSNS_VALID)
            && (req.sbp != NULL)) {
-           req.sb_len_wr = req.mx_sb_len - csio->sense_resid;
-           error = copyout(&csio->sense_data, req.sbp,
+           if (csio->sense_resid >= req.mx_sb_len)
+               req.sb_len_wr = 0;
+           else
+               req.sb_len_wr = req.mx_sb_len - csio->sense_resid;
+           req.sb_len_wr = min(req.sb_len_wr, sizeof(csio->sense_data));
+           error = copyout(&csio->sense_data, req.sbp,
                    req.sb_len_wr);
        }

References

Timeline

  • 2026-07-14 Discovered during automated audit.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-1052 Β· 3 files
FileTypeDescriptionSize
fix.diff suggested-fix git-apply-able fix for the cited path 565 B view raw
VERDICT.md verdict source-confirmation narrative 928 B ↓ raw
env.txt environment guest uname + toolchain 247 B view raw
VERDICT.md verdict source-confirmation narrative
↓ download raw

DF-1052 source-confirmation

Verdict: REPRODUCED (source-confirmed) Impact: none Confidence: likely

Kernel ref: sys/bus/cam/scsi/scsi_sg.c:572

Mechanism

SG_IO sense copyout over-read: sb_len_wr=mx_sb_len-sense_resid wraps when sense_resid>mx_sb_len; copyout reads past 32-byte sense_data leaking kernel pointers. root+buggy SIM; 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:572. combined-70 fix kernel: NK_DONE rc=0 (0 errors, -Werror).

PoC changes

authored/validated fix.diff (findings/poc/DF-1052/fix.diff); part of combined_70 kernel build.

Verified recommended fix

See findings/poc/DF-1052/fix.diff (git-apply-able). Matches finding proposal.

Verdict

REAL: SG_IO sb_len_wr=mx_sb_len-sense_resid wraps when sense_resid>mx_sb_len; copyout reads past 32-byte sense_data leaking kptrs. root+buggy SIM. confirmed.