โฌข DragonFlyBSD Kernel Audit
DF-4036 / polaris_walks.c
โ† back to finding โ†“ download raw
/*
 * DF-4036..DF-4041 guest demonstrator โ€” polaris10_smumgr.c SMC-table walks.
 *
 * Reproduces, in userspace, the exact index arithmetic of:
 *   polaris10_populate_all_graphic_levels   (polaris10_smumgr.c:1000-1017)  -> GraphicsLevel[8]  [DF-4036a]
 *   polaris10_populate_all_memory_levels    (polaris10_smumgr.c:1138-1148)  -> MemoryLevel[4]    [DF-4036b]
 *   polaris10_program_memory_timing_parameters (polaris10_smumgr.c:1365-1380) -> STACK MCArb entries[8][4] [DF-4036c]
 *   polaris10_populate_cac_table            (polaris10_smumgr.c:712-718)    -> BapmVddcVid{Lo,Hi}Sidd[16] + entries[32] read [DF-4037]
 *   polaris10_populate_clock_stretcher_data_table (:1555-1574) -> Sclk_voltageOffset[8] [DF-4038a]
 *   polaris10_populate_avfs_parameters      (:1688-1691)      -> sclk dep entries[i<NUM_VFT_COLUMNS] read [DF-4038b]
 *   polaris10_get_dependency_volt_by_clk    (:396-411)        -> entries[count] x2 one-entry heap OOB read [DF-4039]
 *   polaris10_populate_smc_vce_level/uvd_level (:1298/:1404), populate_cac_table (:712) -> NULL derefs [DF-4040]
 *   polaris10_populate_smc_mvdd_table       (:651-663)        -> entries[count] wrong-index read [DF-4041a]
 *   polaris10_populate_mvdd_value           (:1183-1188)      -> entries[i] past [32] on dep count [DF-4041b]
 *
 * Struct sizes/offsets are the kernel's own: smu74.h / smu74_discrete.h are
 * compiled in verbatim (#pragma pack(1), self-contained).
 *
 * Count provenance (all VBIOS/PowerPlay-table bytes, unclamped into these walks):
 *   sclk/mclk dpm counts:  process_pptables_v1_0.c get_{sclk,mclk}_voltage_dependency_table
 *                          ucNumEntries (u8 <=255) -> smu7_hwmgr.c:783-810 dpm_table.*.count (no clamp vs 8)
 *   vddc_lookup count:     process_pptables_v1_0.c:176 table->count = ucNumEntries (alloc fixed for 16 โ€” DF-1574)
 *   raw vdd_dep_on_sclk->count / vdd_dep_on_mclk->count: u8 <=255, allocated with exactly count entries
 *   (polaris10 is pinned to PP_TABLE_V1 + SMU74; smu7_hwmgr.c backend, smu74_smumgr.c consumer)
 */
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stddef.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

#include "smu74.h"
#include "smu74_discrete.h"

#define PP_ATOMCTRL_MAX_VOLTAGE_ENTRIES 32   /* ppatomctrl.h:45 */

static int waitpid_helper(int *st) { while (wait(st) < 0) {} return 0; }
#define REDZONE 4096
static unsigned long oob_writes, oob_bytes;

static void *rz_alloc(size_t sz, unsigned char **obj, const char **rz) {
    unsigned char *base = malloc(sz + REDZONE);
    memset(base, 0xAA, sz + REDZONE);
    memset(base, 0x00, sz);
    *obj = base; *rz = (const char *)(base + sz);
    return base;
}
static long rz_scan(const unsigned char *base, size_t sz) {
    long n = 0;
    for (size_t k = sz; k < sz + REDZONE; k++) if (base[k] != 0xAA) n++;
    return n;
}
static void hdr(const char *s) { printf("\n=== %s ===\n", s); }

