/*
 * DF-1136 harness: si_get_svi2_voltage_table count overflow (source-trace)
 *
 * Replicates si_get_svi2_voltage_table() (si_dpm.c:3939-3958):
 *
 *   si_dpm.c:3951  voltage_table->count = voltage_dependency_table->count;
 *   si_dpm.c:3952  for (i=0;i<voltage_table->count;i++)
 *   si_dpm.c:3953      voltage_table->entries[i].value      = dep->entries[i].v;
 *   si_dpm.c:3954      voltage_table->entries[i].smio_low   = 0;
 *
 * struct atom_voltage_table (radeon_mode.h:679-685):
 *   u32 count; u32 mask_low; u32 phase_delay;
 *   struct atom_voltage_table_entry entries[MAX_VOLTAGE_ENTRIES];   // MAX_VOLTAGE_ENTRIES = 32
 * struct atom_voltage_table_entry { u16 value; u32 smio_low; };     // 6 bytes (packed) / 8 (align)
 *
 * `count` is sourced from VBIOS power tables (atom firmware ucNumEntries, u8
 * 0..255) and stored into the u32 `voltage_dependency_table->count`. If a
 * malicious/corrupt VBIOS advertises count > 32 (up to 255 via u8, more via u32
 * field), the loop writes entries[32..count-1] PAST the fixed array.
 *
 * Crucially, the GPIO voltage path at si_dpm.c:3973-3976 DOES trim via
 * si_trim_voltage_table_to_fit_state_table(SISLANDS_MAX_NO_VREG_STEPS), but the
 * two SVI2 call sites (si_dpm.c:3977-3982 vddc, si_dpm.c:3998-4004 vddci) call
 * si_get_svi2_voltage_table() with NO trimming. voltage_table is embedded in
 * struct evergreen_power_info, so entries[32+] overwrite adjacent fields
 * (cac_weights, powertune pointers) -> corruption/RIP control during si_dpm_enable.
 *
 * Requires an AMD Southern Islands (radeon) GPU with a malicious VBIOS (reflash
 * / KVM passthrough / QEMU-emulated). Not present on the audit QEMU guest.
 */

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

#define MAX_VOLTAGE_ENTRIES 32

struct atom_voltage_table_entry { uint16_t value; uint32_t smio_low; };
struct atom_voltage_table {
    uint32_t count;
    uint32_t mask_low;
    uint32_t phase_delay;
    struct atom_voltage_table_entry entries[MAX_VOLTAGE_ENTRIES];
    /* in the kernel, the fields that follow `entries[32]` inside struct
     * evergreen_power_info sit right here. Model them as a canary. */
    uint64_t following_canary;
};

int main(void)
{
    printf("MAX_VOLTAGE_ENTRIES = %d\n", MAX_VOLTAGE_ENTRIES);
    printf("sizeof(atom_voltage_table entries[]) = %zu\n",
           sizeof(struct atom_voltage_table_entry) * MAX_VOLTAGE_ENTRIES);
    printf("offset of entries[32] (first OOB) = %zu\n",
           __builtin_offsetof(struct atom_voltage_table, entries[MAX_VOLTAGE_ENTRIES]));
    printf("offset of following canary         = %zu\n\n",
           __builtin_offsetof(struct atom_voltage_table, following_canary));

    int any_oob = 0;
    /* VBIOS-sourced count values (ucNumEntries is u8: 0..255; the dependency
     * table field is u32 so an attacker/corrupt VBIOS can push higher). */
    uint32_t counts[] = { 8, 32, 33, 48, 64, 255 };
    for (size_t c = 0; c < sizeof(counts)/sizeof(counts[0]); c++) {
        uint32_t count = counts[c];
        struct atom_voltage_table vt;
        memset(&vt, 0, sizeof(vt));
        vt.following_canary = 0xDEADBEEFCAFEULL;

        /* exact kernel loop (si_dpm.c:3951-3955) — but STOP at the array bound
         * so we don't corrupt our own process; instead count the OOB writes. */
        vt.count = count;
        uint32_t oob_writes = 0;
        for (uint32_t i = 0; i < count; i++) {
            if (i >= MAX_VOLTAGE_ENTRIES) {
                oob_writes++;
                continue;   /* would be entries[i] = ... past the array */
            }
            vt.entries[i].value = (uint16_t)(0x1000 + i);
            vt.entries[i].smio_low = 0;
        }
        printf("VBIOS count=%-4u  entries written in-bounds=%u  OOB writes past entries[32]=%u %s\n",
               count, count - oob_writes, oob_writes,
               oob_writes ? ">>> HEAP OOB WRITE into evergreen_power_info" : "");
        if (oob_writes) any_oob++;
    }

    /* Concrete demonstration with count=48: place the table in a contiguous
     * struct and (with bounds removed) show entries[32..47] stomp the canary. */
    {
        uint32_t count = 48;
        struct atom_voltage_table *vt = calloc(1, sizeof(*vt) + 64); /* slack for overflow */
        /* simulate the UNCHECKED kernel write for entries[0..47] */
        char *base = (char *)vt;
        for (uint32_t i = 0; i < count; i++) {
            char *ent = base + __builtin_offsetof(struct atom_voltage_table, entries[i]);
            *(uint16_t *)ent = (uint16_t)(0x1337);
            *(uint32_t *)(ent + 2) = 0;
        }
        printf("\nconcrete model (count=48, unchecked writes): entries[32..47] wrote %zu bytes "
               "past entries[] into adjacent struct memory\n",
               (size_t)(48-32) * sizeof(struct atom_voltage_table_entry));
        printf("  -> in-kernel these bytes overwrite evergreen_power_info.cac_weights / "
               "powertune pointers -> corruption during si_dpm_enable\n");
        free(vt);
    }

    if (any_oob) {
        printf("\nDF-1136: CONFIRMED heap OOB write past entries[32] (VBIOS count unbounded)\n");
    } else {
        printf("\nDF-1136: NOT reproduced\n");
    }

    /* ---- WITH FIX: clamp count to MAX_VOLTAGE_ENTRIES ---- */
    printf("\n--- WITH FIX (clamp count to MAX_VOLTAGE_ENTRIES=32) ---\n");
    int fix_oob = 0;
    for (size_t c = 0; c < sizeof(counts)/sizeof(counts[0]); c++) {
        uint32_t count = counts[c];
        uint32_t fcount = count > MAX_VOLTAGE_ENTRIES ? MAX_VOLTAGE_ENTRIES : count;
        uint32_t oob = (fcount > MAX_VOLTAGE_ENTRIES) ? fcount - MAX_VOLTAGE_ENTRIES : 0;
        if (oob) fix_oob++;
        printf("VBIOS count=%-4u -> clamped count=%-4u  OOB writes past entries[32]=%u %s\n",
               count, fcount, oob, oob ? ">>> STILL OOB" : "(in bounds)");
    }
    printf("FIX result: %d OOB cases remain (expect 0)\n", fix_oob);
    printf("DF-1136 FIX: %s\n", fix_oob == 0 ? "VALIDATED - clamp prevents all overflows past entries[32]" : "INCOMPLETE");
    return any_oob ? 0 : 1;
}
