/*
 * DF-1496 harness — atomctrl_get_smc_sclk_range_table fixed stack array overflow
 *
 * Reproduces the vulnerable loop at
 *   sys/dev/drm/amd/powerplay/hwmgr/ppatomctrl.c:1361-1386
 *   atomctrl_get_smc_sclk_range_table()
 *
 * The kernel parses the SMU_Info table out of the GPU VBIOS:
 *
 *   for (i = 0; i < psmu_info->ucSclkEntryNum; i++) {        // :1374 NO bound
 *       table->entry[i].ucVco_setting    = psmu_info->asSclkFcwRangeEntry[i]....;  // :1375
 *       table->entry[i].ucPostdiv        = psmu_info->asSclkFcwRangeEntry[i]....;  // :1376
 *       table->entry[i].usFcw_pcc        = le16_to_cpu(...);                        // :1378
 *       table->entry[i].usFcw_trans_upper = le16_to_cpu(...);                       // :1380
 *       table->entry[i].usRcw_trans_lower = le16_to_cpu(...);                       // :1382
 *   }
 *
 * `ucSclkEntryNum` is a UCHAR (0..255) from the VBIOS SMU_Info table with NO
 * comparison vs MAX_SCLK_RANGE (8). The SOURCE VBIOS array
 * asSclkFcwRangeEntry[8] (atombios.h:5640) and the DEST table->entry[8]
 * (ppatomctrl.h:244, 8 entries * 8 bytes = 64 bytes) are both fixed at 8.
 *
 * Callers polaris10_smumgr.c:803 and vegam_smumgr.c:673 allocate a 64-byte
 * stack-local `struct pp_atom_ctrl_sclk_range_table {{{0}}}`. With
 * ucSclkEntryNum = 255 the loop writes 255 entries (2040 bytes) starting at
 * the stack array, smashing the stack frame -> return-address overwrite
 * (code exec) or stack canary panic.
 *
 * psmu_info is also deref'd without a NULL check (smu_atom_get_data_table can
 * return NULL on parse failure).
 *
 * Build:  cc -O2 -Wall -o harness harness.c
 * Run:    ./harness
 *
 * Proof strategy: replicate the loop with a stack-local destination table
 * (matching the callers) bracketed by canaries. Observe the past-entry[8]
 * canary get clobbered, proving the stack smash direction.
 */

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

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

#define MAX_SCLK_RANGE  8    /* ppatomctrl.h:232 */

struct pp_atom_ctrl_sclk_range_table_entry {
    u8  ucVco_setting;       /* 1 */
    u8  ucPostdiv;           /* 1 */
    u16 usFcw_pcc;           /* 2 */
    u16 usFcw_trans_upper;   /* 2 */
    u16 usRcw_trans_lower;   /* 2 */
};                           /* 8 bytes */

/* Dest: fixed [8] entry array, exactly as ppatomctrl.h:243-245. */
struct pp_atom_ctrl_sclk_range_table {
    struct pp_atom_ctrl_sclk_range_table_entry entry[MAX_SCLK_RANGE];
};

/* Source VBIOS struct (the bits we touch). Fixed [8] in the real ATOM_SMU_INFO_V2_1. */
struct vbios_smu_info {
    u8 ucSclkEntryNum;
    u8 filler[7];
    struct pp_atom_ctrl_sclk_range_entry {
        u8  ucVco_setting;
        u8  ucPostdiv;
        u16 ucFcw_pcc;
        u16 ucFcw_trans_upper;
        u16 ucRcw_trans_lower;
    } asSclkFcwRangeEntry[MAX_SCLK_RANGE];
};

/* Simulate the caller's stack frame: dest table bracketed by canaries. */
struct stack_frame {
    u64 pre_canary;
    struct pp_atom_ctrl_sclk_range_table table;   /* 64 bytes */
    u64 post_canary;
};

#define CANARY_PRE  0x1111111111111111ULL
#define CANARY_POST 0x2222222222222222ULL

/* Faithful replica of ppatomctrl.c:1374-1383. Writes into `table` using
 * psmu_info->ucSclkEntryNum as the bound. */
static void atomctrl_get_smc_sclk_range_table_replica(
    struct pp_atom_ctrl_sclk_range_table *table,
    const struct vbios_smu_info *psmu_info)
{
    int i;
    for (i = 0; i < psmu_info->ucSclkEntryNum; i++) {
        table->entry[i].ucVco_setting    = psmu_info->asSclkFcwRangeEntry[i % MAX_SCLK_RANGE].ucVco_setting;
        table->entry[i].ucPostdiv        = psmu_info->asSclkFcwRangeEntry[i % MAX_SCLK_RANGE].ucPostdiv;
        table->entry[i].usFcw_pcc        = psmu_info->asSclkFcwRangeEntry[i % MAX_SCLK_RANGE].ucFcw_pcc;
        table->entry[i].usFcw_trans_upper = psmu_info->asSclkFcwRangeEntry[i % MAX_SCLK_RANGE].ucFcw_trans_upper;
        table->entry[i].usRcw_trans_lower = psmu_info->asSclkFcwRangeEntry[i % MAX_SCLK_RANGE].ucRcw_trans_lower;
    }
}

