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)
PoC verification
Evidence pack
findings/poc/DF-1185 Β· 12 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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 |
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
Recommended fix
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.
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: whentarget_id == 16, dispatches toarcmsr_handle_virtual_command(acb, pccb)(line 3010).arcmsr_handle_virtual_command()line 2956:case WRITE_BUFFER: case READ_BUFFER:callsarcmsr_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 -lshows 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/pass0is 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
fixedcompile+boot validated
kernel build rc=0 + harness
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.
No comments yet.