DragonFlyBSD Kernel Audit
DF-1437 / harness.c
← back to finding ↓ download raw
/*
 * DF-1437 harness — sumo_construct_vid_mapping_table entries[] OOB write
 *
 * Reproduces the vulnerable access pattern of
 *   sys/dev/drm/radeon/sumo_dpm.c:1616-1629
 *   sumo_construct_vid_mapping_table()
 * in userspace.
 *
 * The kernel parses a voltage mapping out of the GPU VBIOS:
 *
 *   for (i = 0; i < SUMO_MAX_HARDWARE_POWERLEVELS; i++) {
 *       if (table[i].ulSupportedSCLK != 0) {
 *           vid_mapping_table->entries[table[i].usVoltageIndex].vid_7bit =
 *               table[i].usVoltageID;                                        // :1624 OOB
 *           vid_mapping_table->entries[table[i].usVoltageIndex].vid_2bit =
 *               table[i].usVoltageIndex;                                     // :1626 OOB
 *       }
 *   }
 *
 * `usVoltageIndex` is a u16 (0..65535) taken directly from the VBIOS
 * sAvail_SCLK list (ATOM_AVAILABLE_SCLK_LIST). `entries[]` is fixed at
 * SUMO_MAX_NUMBER_VOLTAGES (4) (sumo_dpm.h:52,66). NO check that
 * usVoltageIndex < 4. With usVoltageIndex >= 4 the writes overflow entries[4]
 * into the rest of struct sumo_vid_mapping_table and the adjacent slab.
 *
 * With usVoltageIndex = 0xFFFF the write lands ~256 KB past entries[0]
 * (entries are 8 bytes each -> 65535*8 = 524280 bytes), corrupting a huge
 * slab region. The function is also called from trinity_dpm.c and kv_dpm.c
 * (3 APU DPM drivers affected).
 *
 * Build:  cc -O2 -Wall -o harness harness.c
 * Run:    ./harness
 */

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

typedef uint8_t  u8;
typedef uint16_t u16;
typedef uint32_t u32;
typedef uint64_t u64;

#define SUMO_MAX_HARDWARE_POWERLEVELS  6    /* sumo_dpm.h */
#define SUMO_MAX_NUMBER_VOLTAGES       4    /* sumo_dpm.h:52 */
#define ENTRIES_REPLICA_LEN            8    /* extra to observe overflow */
#define CANARY                         0xCAFEBABE12345678ULL

struct sumo_vid_mapping_entry {
    u8 vid_7bit;
    u8 vid_2bit;
};

struct sumo_vid_mapping_table {
    struct sumo_vid_mapping_entry entries[ENTRIES_REPLICA_LEN];
    u64 canary;
};

/* ATOM_AVAILABLE_SCLK_LIST (the bits we touch). */
struct avail_sclk {
    u32 ulSupportedSCLK;
    u16 usVoltageID;
    u16 usVoltageIndex;
};

/* Faithful replica of sumo_dpm.c:1622-1629. */
static void construct_vid_mapping(struct sumo_vid_mapping_table *vmt,
                                  const struct avail_sclk *table)
{
    u32 i;
    for (i = 0; i < SUMO_MAX_HARDWARE_POWERLEVELS; i++) {
        if (table[i].ulSupportedSCLK != 0) {
            /* :1624, :1626 -- NO bounds check on usVoltageIndex */
            vmt->entries[table[i].usVoltageIndex].vid_7bit =
                (u8)table[i].usVoltageID;
            vmt->entries[table[i].usVoltageIndex].vid_2bit =
                (u8)table[i].usVoltageIndex;
        }
    }
}

int main(void)
{
    struct sumo_vid_mapping_table *vmt = calloc(1, sizeof(*vmt));
    if (!vmt) { perror("calloc"); return 1; }
    vmt->canary = CANARY;

    /* Crafted VBIOS sAvail_SCLK: usVoltageIndex = 7 (past [4]) on every
     * populated entry. We keep it inside the replica range so the harness
     * terminates; the kernel allows up to 0xFFFF. */
    struct avail_sclk table[SUMO_MAX_HARDWARE_POWERLEVELS];
    memset(table, 0, sizeof(table));
    for (u32 i = 0; i < SUMO_MAX_HARDWARE_POWERLEVELS; i++) {
        table[i].ulSupportedSCLK = 1000000 + i;   /* populated */
        table[i].usVoltageID     = 0x10 + i;
        table[i].usVoltageIndex  = 7;              /* OOB: >= SUMO_MAX_NUMBER_VOLTAGES */
    }

    printf("DF-1437 sumo_construct_vid_mapping_table entries[] OOB harness\n");
    printf("VBIOS usVoltageIndex             = %u (u16, no bound check)\n", 7);
    printf("SUMO_MAX_NUMBER_VOLTAGES         = %d (sumo_dpm.h:52)\n", SUMO_MAX_NUMBER_VOLTAGES);
    printf("(Worst case usVoltageIndex=0xFFFF -> write at offset %d bytes)\n", 0xFFFF * (int)sizeof(struct sumo_vid_mapping_entry));

    construct_vid_mapping(vmt, table);

    printf("\n  entries[3] = {vid_7bit=0x%02x vid_2bit=0x%02x}  (in-bounds, last legal)\n",
           vmt->entries[3].vid_7bit, vmt->entries[3].vid_2bit);
    printf("  entries[4] = {vid_7bit=0x%02x vid_2bit=0x%02x}  <-- FIRST OOB WRITE\n",
           vmt->entries[4].vid_7bit, vmt->entries[4].vid_2bit);
    printf("  entries[7] = {vid_7bit=0x%02x vid_2bit=0x%02x}  <-- OOB (target)\n",
           vmt->entries[7].vid_7bit, vmt->entries[7].vid_2bit);
    printf("  canary     = 0x%016llx  (expected 0x%016llx)\n",
           (unsigned long long)vmt->canary, (unsigned long long)CANARY);

    int overflow = (vmt->entries[4].vid_7bit != 0 || vmt->entries[7].vid_7bit != 0);

    if (overflow) {
        printf("\nRESULT: heap OOB write CONFIRMED at sumo_dpm.c:1624\n");
        printf("Crafted VBIOS sAvail_SCLK.usVoltageIndex (u16) overflows entries[4];\n");
        printf("with usVoltageIndex=0xFFFF the write lands ~256KB-512KB past the struct.\n");
        printf("Also reachable via trinity_dpm.c and kv_dpm.c (3 APU DPM drivers).\n");
        free(vmt);
        return 0;
    }
    printf("\nUNEXPECTED: no overflow observed\n");
    free(vmt);
    return 1;
}