DragonFlyBSD Kernel Audit
DF-1134 / harness.c
← back to finding ↓ download raw
/*
 * DF-1134 harness: cz_init_cp_jump_table jt_offset/jt_size OOB (source-trace)
 *
 * Replicates cz_init_cp_jump_table() (gfx_v8_0.c:1301-1364) for Carrizo/Stoney:
 *
 *   gfx_v8_0.c:1321  table_offset = le32(hdr->jt_offset);   // u32, UNVALIDATED
 *   gfx_v8_0.c:1322  table_size   = le32(hdr->jt_size);     // u32, UNVALIDATED
 *   gfx_v8_0.c:1318  fw_data = fw->data + ucode_array_offset_bytes;   // also unchecked
 *   gfx_v8_0.c:1357  for(i=0;i<table_size;i++)
 *   gfx_v8_0.c:1358      dst_ptr[bo_offset+i] = le32(fw_data[table_offset+i]);
 *   gfx_v8_0.c:1362  bo_offset += table_size;   // accumulates across 4..5 ME fw blobs
 *
 * dst_ptr = cp_table_ptr: a VRAM BO of cp_table_size =
 *   ALIGN(96*5*4, 2048) + 64*1024 = 2048 + 65536 = 67584 bytes (gfx_v8_0.c:1407).
 *   cp_table_size/4 = 16896 dwords.
 *
 * fw_data points into the request_firmware() kmalloc blob (fw->datasize bytes).
 *
 * No bounds checks anywhere; amdgpu_ucode_validate() only checks
 * fw->datasize == hdr->size_bytes.
 *
 *   -> OOB WRITE: bo_offset+i >= 16896 writes past the cp_table VRAM BO
 *      (corrupts adjacent VRAM / faults).
 *   -> OOB READ:  table_offset+i >= datasize/4 reads past the firmware blob
 *      into adjacent kernel heap (info leak source).
 *
 * Requires an AMD Carrizo/Stoney APU (amdgpu) loading crafted CE/PFP/ME/MEC
 * firmware headers. Not present on the audit QEMU guest.
 */

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

#define ALIGN(x,a) (((x)+(a)-1) & ~((a)-1))
#define CP_TABLE_SIZE  (ALIGN(96*5*4, 2048) + (64*1024))   /* gfx_v8_0.c:1407 */

int main(void)
{
    uint32_t cp_dwords = CP_TABLE_SIZE / 4;
    printf("cp_table_size = %d bytes (%d dwords capacity for dst_ptr[])\n\n",
           CP_TABLE_SIZE, cp_dwords);

    /* Model the 4 (or 5) ME firmware blobs, each contributing a jt_offset/jt_size
     * that a malicious firmware header could set. */
    struct { const char *name; uint32_t jt_offset; uint32_t jt_size; uint32_t fw_dwords; }
    me[] = {
        { "CE",   0x10,       4000,   1024 },  /* jt_size 4000 alone is fine, but... */
        { "PFP",  0x10000,    200,    1024 },  /* jt_offset way past fw blob -> OOB read */
        { "ME",   0x20,       16896,  2048 },  /* jt_size exhausts whole dst capacity */
        { "MEC",  0x30,       100,    512 },
    };

    uint32_t bo_offset = 0;
    int oob_write = 0, oob_read = 0;
    for (int m = 0; m < 4; m++) {
        /* emulate the per-ME loop bounds */
        for (uint32_t i = 0; i < me[m].jt_size; i++) {
            uint32_t dst_idx = bo_offset + i;
            uint32_t src_idx = me[m].jt_offset + i;
            if (dst_idx >= cp_dwords) oob_write++;
            if (src_idx >= me[m].fw_dwords) oob_read++;
        }
        bo_offset += me[m].jt_size;
        printf("ME[%s] jt_offset=0x%x jt_size=%-5u fw_dwords=%-5u -> bo_offset now %u\n",
               me[m].name, me[m].jt_offset, me[m].jt_size, me[m].fw_dwords, bo_offset);
    }
    printf("\ncumulative bo_offset after all MEs = %u (capacity %u dwords)\n",
           bo_offset, cp_dwords);
    printf("modeled OOB WRITE iterations (dst past cp_table) = %d\n", oob_write);
    printf("modeled OOB READ  iterations (src past fw blob)  = %d\n", oob_read);

    if (oob_write > 0 || oob_read > 0 || bo_offset > cp_dwords) {
        printf("\nDF-1134: CONFIRMED OOB write past cp_table VRAM BO AND/OR OOB read past firmware blob\n");
    } else {
        printf("DF-1134: NOT reproduced\n");
    }

    /* ---- WITH FIX: validate jt_offset+jt_size<=fw_dwords and bo_offset+jt_size<=cp_dwords ---- */
    printf("\n--- WITH FIX (validate jt bounds vs fw blob and cp_table; skip if invalid) ---\n");
    uint32_t fb = 0; int fw_ob=0, fr_ob=0;
    for (int m = 0; m < 4; m++) {
        uint32_t ts = me[m].jt_size, to = me[m].jt_offset, fwd = me[m].fw_dwords;
        int reject = (ts > fwd) || (to > fwd - ts) ||
                     (ts > cp_dwords) || (fb > cp_dwords - ts);
        printf("ME[%s] jt_off=0x%-6x jt_size=%-5u fw_dw=%-5u -> %s\n",
               me[m].name, to, ts, fwd, reject ? "REJECTED (skip, safe)" : "accepted");
        if (!reject) {
            for (uint32_t i=0;i<ts;i++){
                if (fb+i >= cp_dwords) fw_ob++;
                if (to+i >= fwd) fr_ob++;
            }
            fb += ts;
        }
    }
    printf("FIX result: OOB write iters remaining=%d ; OOB read iters remaining=%d (expect 0)\n", fw_ob, fr_ob);
    printf("DF-1134 FIX: %s\n", (fw_ob==0 && fr_ob==0) ?
           "VALIDATED - bounds validation rejects all OOB jt_offset/jt_size" : "INCOMPLETE");
    return (oob_write > 0 || oob_read > 0 || bo_offset > cp_dwords) ? 0 : 1;
}