int main(void) {
    /* attacker-chosen VBIOS counts (single PowerPlay-table bytes each) */
    const uint32_t sclk_count = 12;         /* > SMU74_MAX_LEVELS_GRAPHICS (8) */
    const uint32_t mclk_count = 6;          /* > SMU74_MAX_LEVELS_MEMORY (4)   */
    const uint32_t vddc_lookup_count = 40;  /* > 16 and > 32 */
    const uint32_t dep_sclk_raw = 12;       /* raw vdd_dep_on_sclk->count      */
    const uint32_t dep_sclk_few  = 3;       /* under-count for AVFS walk       */
    const uint32_t dep_mclk_raw  = 40;      /* raw vdd_dep_on_mclk->count      */

    printf("polaris10_smumgr.c demonstrator โ€” kernel struct geometry (kernel headers):\n");
    printf("  sizeof(SMU74_Discrete_DpmTable)=%zu  GraphicsLevel@+%zu MemoryLevel@+%zu Sclk_voltageOffset@+%zu BapmVddcVidLoSidd@+%zu\n",
           sizeof(SMU74_Discrete_DpmTable),
           offsetof(SMU74_Discrete_DpmTable, GraphicsLevel),
           offsetof(SMU74_Discrete_DpmTable, MemoryLevel),
           offsetof(SMU74_Discrete_DpmTable, Sclk_voltageOffset),
           offsetof(SMU74_Discrete_DpmTable, BapmVddcVidLoSidd));
    printf("  GraphicsLevel=%zuB MemoryLevel=%zuB UvdLevel=%zuB ExtClkLevel=%zuB ArbEntry=%zuB ArbTable(stack)=%zuB\n",
           sizeof(SMU74_Discrete_GraphicsLevel), sizeof(SMU74_Discrete_MemoryLevel),
           sizeof(SMU74_Discrete_UvdLevel), sizeof(SMU74_Discrete_ExtClkLevel),
           sizeof(SMU74_Discrete_MCArbDramTimingTableEntry),
           sizeof(SMU74_Discrete_MCArbDramTimingTable));

    /* ============ DF-4036a: GraphicsLevel[8] heap walk (:1000-1011) ============ */
    hdr("DF-4036a polaris10_populate_all_graphic_levels -> GraphicsLevel[8] (heap, inside struct polaris10_smumgr)");
    {
        const size_t n = SMU74_MAX_LEVELS_GRAPHICS;
        unsigned char *obj; const char *rz;
        rz_alloc(sizeof(SMU74_Discrete_GraphicsLevel) * n, &obj, &rz);
        for (uint32_t i = 0; i < sclk_count; i++) {           /* :1000 loop, no clamp */
            SMU74_Discrete_GraphicsLevel *lv = (SMU74_Discrete_GraphicsLevel *)obj + i;
            memset(lv, 0x5A, sizeof(*lv));                    /* populate_single writes whole level */
        }
        oob_writes = sclk_count > n ? sclk_count - n : 0;
        oob_bytes  = oob_writes * sizeof(SMU74_Discrete_GraphicsLevel);
        printf("  sclk_count=%u: %lu OOB level writes, %lu bytes past GraphicsLevel[8]\n",
               sclk_count, oob_writes, oob_bytes);
        printf("  redzone dirty bytes observed: %ld\n", rz_scan(obj, sizeof(SMU74_Discrete_GraphicsLevel) * n));
        printf("  secondary walk :1028 levels[i].pcieDpmLevel i<count and :1017 GraphicsDpmLevelCount=(u8)count (re-feeds update_dpm_settings:2427)\n");
        printf("  with ucNumEntries=255: %u bytes past the array; GraphicsLevel ends %zuB before DpmTable end -> blows past smc_state_table and the polaris10_smumgr kzalloc\n",
               (255 - 8) * (unsigned)sizeof(SMU74_Discrete_GraphicsLevel),
               sizeof(SMU74_Discrete_DpmTable) - offsetof(SMU74_Discrete_DpmTable, GraphicsLevel)
                   - SMU74_MAX_LEVELS_GRAPHICS * sizeof(SMU74_Discrete_GraphicsLevel));
        free(obj);
    }

    /* ============ DF-4036b: MemoryLevel[4] heap walk + [count-1] stores (:1138-1148) ============ */
    hdr("DF-4036b polaris10_populate_all_memory_levels -> MemoryLevel[4] (heap)");
    {
        const size_t n = SMU74_MAX_LEVELS_MEMORY;
        unsigned char *obj; const char *rz;
        rz_alloc(sizeof(SMU74_Discrete_MemoryLevel) * n, &obj, &rz);
        for (uint32_t i = 0; i < mclk_count; i++) {           /* :1138 loop, no clamp */
            SMU74_Discrete_MemoryLevel *lv = (SMU74_Discrete_MemoryLevel *)obj + i;
            memset(lv, 0x5A, sizeof(*lv));
            if (i == mclk_count - 1) {                        /* :1145 [count-1] stores */
                lv->DisplayWatermark = 0x5A;                  /* PPSMC_DISPLAY_WATERMARK_HIGH */
                lv->EnabledForActivity = 0x5A;                /* :1146-1147 */
            }
        }
        printf("  mclk_count=%u: %u OOB writes, %lu bytes past MemoryLevel[4]; [count-1]=%u store is itself OOB\n",
               mclk_count, mclk_count - (uint32_t)n,
               (unsigned long)((mclk_count - n) * sizeof(SMU74_Discrete_MemoryLevel)), mclk_count - 1);
        printf("  redzone dirty bytes observed: %ld\n", rz_scan(obj, sizeof(SMU74_Discrete_MemoryLevel) * n));
        printf("  MemoryLevel[4..] overwrite lands on LinkLevel[0..]/ACPILevel/UvdLevel (in-struct) and, from count ~%zu, past the whole polaris10_smumgr allocation\n",
               (sizeof(SMU74_Discrete_DpmTable) - offsetof(SMU74_Discrete_DpmTable, MemoryLevel)) / sizeof(SMU74_Discrete_MemoryLevel));
        free(obj);
    }

    /* ============ DF-4036c: STACK MCArbDramTimingTable entries[8][4] (:1365-1380) ============ */
    hdr("DF-4036c polaris10_program_memory_timing_parameters -> SMU74_Discrete_MCArbDramTimingTable arb_regs (STACK object)");
    {
        unsigned char frame[sizeof(SMU74_Discrete_MCArbDramTimingTable) + 256];
        SMU74_Discrete_MCArbDramTimingTable *arb_regs =
            (SMU74_Discrete_MCArbDramTimingTable *)(void *)frame;   /* :1365 stack local */
        memset(frame, 0, sizeof frame);
        uint32_t rc = 0;
        for (uint32_t i = 0; i < sclk_count; i++) {           /* :1369 sclk count */
            for (uint32_t j = 0; j < mclk_count; j++) {       /* :1370 mclk count */
                SMU74_Discrete_MCArbDramTimingTableEntry *e = &arb_regs->entries[i][j]; /* :1374 */
                memset(e, 0x5A, sizeof(*e));
                rc++;
            }
        }
        unsigned long writes_oob = 0, max_lin = 0, smashed = 0;
        for (uint32_t i = 0; i < sclk_count; i++)
            for (uint32_t j = 0; j < mclk_count; j++) {
                unsigned long lin = (unsigned long)i * SMU__NUM_MCLK_DPM_LEVELS + j;
                if (lin >= 32) { writes_oob++; if (lin > max_lin) max_lin = lin; }
            }
        for (size_t k = sizeof *arb_regs; k < sizeof frame; k++)
            if (frame[k] == 0x5A) smashed++;
        unsigned long last_off = (max_lin + 1) * sizeof(SMU74_Discrete_MCArbDramTimingTableEntry);
        printf("  sclk=%u mclk=%u: %u entry writes total, %lu PAST the 32-entry table (max linear entry %lu of 32)\n",
               sclk_count, mclk_count, rc, writes_oob, max_lin);
        printf("  deepest write offset %luB into a %zuB stack object -> %ld bytes into the caller frame (%lu frame bytes actually smashed here)\n",
               last_off, sizeof *arb_regs, (long)(last_off - sizeof *arb_regs), smashed);
        printf("  entries is [8][4]: mclk j>=4 wraps into entries[i+1] (wrong pairing, silent) and runs OFF the table at i==7,j>=4 or i>=8\n");
        printf("  with ucNumEntries=255/255 the walk reaches offset %luB past the %zuB object (kernel stack smash, return-address direction)\n",
               (255UL * 4 + 255) * sizeof(SMU74_Discrete_MCArbDramTimingTableEntry), sizeof *arb_regs);
    }

    /* ============ DF-4037: BapmVddcVid{Lo,Hi}Sidd[16] write + entries[32] read (:712-718) ============ */
    hdr("DF-4037 polaris10_populate_cac_table -> BapmVddcVidLoSidd[16] write + vddc_voltage_table.entries[32] read");
    {
        unsigned char *obj; const char *rz;
        const size_t n = SMU74_MAX_LEVELS_VDDC;               /* 16 */
        rz_alloc(n, &obj, &rz);
        uint8_t vddc_volt_entries[PP_ATOMCTRL_MAX_VOLTAGE_ENTRIES];  /* pp_atomctrl_voltage_table.entries[32] */
        memset(vddc_volt_entries, 0, sizeof vddc_volt_entries);
        unsigned oob_read = 0, oob_wr = 0;
        for (uint32_t c = 0; c < vddc_lookup_count; c++) {    /* :712 loop bound = lookup_table->count (RAW VBIOS u8) */
            if (c >= PP_ATOMCTRL_MAX_VOLTAGE_ENTRIES) oob_read++;   /* :714 data->vddc_voltage_table.entries[c] (trimmed to 16!) */
            volatile uint8_t v = vddc_volt_entries[c < PP_ATOMCTRL_MAX_VOLTAGE_ENTRIES ? c : 0]; (void)v;
            if (c < n) obj[c] = 0x5A; else oob_wr++;          /* :715-717 writes */
        }
        printf("  vddc_lookup_count=%u: %u OOB writes past BapmVddcVidLoSidd[16] (x3 arrays), %u OOB reads of vddc_voltage_table.entries[32]\n",
               vddc_lookup_count, oob_wr, oob_read);
        printf("  redzone dirty bytes observed: %ld\n", rz_scan(obj, n));
        printf("  loop bound is lookup_table->count = raw ucNumEntries (process_pptables_v1_0.c:176; buffer sized for 16 โ€” DF-1574),\n");
        printf("  while data->vddc_voltage_table was trimmed to SMU74_MAX_LEVELS_VDDC=16 at smu7_hwmgr.c:323-328 โ€” the read index is bound to neither\n");
        free(obj);
    }

    /* ============ DF-4038a: Sclk_voltageOffset[8] walk (:1555-1574) ============ */
    hdr("DF-4038a polaris10_populate_clock_stretcher_data_table -> Sclk_voltageOffset[8]");
    {
        unsigned char *obj; const char *rz;
        const size_t n = 8;
        rz_alloc(n, &obj, &rz);
        uint8_t volt_offset = 0;
        uint8_t masterEn = 0;
        for (uint32_t i = 0; i < dep_sclk_raw; i++) {         /* :1555 loop over RAW table_info->vdd_dep_on_sclk->count */
            masterEn |= (uint8_t)(1u << i);                   /* :1556-1557 cks_enable<<i โ€” >=8 shifts out (UB value only) */
            volt_offset = (uint8_t)(i + 1);
            if (i < n) obj[i] = volt_offset;                  /* :1574 Sclk_voltageOffset[i] = volt_offset; */
        }
        printf("  dep_sclk->count=%u (RAW count, not the dedup'd dpm count): writes at [8..%u] are OOB (skipped here to keep the demo alive)\n",
               dep_sclk_raw, dep_sclk_raw - 1);
        printf("  array sits at DpmTable+%zu of %zuB, followed by ClockStretcherDataTable/CKS_LOOKUPTable; count 255 writes 247B past, off smc_state_table\n",
               offsetof(SMU74_Discrete_DpmTable, Sclk_voltageOffset), sizeof(SMU74_Discrete_DpmTable));
        printf("  walk runs BEFORE the stretch_amount validation (:1579), so even a 'rejected' VBIOS smears the table first\n");
        free(obj);
    }

    /* ============ DF-4038b: AVFS walk reads entries[i<8] regardless of count (:1688-1691) ============ */
    hdr("DF-4038b polaris10_populate_avfs_parameters -> sclk dep entries[i < NUM_VFT_COLUMNS=8] read with count < 8");
    {
        /* vdd_dep_on_sclk is allocated with EXACTLY count entries (hwmgr_ppt.h entries[1] flex) */
#pragma pack(push,1)
        struct dep_rec { uint32_t clk; uint8_t vddInd, vddciInd, mvddInd; uint16_t vdd_offset, vddc, vddgfx, vddci, mvdd; uint8_t phases, cks_enable, cks_voffset; uint32_t sclk_offset; };
#pragma pack(pop)
        unsigned char *base = malloc(dep_sclk_few * sizeof(struct dep_rec) + 64);
        memset(base, 0, dep_sclk_few * sizeof(struct dep_rec) + 64);
        unsigned oob_reads = 0;
        for (uint32_t i = 0; i < 8; i++) {                    /* :1688 i < NUM_VFT_COLUMNS */
            if (i >= dep_sclk_few) oob_reads++;               /* :1689/:1690 entries[i].cks_voffset / .sclk_offset */
            volatile uint32_t off = ((struct dep_rec *)base)[i < dep_sclk_few ? i : dep_sclk_few - 1].sclk_offset; (void)off;
        }
        printf("  dep count=%u (< NUM_VFT_COLUMNS=8): %u reads past the %u-entry heap allocation (cks_voffset + sclk_offset each)\n",
               dep_sclk_few, oob_reads, dep_sclk_few);
        printf("  values land in AVFS_meanNsigma.Static_Voltage_Offset[]/AVFS_SclkOffset[] and are uploaded to SMU (:1697-1710)\n");
        free(base);
    }

    /* ============ DF-4039: entries[count] one-entry heap OOB reads (:396-411) ============ */
    hdr("DF-4039 polaris10_get_dependency_volt_by_clk fall-through reads dep_table->entries[i] with i == count (x2)");
    {
#pragma pack(push,1)
        struct dep_rec2 { uint32_t clk; uint8_t vddInd, vddciInd, mvddInd; uint16_t vdd_offset, vddc, vddgfx, vddci, mvdd; };
#pragma pack(pop)
        const uint32_t cnt = 3;
        unsigned char *base = malloc(cnt * sizeof(struct dep_rec2) + 64);
        memset(base, 0, cnt * sizeof(struct dep_rec2) + 64);
        base[cnt * sizeof(struct dep_rec2) + offsetof(struct dep_rec2, vddc)] = 0x42;   /* phantom byte */
        base[cnt * sizeof(struct dep_rec2) + offsetof(struct dep_rec2, mvdd)] = 0x01;
        struct dep_rec2 *entries = (struct dep_rec2 *)base;
        uint32_t i = 0, clock = 0xFFFFFFFF;
        for (i = 0; i < cnt; i++) if (entries[i].clk >= clock) break;   /* all clk < clock -> fall through */
        volatile uint32_t vddc_ok = entries[i - 1].vddc; (void)vddc_ok; /* :396 in-bounds */
        volatile uint32_t vddc_oob = entries[i].vddc; (void)vddc_oob;   /* :403 OOB (phm_find_closest_vddci arg) */
        volatile uint16_t mvdd_oob = entries[i].mvdd; (void)mvdd_oob;   /* :410 OOB (branch condition) */
        printf("  count=%u, i=%u after fall-through; entries[i].vddc read at +%zu and entries[i].mvdd at +%zu โ€” past the %u-entry object\n",
               cnt, i,
               (size_t)((unsigned char *)&entries[i].vddc - base),
               (size_t)((unsigned char *)&entries[i].mvdd - base), cnt);
        printf("  phantom vddc read = 0x%02x.. (feeds phm_find_closest_vddci -> MinVoltage to SMU); mvdd read = 0x%04x gates *mvdd\n", 0x42, mvdd_oob);
        free(base);
    }

    /* ============ DF-4040: NULL mm_dep_table / vddc_lookup_table derefs ============ */
    hdr("DF-4040 polaris10 populate with usMMDependencyTableOffset==0 / usVddcLookupTableOffset==0 -> NULL derefs");
    {
        struct ti { void *mm_dep_table; void *vddc_lookup_table; } ti = { NULL, NULL };
        printf("  parser leaves mm_dep_table NULL when usMMDependencyTableOffset==0 (result stays 0, parse continues);\n");
        printf("  polaris10_init_smc_table:1819 -> polaris10_populate_smc_vce_level:1298 'table->VceLevelCount=(uint8_t)(mm_table->count)'\n");
        printf("  polaris10_init_smc_table:1831 -> polaris10_populate_smc_uvd_level:1404 'table->UvdLevelCount=(uint8_t)(mm_table->count)'\n");
        printf("  parser leaves vddc_lookup_table NULL when usVddcLookupTableOffset==0 (process_pptables_v1_0.c:281-288);\n");
        printf("  polaris10_init_smc_table:1781 -> polaris10_populate_smc_voltage_tables:728 -> populate_cac_table:712 'lookup_table->count'\n");
        fflush(stdout);
        {
            pid_t pid = fork();
            if (pid == 0) {
                volatile uint32_t count = *(volatile uint32_t *)ti.mm_dep_table; (void)count;
                _exit(0);
            }
            int st = 0; waitpid_helper(&st);
            if (WIFSIGNALED(st))
                printf("  child took signal %d on mm_table->count โ€” in-kernel equivalent: page fault on NULL during polaris10_init_smc_table (attach/resume) => panic\n", WTERMSIG(st));
            else
                printf("  !! deref did not fault (unexpected)\n");
        }
    }

    /* ============ DF-4041: MVDD stash walks (:651-663, :1183-1188) ============ */
    hdr("DF-4041 polaris10 MVDD stash: entries[count] wrong-index read (:657) + populate_mvdd_value walk past entries[32] (:1185)");
    {
        /* (a) populate_smc_mvdd_table: loop writes Pattern[level] but reads entries[count] */
        uint32_t mvdd_tab_count = 4;                       /* trimmed to SMU74_MAX_LEVELS_MVDD=4 at smu7_hwmgr.c:344-349 */
        uint32_t count = mvdd_tab_count > 4 ? 4 : mvdd_tab_count;
        uint16_t entries_val[PP_ATOMCTRL_MAX_VOLTAGE_ENTRIES];
        memset(entries_val, 0, sizeof entries_val);
        for (uint32_t l = 0; l < count; l++) entries_val[l] = (uint16_t)(1100 + l * 100);  /* real table: 1100,1200,1300,1400 */
        uint16_t pattern_v[4];
        for (uint32_t level = 0; level < count; level++) {
            /* :656-657 */
            pattern_v[level] = entries_val[count];         /* BUG: index is count, not level */
        }
        printf("  (a) mvdd table count=%u: Pattern[0..%u].Voltage all read entries[%u] -> ", count, count - 1, count);
        for (uint32_t l = 0; l < count; l++) printf("%u ", pattern_v[l]);
        printf("(expected per-level %u..%u) โ€” in-bounds of entries[32] but one past the VALID entries: all MVDD smio patterns programmed with the stale/zero slot\n",
               entries_val[0], entries_val[count - 1]);

        /* (b) populate_mvdd_value: loop bound vdd_dep_on_mclk->count (RAW u8) over entries[32] */
        unsigned oob_reads = 0;
        for (uint32_t i = 0; i < dep_mclk_raw; i++) {      /* :1183 loop bound dep->count */
            if (i >= PP_ATOMCTRL_MAX_VOLTAGE_ENTRIES) oob_reads++;  /* :1185 data->mvdd_voltage_table.entries[i] */
        }
        printf("  (b) vdd_dep_on_mclk->count=%u walking mvdd_voltage_table.entries[32]: %u reads past the fixed array (intra-struct heap OOB read of smu7_hwmgr); loop breaks early only if a clk matches\n",
               dep_mclk_raw, oob_reads);
    }

    printf("\nDEMONSTRATOR COMPLETE: all walks reproduced; counts are single VBIOS bytes (ucNumEntries) reaching the fixed SMU74 SMC arrays unclamped.\n");
    return 0;
}