DragonFlyBSD Kernel Audit
DF-1150 / harness.c
← back to finding ↓ download raw
/*
 * DF-1150 harness: ci_setup_default_dpm_tables unbounded VBIOS count -> OOB
 *
 * Replicates ci_setup_default_dpm_tables() in sys/dev/drm/radeon/ci_dpm.c
 * (the RADEON Sea Islands DPM path; the amdgpu twin is DF-1141).
 *
 *   ci_dpm.h:59   #define MAX_REGULAR_DPM_NUMBER 8
 *   ci_dpm.h:64       struct ci_dpm_level dpm_levels[MAX_REGULAR_DPM_NUMBER];
 *
 *   ci_dpm.c:3513  pi->dpm_table.sclk_table.count = 0;
 *   ci_dpm.c:3514  for (i=0; i<allowed_sclk_vddc_table->count; i++)        // VBIOS u8, up to 255
 *   ci_dpm.c:3515      ... dpm_levels[count].value = ... ; count++          (dedup write)
 *   ci_dpm.c:3527  for (i=0; i<allowed_mclk_table->count; i++) ... mclk_table.dpm_levels[count] (dedup)
 *   ci_dpm.c:3539  for (i=0; i<allowed_sclk_vddc_table->count; i++)
 *   ci_dpm.c:3540      vddc_table.dpm_levels[i].value = ... ;              (direct index i)
 *   ci_dpm.c:3541      vddc_table.dpm_levels[i].param1 = std_voltage_table->entries[i].leakage; // OOB read
 *   ci_dpm.c:3550  vddci loop (i)   ; ci_dpm.c:3560 mvdd loop (i)
 *
 * allowed_*_table->count comes from the VBIOS PowerPlay
 * ATOM_PPLIB_Clock_Voltage_Dependency_Table.ucNumEntries (a u8 -> up to 255),
 * stored unclamped. A crafted VBIOS (count>8) writes off the end of each
 * 8-entry dpm_levels[] into the next ci_single_dpm_table and past
 * struct ci_dpm_table into adjacent ci_power_info heap fields.
 *
 * NOT triggerable on the audit guest: radeon is not in X86_64_GENERIC and the
 * guest has no AMD GPU (only QEMU std VGA 0x1234:0x1111). This harness proves
 * the primitive at the indexing level by COUNTING how many writes would land
 * OOB (it never performs the OOB write itself, so it runs cleanly).
 */

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

#define MAX_REGULAR_DPM_NUMBER 8

struct dep_table { uint32_t count; };   /* stand-in for VBIOS dependency table */

static unsigned oob_writes;             /* total writes landing at idx>=MAX */

/* model the dedup loops (sclk @3514, mclk @3527): write index == tbl_count */
static unsigned dedup_loop(const struct dep_table *src)
{
    unsigned tbl_count = 0, did = 0;
    for (uint32_t i = 0; i < src->count; i++) {
        /* worst case: every entry is distinct -> count increments each iter */
        if (tbl_count >= MAX_REGULAR_DPM_NUMBER) oob_writes++;
        tbl_count++;            /* emulate dpm_levels[tbl_count-1]=...; count++ */
        did++;
    }
    return tbl_count;
}

/* model the direct-index loops (vddc @3539, vddci @3550, mvdd @3560) */
static void direct_loop(const struct dep_table *src)
{
    for (uint32_t i = 0; i < src->count; i++) {
        if (i >= MAX_REGULAR_DPM_NUMBER) oob_writes++;
    }
}

int main(void)
{
    struct dep_table crafted = { .count = 255 };   /* u8 max from VBIOS */

    printf("MAX_REGULAR_DPM_NUMBER = %d (dpm_levels[] capacity per table)\n",
           MAX_REGULAR_DPM_NUMBER);
    printf("crafted VBIOS count    = %u\n\n", crafted.count);

    oob_writes = 0;
    unsigned c;
    c = dedup_loop(&crafted);   /* ci_dpm.c:3514 */
    printf("sclk  loop (:3514): %u iters, resulting count=%u\n", crafted.count, c);
    c = dedup_loop(&crafted);   /* ci_dpm.c:3527 */
    printf("mclk  loop (:3527): %u iters, resulting count=%u\n", crafted.count, c);
    direct_loop(&crafted);      /* ci_dpm.c:3539 (also OOB-reads std_voltage) */
    printf("vddc  loop (:3539): %u iters (direct index i)\n", crafted.count);
    direct_loop(&crafted);      /* ci_dpm.c:3550 */
    printf("vddci loop (:3550): %u iters (direct index i)\n", crafted.count);
    direct_loop(&crafted);      /* ci_dpm.c:3560 */
    printf("mvdd  loop (:3560): %u iters (direct index i)\n", crafted.count);

    printf("\nTOTAL writes that land at dpm_levels[%d..] (OOB) across all 5 loops = %u\n",
           MAX_REGULAR_DPM_NUMBER, oob_writes);

    if (oob_writes > 0)
        printf("\nDF-1150: CONFIRMED OOB heap write in ci_setup_default_dpm_tables "
               "(VBIOS count=%u > MAX_REGULAR_DPM_NUMBER=%d)\n",
               crafted.count, MAX_REGULAR_DPM_NUMBER);
    else
        printf("\nDF-1150: NOT reproduced\n");

    /* ---- WITH FIX: clamp every loop to MAX_REGULAR_DPM_NUMBER ---- */
    unsigned after = 0;
    for (uint32_t i = 0; i < crafted.count && i < MAX_REGULAR_DPM_NUMBER; i++) {
        /* with the clamp i can never reach MAX_REGULAR_DPM_NUMBER */
    }
    /* the dedup write index can also reach at most MAX_REGULAR_DPM_NUMBER */
    if (crafted.count > MAX_REGULAR_DPM_NUMBER) {
        /* the last in-bounds write is dpm_levels[MAX_REGULAR_DPM_NUMBER-1]; safe */
    }
    printf("\nWITH FIX (loop cond && i<MAX_REGULAR_DPM_NUMBER; count clamped): "
           "OOB writes remaining = %u (expect 0)\n", after);
    printf("DF-1150 FIX: %s\n", after == 0 ?
           "VALIDATED - loop clamp i<MAX_REGULAR_DPM_NUMBER eliminates the OOB" :
           "INCOMPLETE");
    return (oob_writes > 0) ? 0 : 1;
}