/*
 * DF-1307 harness — kv_parse_power_table VCE clk_idx OOB heap read
 *
 * Reproduces the vulnerable access pattern of
 *   sys/dev/drm/radeon/kv_dpm.c:2705-2707
 * in userspace.
 *
 * The kernel function kv_parse_power_table() fills VCE power states at the
 * tail of the powerplay-table parse. At line 2705-2707:
 *
 *   clock_array_index = rdev->pm.dpm.vce_states[i].clk_idx;   // 6-bit 0..63 VBIOS
 *   clock_info = (union pplib_clock_info *)
 *       &clock_info_array->clockInfo[clock_array_index * clock_info_array->ucEntrySize];
 *
 * `clk_idx` originates from the VBIOS VCE state record:
 *   r600_dpm.c:1127-1128:
 *     rdev->pm.dpm.vce_states[i].clk_idx = state_entry->ucClockInfoIndex & 0x3f;
 * It is used to byte-index clockInfo[] (a UCHAR flex[1] array, pptable.h:453)
 * with NO bounds check against clock_info_array->ucNumEntries. Contrast: the
 * main clock loop at kv_dpm.c:2683 DOES check
 *   if (clock_array_index >= clock_info_array->ucNumEntries) continue;
 * The VCE loop at 2705 does NOT.
 *
 * With clk_idx up to 63 and ucEntrySize up to 255 (UCHAR), the byte offset
 * can reach 63*255 = 16065 past clockInfo[0] -> heap OOB read.
 *
 * Build:  cc -O2 -Wall -o harness harness.c
 * Run:    ./harness
 *
 * Proof strategy: place clockInfo[0] at the end of a page-backed region with
 * the next page unmapped; craft clk_idx=63 + ucEntrySize=255 so the read at
 * byte offset 63*255=16065 faults on the unmapped page.
 */

#include <stdio.h>
#include <stdint.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <setjmp.h>
#include <unistd.h>
#include <sys/mman.h>

static sig_atomic_t got_fault = 0;
static jmp_buf jb;
static void fault_handler(int sig) { (void)sig; got_fault = sig; longjmp(jb, 1); }

typedef uint8_t  UCHAR;
typedef uint16_t USHORT;

/* _ClockInfoArray, pptable.h:446-454 */
typedef struct {
    UCHAR ucNumEntries;     /* VBIOS: how many real clock entries */
    UCHAR ucEntrySize;      /* sizeof(union pplib_clock_info), up to 255 */
    UCHAR clockInfo[1];     /* UCHAR flex array, byte-indexed */
} ClockInfoArray;

/* Minimal slice of union pplib_clock_info (radeon_dpm.h) — we read 4 bytes. */
typedef struct {
    USHORT usEngineClockLow;
    UCHAR  ucEngineClockHigh;
    UCHAR  pad;
} clock_info_slice;

/* Faithful replica of the vulnerable access (kv_dpm.c:2705-2707).
 * noinline + volatile inputs so gcc -O2 cannot constant-fold the array index
 * and elide the OOB load as UB (clockInfo[1] indexed past [0]). The real
 * kernel reads clk_idx and ucEntrySize at runtime from VBIOS, so gcc emits the
 * real address computation and load. */
__attribute__((noinline))
static UCHAR read_vce_clock_engine_high(volatile ClockInfoArray *arr,
                                        uint8_t clk_idx)
{
    /* byte-index the flat UCHAR array — NO bounds check vs ucNumEntries */
    clock_info_slice *ci = (clock_info_slice *)
        &arr->clockInfo[(uint32_t)clk_idx * arr->ucEntrySize];
    return ci->ucEngineClockHigh;   /* the OOB read */
}

int main(void)
{
    struct sigaction sa;
    sa.sa_handler = fault_handler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = 0;
    sigaction(SIGSEGV, &sa, NULL);
    sigaction(SIGBUS,  &sa, NULL);

    printf("DF-1307 kv_parse_power_table VCE clk_idx OOB read harness\n");
    printf("sizeof(ClockInfoArray) overhead = %zu (clockInfo is UCHAR flex[1])\n",
           offsetof(ClockInfoArray, clockInfo) + 1);

    long pagesize = (long)getpagesize();
    /* The OOB can reach 63*255 = 16065 bytes (~4 pages), so a single guard
     * page is not enough — the access would skip over it into random memory.
     * Allocate enough pages and mprotect a large guard region after the data
     * page so ANY OOB access (up to ~20KB) faults. */
    size_t npages = 6;   /* page 0 = data; pages 1-5 = PROT_NONE guard (20480 B) */
    size_t maplen = (size_t)pagesize * npages;
    uint8_t *base = mmap(NULL, maplen, PROT_READ | PROT_WRITE,
                         MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
    if (base == MAP_FAILED) { perror("mmap"); return 1; }
    /* Guard: pages 1..(npages-1) become PROT_NONE -> any read faults. */
    if (mprotect(base + pagesize, (size_t)pagesize * (npages - 1), PROT_NONE) != 0) {
        perror("mprotect"); return 1; }

    /* Place clockInfo[0] near the END of the data page so even a small OOB
     * crosses into the guard. */
    size_t overhead = offsetof(ClockInfoArray, clockInfo);
    volatile ClockInfoArray *arr =
        (volatile ClockInfoArray *)(base + pagesize - overhead - 16);
    arr->ucNumEntries = 2;        /* only 2 real entries */
    arr->ucEntrySize  = 255;      /* max UCHAR — amplifies the 6-bit clk_idx */
    memset((void *)&arr->clockInfo[0], 0xBB, 16);

    /* Crafted VBIOS VCE state: clk_idx = 63 (max 6-bit value from
     * state_entry->ucClockInfoIndex & 0x3f at r600_dpm.c:1128). volatile so
     * gcc treats it as an unknown runtime value. */
    volatile uint8_t clk_idx = 63;
    unsigned byte_off = (unsigned)clk_idx * arr->ucEntrySize;   /* 63*255 = 16065 */

    printf("ClockInfoArray @ %p (clockInfo[0] @ %p), ucNumEntries=%u, ucEntrySize=%u\n",
           (void *)arr, (void *)&arr->clockInfo[0], arr->ucNumEntries, arr->ucEntrySize);
    printf("VBIOS VCE clk_idx = %u  (NO check vs ucNumEntries=%u)\n",
           clk_idx, arr->ucNumEntries);
    printf("access byte offset = %u * %u = %u  (past the %u valid entries)\n",
           clk_idx, arr->ucEntrySize, byte_off, arr->ucNumEntries);

    if (setjmp(jb) == 0) {
        UCHAR v = read_vce_clock_engine_high(arr, clk_idx);
        printf("UNEXPECTED: read succeeded (ucEngineClockHigh=0x%02x) — layout too loose\n", v);
    } else {
        printf("FAULT (signal %d): OOB read at clockInfo[%u] off the bios buffer\n",
               got_fault, byte_off);
        printf("  -> in-kernel equivalent: heap OOB read up to %u bytes past clockInfo[0]\n",
               byte_off);
        printf("RESULT: OOB read CONFIRMED at kv_dpm.c:2707 "
               "(VCE clk_idx byte-index with NO bounds check vs ucNumEntries)\n");
    }

    munmap(base, maplen);
    return 0;
}
