/*
 * DF-1127 userspace harness — si_parse_power_table BIOS-index OOB read
 * primitive.
 *
 * The kernel bug (sys/dev/drm/amd/amdgpu/si_dpm.c:7253-7297):
 *   - nonClockInfoIndex read from BIOS, used unvalidated to index
 *     non_clock_info_array->nonClockInfo[] at :7253-7255 (NO check vs
 *     ucNumEntries).
 *   - VCE clk_idx at :7288-7290 also unvalidated.
 *   - The sibling clock loop at :7267-7275 DOES check
 *     `if (clock_array_index >= clock_info_array->ucNumEntries) continue;`
 *     proving the omission is an oversight, not policy.
 *
 * Trigger: crafted/malformed VBIOS (VFIO GPU passthrough romfile, corrupt
 * EEPROM) -> OOB read from BIOS mapping -> info leak via sysfs pp_dpm_sclk or
 * panic.
 *
 * Reachability: requires AMD GPU (Tahiti/si) with si_dpm. Not in GENERIC, no
 * AMD GPU on this audit guest.
 *
 * Build:  cc -O2 -o harness harness.c
 * Run:    ./harness
 * Expected: demonstrates the OOB read past the BIOS array.
 */
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <stddef.h>

#define NONCLOCK_INFO_ENTRIES   4
#define ENTRY_SIZE              16

struct _NonClockInfoArray {
    uint8_t  ucNumEntries;
    uint8_t  ucEntrySize;
    /* entries follow */
    uint8_t  nonClockInfo[NONCLOCK_INFO_ENTRIES][ENTRY_SIZE];
};

/* Simulate a BIOS mapping: a small region with the valid array, then unrelated
 * bytes beyond it (in kernel these are whatever follows in the BIOS mapping). */
static uint8_t bios_mapping[256];

static void dump_array(struct _NonClockInfoArray *a) {
    printf("non_clock_info_array @ %p, ucNumEntries=%u, ucEntrySize=%u\n",
           (void *)a, a->ucNumEntries, a->ucEntrySize);
}

int main(void) {
    memset(bios_mapping, 0xAA, sizeof(bios_mapping));  /* poison */
    struct _NonClockInfoArray *arr =
        (struct _NonClockInfoArray *)bios_mapping;
    arr->ucNumEntries  = NONCLOCK_INFO_ENTRIES;
    arr->ucEntrySize   = ENTRY_SIZE;
    for (int i = 0; i < NONCLOCK_INFO_ENTRIES; i++)
        memset(arr->nonClockInfo[i], 'A' + i, ENTRY_SIZE);

    dump_array(arr);

    /* crafted BIOS supplies a bogus nonClockInfoIndex */
    uint8_t bad_index = 42;   /* >> ucNumEntries (4) */

    printf("\n[buggy path] si_dpm.c:7253-7255 -- NO bounds check\n");
    printf("  nonClockInfoIndex = %u (ucNumEntries = %u)\n",
           bad_index, arr->ucNumEntries);

    /* the exact indexing the kernel does */
    uint8_t *non_clock_info = (uint8_t *)
        &arr->nonClockInfo[bad_index];
    ptrdiff_t off = non_clock_info - bios_mapping;
    printf("  accesses bios_mapping[%td] (mapping size = %zu)\n",
           off, sizeof(bios_mapping));
    if ((size_t)off >= sizeof(bios_mapping)) {
        printf("  -> READ PAST MAPPING BOUNDARY -> kernel OOB read\n");
    } else if (bad_index >= arr->ucNumEntries) {
        printf("  -> READ PAST nonClockInfo[%u] (valid 0..%u) -> OOB within mapping\n",
               arr->ucNumEntries - 1, arr->ucNumEntries - 1);
        printf("     bytes read: ");
        for (int i = 0; i < 8; i++) printf("%02x ", non_clock_info[i]);
        printf("...\n");
    }

    printf("\n[fixed path] sibling clock loop si_dpm.c:7269 DOES check:\n");
    printf("  if (clock_array_index >= clock_info_array->ucNumEntries) continue;\n");
    printf("  -> the fix is to apply the same check to nonClockInfoIndex.\n");

    return 0;
}
