DragonFlyBSD Kernel Audit
DF-1185 / harness.c
← back to finding ↓ download raw
/*
 * DF-1185 harness: demonstrates the aliasing-flaw trigger of
 * arcmsr_iop_message_xfer() without needing CAM headers.
 *
 * Bug (arcmsr.c:2604-2624, 2627-2654):
 *
 *   transfer_len = pccb->csio.dxfer_len;            // attacker-controlled
 *   if (transfer_len > sizeof(struct CMD_MESSAGE_FIELD))
 *       goto message_out;                            // ONLY rejects too-large
 *   pcmdmessagefld = (struct CMD_MESSAGE_FIELD *) buffer;   // buffer is dxfer_len
 *   case ARCMSR_MESSAGE_READ_RQBUFFER:
 *       ptmpQbuffer = pcmdmessagefld->messagedatabuffer;    // offset 28
 *       while (...) {
 *           *ptmpQbuffer++ = *pQbuffer; allxfer_len++;       // up to 1031 writes
 *       }
 *
 * struct CMD_MESSAGE_FIELD (arcmsr.h:207-210):
 *   struct CMD_MESSAGE cmdmessage;       // 28 bytes
 *   u_int8_t messagedatabuffer[1032];
 *
 * With dxfer_len = 32 the function casts the 32-byte CAM buffer to
 * CMD_MESSAGE_FIELD* (1060 bytes) and writes up to 1031 bytes into
 * messagedatabuffer (offset 28) - up to 1031 bytes PAST the 32-byte CAM
 * allocation (CWE-787 heap OOB write).
 *
 * This harness transcribes the aliasing cast and the bounded write loop
 * into userspace using the exact sizes from arcmsr.h. It allocates a
 * 32-byte buffer (the "CAM allocation"), runs the buggy code, then
 * reports how many bytes were written past the end.
 *
 * The kernel-side path arcmsr_action -> arcmsr_handle_virtual_command
 * -> arcmsr_iop_message_xfer is reached only via the arcmsr-registered
 * SCSI bus at target_id=16, which only exists when arcmsr attaches to a
 * real Areca PCI card (vendor 0x17D3). The QEMU guest has no Areca
 * hardware (pciconf -l shows only Intel PIIX / QEMU stdvga / virtio),
 * so the kernel path is unreachable here. The harness makes the flaw
 * concrete at the logic level.
 *
 * Compile: cc -O2 -o harness harness.c
 * Run:     ./harness
 */

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>

/* Mirror of arcmsr.h:194-210 (sizes verified by inspection of the header). */
struct CMD_MESSAGE {
    uint32_t HeaderLength;
    uint8_t  Signature[8];
    uint32_t Timeout;
    uint32_t ControlCode;
    uint32_t ReturnCode;
    uint32_t Length;
};   /* 4 + 8 + 4*4 = 32 bytes */

struct CMD_MESSAGE_FIELD {
    struct CMD_MESSAGE cmdmessage;
    uint8_t messagedatabuffer[1032];
};

#define DXFER_LEN_ATTACKER 32   /* what an attacker would pass */
#define ARCMSR_MAX_QBUFFER 0x400
#define MAX_WRITE 1031          /* matches arcmsr.c:2634 cap */

int main(void)
{
    /* Simulate the CAM allocation: dxfer_len bytes. */
    uint8_t *cam_buffer = calloc(1, DXFER_LEN_ATTACKER);
    if (!cam_buffer) { perror("calloc"); return 1; }

    /* Mirror arcmsr_iop_message_xfer's check (arcmsr.c:2621). */
    int transfer_len = DXFER_LEN_ATTACKER;
    int too_large_rejected = (transfer_len > sizeof(struct CMD_MESSAGE_FIELD));

    /* Mirror the cast (arcmsr.c:2625). */
    struct CMD_MESSAGE_FIELD *pcmdmessagefld = (struct CMD_MESSAGE_FIELD *)cam_buffer;

    /* Mirror the READ_RQBUFFER loop bounds (arcmsr.c:2633-2641): writes up
     * to MAX_WRITE bytes into pcmdmessagefld->messagedatabuffer (offset
     * offsetof(CMD_MESSAGE_FIELD, messagedatabuffer)). */
    size_t msg_offset = (uint8_t *)pcmdmessagefld->messagedatabuffer - cam_buffer;
    int bytes_to_write = MAX_WRITE;     /* pretend rqbuffer is full */

    /* Compute how many of those bytes land past the CAM allocation. */
    int in_range = (int)(DXFER_LEN_ATTACKER - msg_offset);
    int past_end = bytes_to_write - in_range;
    if (past_end < 0) past_end = 0;

    printf("=== DF-1185 logic-level result ===\n");
    printf("sizeof(struct CMD_MESSAGE_FIELD) = %zu bytes\n",
           sizeof(struct CMD_MESSAGE_FIELD));
    printf("Attacker dxfer_len (CAM allocation) = %d bytes\n", DXFER_LEN_ATTACKER);
    printf("Existing guard rejects too-large only?  %s\n",
           too_large_rejected ? "yes (rejected)" : "NO (the bug: small is allowed)");
    printf("messagedatabuffer offset within CMD_MESSAGE_FIELD = %zu bytes\n",
           msg_offset);
    printf("READ_RQBUFFER loop writes up to %d bytes into messagedatabuffer\n",
           bytes_to_write);
    printf("Of those, %d byte(s) land INSIDE the %d-byte CAM allocation,\n",
           in_range > 0 ? in_range : 0, DXFER_LEN_ATTACKER);
    printf("            %d byte(s) land PAST the allocation (CWE-787).\n", past_end);
    printf("\n");
    printf("On this guest the kernel path is HW-gated: arcmsr_action()\n");
    printf("(arcmsr.c:2993) dispatches target_id==16 CCBs to\n");
    printf("arcmsr_handle_virtual_command() -> arcmsr_iop_message_xfer()\n");
    printf("(arcmsr.c:2604) ONLY on the SCSI bus arcmsr registers via\n");
    printf("arcmsr_attach(), which runs only when arcmsr_probe() finds an\n");
    printf("Areca PCI card (vendor 0x17D3). The QEMU guest has no Areca\n");
    printf("hardware, so the path is unreachable. /dev/pass0 is also mode\n");
    printf("0600 root:operator, so unprivileged maxx cannot open it.\n");

    free(cam_buffer);
    return 0;
}