DragonFlyBSD Kernel Audit
DF-1163 / harness.c
← back to finding ↓ download raw
/*
 * DF-1163 harness: gfx_v7_0_init_cp_pg_table jt_offset/jt_size OOB
 *
 * Replicates gfx_v7_0_init_cp_pg_table() (sys/dev/drm/amd/amdgpu/gfx_v7_0.c
 * :3787-3853) for Sea Islands (CIK). This is the gfx_v7 twin of DF-1134
 * (gfx_v8_0 cz_init_cp_jump_table).
 *
 *   gfx_v7_0.c:3300  cp_table_size = ALIGN(CP_ME_TABLE_SIZE * 5 * 4, 2048)
 *   gfx_v7_0.c:3301                += 64 * 1024            -> 67584 bytes
 *   cikd.h:36        #define CP_ME_TABLE_SIZE 96            -> 16896 dwords
 *
 *   gfx_v7_0.c:3800  dst_ptr = adev->gfx.rlc.cp_table_ptr;  (the cp_table BO map)
 *   for (me = 0; me < max_me; me++) {          // max_me=4 (or 5 on KAVERI)
 *       hdr = <ce|pfp|me|mec|mec2>_fw->data;
 *       fw_data = fw->data + ucode_array_offset_bytes;     // UNCHECKED
 *       table_offset = le32(hdr->jt_offset);               // UNVALIDATED u32
 *       table_size   = le32(hdr->jt_size);                 // UNVALIDATED u32
 *   gfx_v7_0.c:3847      for (i = 0; i < table_size; i++)
 *   gfx_v7_0.c:3848          dst_ptr[bo_offset + i] =
 *   gfx_v7_0.c:3849              le32(fw_data[table_offset + i]);
 *   gfx_v7_0.c:3852      bo_offset += table_size;          // accumulates across all MEs
 *   }
 *
 * No bounds checks anywhere:
 *   - OOB WRITE (CWE-787): bo_offset+i >= 16896 writes past the cp_table VRAM BO.
 *   - OOB READ  (CWE-125): table_offset+i >= fw->datasize/4 reads past the
 *     firmware kmalloc blob into adjacent kernel heap (info-leak source).
 *
 * amdgpu_ucode_validate() (amdgpu_ucode.c:256) only checks
 * fw->datasize == hdr->size_bytes and validates neither jt_offset, jt_size,
 * nor the ucode array offset.
 *
 * NOT triggerable on the audit guest: amdgpu is not in X86_64_GENERIC and the
 * guest has no AMD GPU. Harness proves the primitive by COUNTING OOB
 * write/read iterations (never performs the OOB access).
 */

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

#define ALIGN(x,a) (((x)+(a)-1) & ~((a)-1))
#define CP_ME_TABLE_SIZE 96                          /* cikd.h:36 */
#define CP_TABLE_SIZE  (ALIGN(CP_ME_TABLE_SIZE*5*4, 2048) + (64*1024))  /* gfx_v7_0.c:3300-3301 */

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

    /* Model 4 (CIK) ME firmware blobs with attacker-chosen jt_offset/jt_size
     * and a finite firmware blob size (datasize/4 dwords). */
    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 },
    };
    const int max_me = 4;

    uint32_t bo_offset = 0;
    uint32_t oob_write = 0, oob_read = 0;
    for (int m = 0; m < max_me; m++) {
        for (uint32_t i = 0; i < me[m].jt_size; i++) {
            if (bo_offset + i >= cp_dwords) oob_write++;      /* dst past cp_table BO */
            if (me[m].jt_offset + i >= me[m].fw_dwords) oob_read++; /* src past fw blob */
        }
        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 %d MEs = %u (capacity %u dwords)\n",
           max_me, bo_offset, cp_dwords);
    printf("modeled OOB WRITE iters (dst past cp_table) = %u\n", oob_write);
    printf("modeled OOB READ  iters (src past fw blob)  = %u\n", oob_read);

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

    /* ---- WITH FIX: reject jt_size/jt_offset that read past fw blob or write past cp_table ---- */
    printf("\n--- WITH FIX (validate jt bounds vs fw blob and cp_table; skip if invalid) ---\n");
    uint32_t fb = 0; uint32_t fw_ob = 0, fr_ob = 0;
    for (int m = 0; m < max_me; 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=%u ; OOB read iters remaining=%u (expect 0)\n",
           fw_ob, fr_ob);
    printf("DF-1163 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;
}