/*
 * DF-1468 harness — processpptables VBIOS indices into clock-info arrays OOB read
 *
 * Reproduces the vulnerable access pattern at several sites in
 *   sys/dev/drm/amd/powerplay/hwmgr/processpptables.c
 * representative site:
 *   :1099-1107  (UVD)  `&array->entries[table->entries[i].ucUVDClockInfoIndex]`
 *   :1131-1139  (VCE)  ucVCEClockInfoIndex
 *   :1576-1587  (VCE state) ucClockInfoIndex
 *   :923-935    (v2 state walk) clockInfoIndex[i], nonClockInfoIndex
 *
 * The pattern: a UCHAR index taken directly from the VBIOS is used to index a
 * flexible-length array whose real length is given by a sibling `ucNumEntries`
 * field, but the index is NEVER compared to ucNumEntries. A crafted index
 * reads arbitrarily far past the array into adjacent kernel/VBIOS memory.
 *
 * Representative kernel code (processpptables.c:1099-1107):
 *
 *   for (i = 0; i < table->numEntries; i++) {
 *       const UVDClockInfo *entry =
 *           &array->entries[table->entries[i].ucUVDClockInfoIndex];  // OOB read
 *       uvd_table->entries[i].vclk = (entry->ucVClkHigh << 16) | entry->usVClkLow;
 *       ...
 *   }
 *
 * `array` is a VBIOS flex-array `entries[]` whose real length is
 * `array->ucNumEntries`. `ucUVDClockInfoIndex` (u8 0..255) is used without
 * any check. With ucUVDClockInfoIndex >= ucNumEntries the read lands past
 * entries[] -> heap OOB read, dereferenced as clock/voltage values (driving
 * MMIO writes -> corruption, or leaked via sysfs pp_dpm_sclk / hwmon).
 *
 * soft_pp_table_size is stored (processpptables.c:844) but never used as a
 * bound. Enabler: malicious VBIOS flash or SR-IOV guest atom context during
 * driver init.
 *
 * Build:  cc -O2 -Wall -o harness harness.c
 * Run:    ./harness
 *
 * Proof strategy: marker redzone. Allocate a buffer backing the array with
 * extra "redzone" slots AFTER the legitimate entries, and fill those redzone
 * slots with a recognisable marker. Set the VBIOS index past ucNumEntries
 * and show the read returns the marker bytes -> the access reads memory it
 * was never entitled to (in-kernel: adjacent slab/VBIOS bytes). This is the
 * same proof class as a guard-page fault but deterministic and portable.
 */

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

typedef uint8_t  UCHAR;
typedef uint16_t USHORT;
typedef uint32_t ULONG;

/* UVDClockInfo (representative VBIOS struct, processpptables.c) — packed. */
typedef struct __attribute__((packed)) {
    UCHAR ucVClkHigh;     /* 1 */
    UCHAR ucDClkHigh;     /* 1 */
    USHORT usVClkLow;     /* 2 */
    USHORT usDClkLow;     /* 2 */
} UVDClockInfo;           /* 6 bytes */

#define REAL_ENTRIES    1     /* array->ucNumEntries = 1 (a single legit entry) */
#define REDZONE_ENTRIES 4     /* slots past ucNumEntries, filled with marker */
#define MARKER_VCLK_HI  0xBB
#define MARKER_VCLK_LO  0xBEEF

/* Backing buffer: header + (REAL + REDZONE) entries, all contiguous as the
 * kernel would see them in the kmalloc'd VBIOS image. */
typedef struct {
    UCHAR ucNumEntries;
    UCHAR ucEntrySize;
    UVDClockInfo entries[REAL_ENTRIES + REDZONE_ENTRIES];
} array_buf;

/* Faithful replica of processpptables.c:1100-1101. NO bounds check on idx. */
static ULONG read_vclk(const array_buf *arr, UCHAR idx)
{
    const UVDClockInfo *entry = &arr->entries[idx];   /* :1101 NO bounds check */
    return ((ULONG)entry->ucVClkHigh << 16) | entry->usVClkLow;
}

int main(void)
{
    array_buf *arr = calloc(1, sizeof(*arr));
    if (!arr) { perror("calloc"); return 1; }

    arr->ucNumEntries = REAL_ENTRIES;
    arr->ucEntrySize  = (UCHAR)sizeof(UVDClockInfo);

    /* The ONE legitimate entry: */
    arr->entries[0].ucVClkHigh = 0x11;
    arr->entries[0].usVClkLow  = 0x2233;

    /* Redzone slots (simulate adjacent slab/VBIOS bytes): */
    for (int i = REAL_ENTRIES; i < REAL_ENTRIES + REDZONE_ENTRIES; i++) {
        arr->entries[i].ucVClkHigh = MARKER_VCLK_HI;
        arr->entries[i].usVClkLow  = MARKER_VCLK_LO;
    }

    printf("DF-1468 processpptables VBIOS clock-info index OOB read harness\n");
    printf("array->ucNumEntries   = %u (the real length)\n", arr->ucNumEntries);
    printf("sizeof(UVDClockInfo)  = %zu\n", sizeof(UVDClockInfo));
    printf("redzone marker        = vclk_hi=0x%02x vclk_lo=0x%04x\n",
           MARKER_VCLK_HI, MARKER_VCLK_LO);

    /* In-bounds read first. */
    UCHAR good_idx = 0;
    ULONG v = read_vclk(arr, good_idx);
    printf("\nIn-bounds  ucUVDClockInfoIndex=%u (< ucNumEntries) -> vclk=0x%lx (OK)\n",
           good_idx, (unsigned long)v);

    /* OOB read: index >= ucNumEntries. The kernel would read adjacent memory. */
    UCHAR bad_idx = REAL_ENTRIES;   /* first illegal index */
    ULONG v2 = read_vclk(arr, bad_idx);
    printf("OOB        ucUVDClockInfoIndex=%u (>= ucNumEntries) -> vclk=0x%lx\n",
           bad_idx, (unsigned long)v2);

    /* Worst case: index = 255 (max UCHAR). */
    UCHAR worst_idx = 255;
    /* We can't size the buffer to 255 without losing the marker clarity, but
     * the arithmetic alone shows the reach: */
    printf("Worst case ucUVDClockInfoIndex=255 -> kernel reads at byte offset %zu past entries[0]\n",
           (size_t)worst_idx * sizeof(UVDClockInfo));

    int leaked_marker = ((v2 >> 16) & 0xFF) == MARKER_VCLK_HI &&
                        (v2 & 0xFFFF) == MARKER_VCLK_LO;

    printf("\nread at entries[%u] returned the redzone marker (0x%02x/0x%04x): %s\n",
           bad_idx, MARKER_VCLK_HI, MARKER_VCLK_LO,
           leaked_marker ? "YES -> OOB read proven" : "no");

    if (leaked_marker) {
        printf("\nRESULT: heap OOB read CONFIRMED (processpptables.c:1101 pattern)\n");
        printf("ucUVDClockInfoIndex/ucVCEClockInfoIndex/clockInfoIndex/nonClockInfoIndex\n");
        printf("(all UCHAR, VBIOS) index into flex arrays with NO check vs ucNumEntries.\n");
        printf("soft_pp_table_size is available (:844) but never used as a bound.\n");
        free(arr);
        return 0;
    }
    printf("\nUNEXPECTED: marker not observed\n");
    free(arr);
    return 1;
}
