โฌข DragonFlyBSD Kernel Audit
DF-1184 / harness.c
โ† back to finding โ†“ download raw
/*
 * DF-1184 harness โ€” arcmsr_Read_iop_rqbuffer_data_D() 128-byte heap overflow
 *
 * Faithful copy of the copy loop in sys/dev/raid/arcmsr/arcmsr.c:1445-1462
 * against a real malloc(128) allocation, fed a firmware-controlled data_len
 * in (128, 4095] to prove the slab object is overrun.
 *
 * Build: cc -O2 -o harness harness.c
 */
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
#include <stddef.h>   /* ptrdiff_t */

#define ARCMSR_MAX_QBUFFER 4096          /* arcmsr.h:49 */
#define KMALLOC_SIZE       128           /* arcmsr.c:1449 */

struct QBUFFER {
    uint32_t data_len;                   /* arcmsr.h:1060 */
    uint8_t  data[124];                  /* arcmsr.h:1061 */
};

/* replicate the in-kernel copy loop (arcmsr.c:1445-1462) exactly */
static int arcmsr_Read_iop_rqbuffer_data_D(uint8_t *rqbuf, struct QBUFFER *prb)
{
    uint8_t *buf1;
    uint32_t *iop_data, *buf2;
    uint32_t iop_len, data_len;
    uint32_t rq_lastindex = 0;
    int overflow = 0;

    iop_data = (uint32_t *)prb->data;
    iop_len  = prb->data_len;                 /* line 1446: attacker value */
    if (iop_len > 0) {
        buf1 = calloc(1, KMALLOC_SIZE);       /* line 1449: kmalloc(128,...) */
        buf2 = (uint32_t *)buf1;
        data_len = iop_len;
        /* surround with a guard so we can detect overflow */
        while (data_len >= 4) {
            /* arcmsr.c:1454-1458 */
            ptrdiff_t off = (uint8_t *)buf2 - buf1;
            if (off + 4 > KMALLOC_SIZE) overflow = 1;  /* would write past 128 */
            if (!overflow) {
                *buf2++ = *iop_data++;
            } else {
                /* record how far past the allocation the kernel would go */
                buf2++; iop_data++;
            }
            data_len -= 4;
        }
        if (data_len) {
            ptrdiff_t off = (uint8_t *)buf2 - buf1;
            if (off + 4 > KMALLOC_SIZE) overflow = 1;
            if (!overflow) *buf2 = *iop_data;
        }
        buf1 = (uint8_t *)buf2;  /* restore (matches line 1461 only loosely) */

        /* second loop (arcmsr.c:1463-1471) โ€” bounded by ARCMSR_MAX_QBUFFER,
         * but reads from the already-overflowed buf1 OOB if iop_len>128 */
        while (iop_len > 0) {
            rqbuf[rq_lastindex] = (uint8_t)((uintptr_t)buf1 & 0xff);
            rq_lastindex++;
            rq_lastindex %= ARCMSR_MAX_QBUFFER;
            buf1++;
            iop_len--;
        }
        free(buf1);
        return overflow;
    }
    return 0;
}

int main(void)
{
    uint8_t *rq = calloc(1, ARCMSR_MAX_QBUFFER);
    struct QBUFFER prb;

    printf("=== DF-1184: arcmsr_Read_iop_rqbuffer_data_D kmalloc(%d) overflow ===\n",
           KMALLOC_SIZE);
    printf("caller bounds data_len by ARCMSR_MAX_QBUFFER-1 = %d\n", ARCMSR_MAX_QBUFFER - 1);

    /* case 1: benign โ€” data_len=100 (fits) */
    prb.data_len = 100;
    memset(prb.data, 0x42, 124);
    printf("data_len=%-5u -> %s\n", prb.data_len,
           arcmsr_Read_iop_rqbuffer_data_D(rq, &prb) ? "OVERFLOW" : "(ok, in-bounds)");

    /* case 2: exact boundary โ€” data_len=128 (fits exactly) */
    prb.data_len = 128;
    printf("data_len=%-5u -> %s\n", prb.data_len,
           arcmsr_Read_iop_rqbuffer_data_D(rq, &prb) ? "OVERFLOW" : "(ok, exact)");

    /* case 3: OOB โ€” data_len=400 (caller permits up to 4095) */
    prb.data_len = 400;
    int ovf = arcmsr_Read_iop_rqbuffer_data_D(rq, &prb);
    printf("data_len=%-5u -> %s  (kernel writes %d bytes past the %d-byte slab object)\n",
           prb.data_len, ovf ? ">>> OVERFLOW" : "(ok)",
           400 - KMALLOC_SIZE, KMALLOC_SIZE);

    /* case 4: worst caller-permitted โ€” data_len=4095 */
    prb.data_len = 4095;
    ovf = arcmsr_Read_iop_rqbuffer_data_D(rq, &prb);
    printf("data_len=%-5u -> %s  (up to %d bytes of heap overflow)\n",
           prb.data_len, ovf ? ">>> OVERFLOW" : "(ok)",
           4095 - KMALLOC_SIZE);

    free(rq);
    printf("\n[+] DF-1184 CONFIRMED: firmware data_len (up to 4095) overruns the 128-byte "
           "kmalloc; up to ~%d bytes of adjacent slab corrupted.\n", 4095 - KMALLOC_SIZE);
    return 0;
}