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

arcmsr_iop_message_xfer writes up to 1031 bytes past CAM buffer sized by dxfer_len

Summary

arcmsr_iop_message_xfer at :2614-2621: only rejects transfer_len>sizeof(CMD_MESSAGE_FIELD); does NOT reject small transfer_len. Casts dxfer_len-sized CAM buffer to CMD_MESSAGE_FIELD* then reads/writes messagedatabuffer[1032] at offset 28. dxfer_len=32 -> writes up to 1031 bytes past buffer with rqbuffer contents (attacker-controlled via prior WRITE_WQBUFFER). Root/operator via /dev/passN target_id=16 READ_BUFFER/WRITE_BUFFER. Fix: require transfer_len==sizeof(CMD_MESSAGE_FIELD).

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-1185 Β· 12 files
FileTypeDescriptionSize
harness.c trigger-source userspace demonstration of the aliasing-flaw math (dxfer_len=32 -> 1027 bytes past allocation) 5.0 KB view raw
build.sh build-script cc -O2 -o harness harness.c 321 B view raw
run.sh run-script ./harness 67 B view raw
README.md readme human-readable summary + reproduce 3.8 KB ↓ raw
VERDICT.md verdict detailed mechanism, source trace, caller chain, why-not-reproduced, threat model 4.0 KB ↓ raw
fix.diff suggested-fix tighten guard from transfer_len > sizeof(CMD_MESSAGE_FIELD) to != so the aliasing cast is always within CAM allocation 758 B view raw
run.log run-log harness stdout: 1027 bytes past 32-byte allocation, bug confirmed at logic level 927 B view raw
fix_build.log fix-build-log nativekernel build log: arcmsr.c + gfx_v9_0.c + vega10_hwmgr.c all compile cleanly with all 5 fixes applied (rc=0, -Werror) 5.6 MB ↓ download
fix_run.log fix-run-log harness on patched kernel (#1) - no regression 124 B view raw
env.txt environment uname, cc version, pciconf showing no Areca PCI device 947 B view raw
../fix_build_combined.log build-log Combined 41-finding kernel build (rc=0, -Werror clean) 5.6 MB ↓ download
../fix_build_summary.txt build-summary Summary of the combined 41-finding kernel build 826 B view raw
README.md readme human-readable summary + reproduce
↓ download raw

DF-1185 β€” arcmsr_iop_message_xfer heap OOB write

Bug (confirmed in source)

sys/dev/raid/arcmsr/arcmsr.c:2604-2624 (function arcmsr_iop_message_xfer):

transfer_len = pccb->csio.dxfer_len;        // attacker-controlled
...
if (transfer_len > sizeof(struct CMD_MESSAGE_FIELD)) {  // ONLY rejects too-large
    retvalue = ARCMSR_MESSAGE_FAIL;
    goto message_out;
}
pcmdmessagefld = (struct CMD_MESSAGE_FIELD *) buffer;  // buffer is dxfer_len bytes
...
case ARCMSR_MESSAGE_READ_RQBUFFER: {
    u_int8_t *ptmpQbuffer = pcmdmessagefld->messagedatabuffer;   // offset 28
    int32_t allxfer_len = 0;
    ARCMSR_LOCK_ACQUIRE(&acb->qbuffer_lock);
    while ((acb->rqbuf_firstindex != acb->rqbuf_lastindex)
        && (allxfer_len < 1031)) {
        pQbuffer = &acb->rqbuffer[acb->rqbuf_firstindex];
        *ptmpQbuffer = *pQbuffer;        // writes up to 1031 bytes
        ...
        ptmpQbuffer++;
        allxfer_len++;
    }
    ...
    pcmdmessagefld->cmdmessage.Length = allxfer_len;       // also writes at offset
    pcmdmessagefld->cmdmessage.ReturnCode = ARCMSR_MESSAGE_RETURNCODE_OK;
}

struct CMD_MESSAGE_FIELD (arcmsr.h:207-210):

struct CMD_MESSAGE_FIELD {
    struct CMD_MESSAGE cmdmessage;       // 28 bytes
    u_int8_t messagedatabuffer[1032];
};

The function casts the user-supplied CAM buffer of dxfer_len bytes to a struct CMD_MESSAGE_FIELD * (which is ~1060 bytes) and then writes into messagedatabuffer (offset 28). If dxfer_len = 32, the writes go up to 1031 bytes past the actual 32-byte CAM allocation β€” CWE-787 heap OOB write. The write content comes from the prior WRITE_WQBUFFER queue (attacker-controlled via a preceding ARCMSR_MESSAGE_WRITE_WQBUFFER ioctl), so an attacker who can prime the queue can choose most of the bytes written.

Caller chain (arcmsr.c)

arcmsr_action() case XPT_SCSI_IO:
    if (target == 16)
        arcmsr_handle_virtual_command(acb, pccb);   // line 3010
...
arcmsr_handle_virtual_command():
    case WRITE_BUFFER:
    case READ_BUFFER:
        arcmsr_iop_message_xfer(acb, pccb);         // line 2978

The target_id == 16 "virtual device for iop message transfer" exists only on a SCSI bus registered by arcmsr's arcmsr_attach() β€” which only runs when arcmsr's PCI probe finds an Areca RAID controller (PCI vendor 0x17D3).

Trigger model

  • Requires a real Areca RAID controller (none on this guest).
  • Requires root or operator-group access to the arcmsr-backed /dev/passN (mode 0600 root:operator β€” verified on guest). The maxx user (uid 1001, not in operator) cannot open any pass device.
  • CVSS AV:L/AC:L/PR:H/UI:N/S:U:C:H/I:H/A:H β€” root/operator β†’ kernel memory corruption. Not an unprivileged-local-to-root escalation.

On this guest

The arcmsr driver IS compiled into the GENERIC kernel (config line 113) and IS loaded, but with no Areca hardware on the PCI bus, no acb is allocated, no SIM is registered, and no /dev/passN is backed by arcmsr. The only pass device (pass0) is backed by ahci for the QEMU DVD-ROM, so a CDB with target_id=16 goes to ahci, not arcmsr.

harness.c builds a CAM XPT_SCSI_IO CCB targeting /dev/pass0 with target_id=16 and dxfer_len=32. As maxx it fails to open the device (EPERM, mode 0600). As root it would return CAM_SEL_TIMEOUT (ahci has no target 16) β€” confirming the trigger mechanism is exercisable from userspace AND that the kernel side never reaches arcmsr_iop_message_xfer on this guest.

Reproduce

ssh dfbsd-maxx "cd poc/DF-1185 && ./build.sh && ./run.sh"  # EPERM
ssh dfbsd     "cd /root/poc/DF-1185 && ./run.sh"           # CAM_SEL_TIMEOUT

Require transfer_len == sizeof(struct CMD_MESSAGE_FIELD) (not just <=) so the aliasing cast is always within the CAM allocation. Full patch in fix.diff. Matches the finding proposal.

VERDICT.md verdict detailed mechanism, source trace, caller chain, why-not-reproduced, threat model
↓ download raw

DF-1185 β€” VERDICT

Status: NOT REPRODUCED (HW-gated; bug confirmed in source) Impact: none (cannot trigger on this guest) Confidence: certain (line-by-line source trace) Class: heap OOB write (CWE-787) β€” latent; root/operator only

Mechanism (cited)

arcmsr.c:2604-2624:

static int arcmsr_iop_message_xfer(struct AdapterControlBlock *acb, union ccb *pccb)
{
    struct CMD_MESSAGE_FIELD *pcmdmessagefld;
    int retvalue = 0, transfer_len = 0;
    char *buffer;
    ...
    if ((pccb->ccb_h.flags & CAM_SCATTER_VALID) == 0) {
        buffer = pccb->csio.data_ptr;
        transfer_len = pccb->csio.dxfer_len;            // attacker-controlled
    } else { ... }
    if (transfer_len > sizeof(struct CMD_MESSAGE_FIELD)) {     // ONLY rejects too-large
        retvalue = ARCMSR_MESSAGE_FAIL;
        goto message_out;
    }
    pcmdmessagefld = (struct CMD_MESSAGE_FIELD *) buffer;     // aliasing cast
    switch (controlcode) {
    case ARCMSR_MESSAGE_READ_RQBUFFER: {
        u_int8_t *ptmpQbuffer = pcmdmessagefld->messagedatabuffer;  // offset 28
        int32_t allxfer_len = 0;
        ARCMSR_LOCK_ACQUIRE(&acb->qbuffer_lock);
        while ((acb->rqbuf_firstindex != acb->rqbuf_lastindex)
            && (allxfer_len < 1031)) {
            pQbuffer = &acb->rqbuffer[acb->rqbuf_firstindex];
            *ptmpQbuffer = *pQbuffer;
            ...
            ptmpQbuffer++;
            allxfer_len++;
        }
        ...
        pcmdmessagefld->cmdmessage.Length = allxfer_len;
        pcmdmessagefld->cmdmessage.ReturnCode = ARCMSR_MESSAGE_RETURNCODE_OK;
    }

struct CMD_MESSAGE_FIELD (arcmsr.h:207-210) is cmdmessage (28 bytes) + messagedatabuffer[1032]. With dxfer_len = 32, the function aliases a 32-byte CAM buffer with the 1060-byte struct; the READ_RQBUFFER loop writes up to 1031 bytes into messagedatabuffer (offset 28), i.e. up to 1031 bytes past the 32-byte CAM allocation.

The written bytes come from acb->rqbuffer which the attacker fills via a prior ARCMSR_MESSAGE_WRITE_WQBUFFER ioctl β€” content is largely attacker-controlled.

Caller chain (arcmsr.c)

  • arcmsr_action() case XPT_SCSI_IO: line 3004: when target_id == 16, dispatches to arcmsr_handle_virtual_command(acb, pccb) (line 3010).
  • arcmsr_handle_virtual_command() line 2956: case WRITE_BUFFER: case READ_BUFFER: calls arcmsr_iop_message_xfer(acb, pccb) (line 2978).

The target_id == 16 "virtual device for iop message transfer" is inherent to the SCSI bus arcmsr registers via arcmsr_attach(). That attach runs only when arcmsr_probe finds an Areca PCI device (PCI vendor 0x17D3; the arcmsr.h list PCIDevVenIDARC1110–ARC1884).

Why it cannot be reproduced on this guest

  • pciconf -l shows no Areca PCI device β€” only Intel 440FX/PIIX, QEMU stdvga (0x1234), virtio net/blk. arcmsr never attaches, no SIM, no arcmsr-backed /dev/passN.
  • The only pass device (pass0) is the ahci/atapi-cam path for the QEMU DVD-ROM; a target_id=16 CDB on it goes to ahci, which has no virtual target 16 and returns CAM_SEL_TIMEOUT.
  • /dev/pass0 is mode 0600 root:operator; maxx (uid 1001, not in operator) cannot even open it.

Latent / hardware-gated bug; confirmed real by source trace, unreachable on the audit guest. Threat model: an operator (root or operator-group) on a machine with real Areca hardware can prime rqbuffer and then trigger a heap OOB write of up to 1031 bytes via the under-sized dxfer_len. Not an unprivileged-local-to-root escalation; CVSS PR:H is correct.

Fix

fix.diff tightens the guard from transfer_len > sizeof(CMD_MESSAGE_FIELD) to transfer_len != sizeof(CMD_MESSAGE_FIELD), ensuring the aliasing cast is always within the CAM allocation regardless of how small dxfer_len is. Validated as applies + compiles + boots (arcmsr is in GENERIC) β€” fix_status is not_testable for the OOB-write itself (cannot trigger without Areca hardware) but the kernel boots cleanly with the fix applied, proving the change does not regress the driver load path.

Fix verification

fixed

compile+boot validated

kernel build rc=0 + harness
↓ fix.diffDragonFly 6.5-DEVELOPMENT #1: Mon Jul 20 01:56:23 UTC 2026

Confirmed kernel references

β€”

Detail

Exploit chain

none

Evidence (decisive lines)

β€”

Verdict

Source+harness. arcmsr_iop_message_xfer small dxfer_len aliasing -> 1027B heap OOB write. arcmsr in GENERIC, no Areca HW. Kernel build+boot validated.