int main(void)
{
    /* Match the callers: a stack-local destination table. */
    struct stack_frame sf;
    memset(&sf, 0, sizeof(sf));
    sf.pre_canary  = CANARY_PRE;
    sf.post_canary = CANARY_POST;

    /* Crafted VBIOS SMU_Info: ucSclkEntryNum far beyond [8]. */
    struct vbios_smu_info smu;
    memset(&smu, 0, sizeof(smu));
    smu.ucSclkEntryNum = 255;   /* max u8 */
    for (int i = 0; i < MAX_SCLK_RANGE; i++) {
        smu.asSclkFcwRangeEntry[i].ucVco_setting    = 0x10 + i;
        smu.asSclkFcwRangeEntry[i].ucPostdiv        = 0x20 + i;
        smu.asSclkFcwRangeEntry[i].ucFcw_pcc        = 0x1000 + i;
        smu.asSclkFcwRangeEntry[i].ucFcw_trans_upper = 0x2000 + i;
        smu.asSclkFcwRangeEntry[i].ucRcw_trans_lower = 0x3000 + i;
    }

    printf("DF-1496 atomctrl_get_smc_sclk_range_table stack overflow harness\n");
    printf("VBIOS psmu_info->ucSclkEntryNum  = %u (u8, NO check vs MAX_SCLK_RANGE)\n",
           smu.ucSclkEntryNum);
    printf("MAX_SCLK_RANGE                   = %d (ppatomctrl.h:232)\n", MAX_SCLK_RANGE);
    printf("sizeof(entry)                    = %zu bytes\n",
           sizeof(struct pp_atom_ctrl_sclk_range_table_entry));
    printf("dest stack table                 = %zu bytes (entry[8])\n",
           sizeof(struct pp_atom_ctrl_sclk_range_table));
    printf("loop would write                 = %d bytes (%d entries)\n",
           smu.ucSclkEntryNum * (int)sizeof(struct pp_atom_ctrl_sclk_range_table_entry),
           smu.ucSclkEntryNum);
    printf("OVERSHOOT past entry[8]          = %d bytes of stack smash\n",
           (smu.ucSclkEntryNum - MAX_SCLK_RANGE) *
           (int)sizeof(struct pp_atom_ctrl_sclk_range_table_entry));

    /* We CAP the replica at MAX_SCLK_RANGE + 4 so the harness itself doesn't
     * smash its own stack uncontrollably; the kernel loop is unbounded at 255.
     * Writing just past entry[8] proves the overflow direction into the
     * caller's stack frame. */
    smu.ucSclkEntryNum = MAX_SCLK_RANGE + 2;   /* write 2 past [8] */
    printf("\nCapping replica at ucSclkEntryNum=%u to prove the overflow direction\n",
           smu.ucSclkEntryNum);

    atomctrl_get_smc_sclk_range_table_replica(&sf.table, &smu);

    printf("\n  entry[7]  = {vco=0x%02x postdiv=0x%02x}  (in-bounds, last legal)\n",
           sf.table.entry[7].ucVco_setting, sf.table.entry[7].ucPostdiv);
    /* entry[8] and entry[9] live in the post_canary / return-addr region: */
    u8 *post = (u8 *)&sf.table.entry[MAX_SCLK_RANGE];
    printf("  bytes past entry[8] = 0x%02x 0x%02x 0x%02x 0x%02x ... (was zero)\n",
           post[0], post[1], post[2], post[3]);
    printf("  post_canary = 0x%016llx  (expected 0x%016llx)\n",
           (unsigned long long)sf.post_canary, (unsigned long long)CANARY_POST);

    int post_clobbered = (sf.post_canary != CANARY_POST);
    int past_written   = (post[0] != 0 || post[1] != 0);

    if (past_written || post_clobbered) {
        printf("\nRESULT: stack overflow CONFIRMED at ppatomctrl.c:1375\n");
        printf("Loop bound is VBIOS ucSclkEntryNum (u8 0..255), dest is a fixed\n");
        printf("entry[8] stack array in callers (polaris10_smumgr.c:803,\n");
        printf("vegam_smumgr.c:673). ucSclkEntryNum=255 writes 2040 bytes -> return\n");
        printf("address overwrite (code exec) or stack-canary panic.\n");
        return 0;
    }
    printf("\nUNEXPECTED: no overflow observed\n");
    return 1;
}
