# 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`:

```c
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.
