/*
 * DF-1178 harness: demonstrates the indexing logic flaw in
 * gfx_v9_1_parse_ind_reg_list() — a static function inside the amdgpu
 * driver's gfx_v9_0.c.  The bug is reproduced by extracting the exact
 * parser logic into userspace and feeding it the kind of crafted
 * register_list_format the function would receive from RLC v2.1 firmware.
 *
 * The vulnerable function runs *only* when the amdgpu driver attaches to
 * real AMD Vega-class GPU hardware (gfx_v9_0_init_pg() at line 2396 is
 * invoked from gfx_v9_0_hw_init(), the .hw_init entry of the GFX v9 IP
 * block; it then calls gfx_v9_1_init_rlc_save_restore_list() at line 2405
 * only when adev->gfx.rlc.is_rlc_v2_1 == true, which itself is set only
 * when the RLC firmware header carries v2.1 magic during gfx_v9_0_init_rlc()
 * at line 673).  This guest has no AMD GPU (vgapci0 = vendor 0x1234 stdvga),
 * so the kernel-side path is unreachable here — the harness exists to make
 * the flaw concrete and reproducible at the logic level.
 *
 * Confirmed bug: gfx_v9_0.c:2096-2098
 *   WARN_ON(*indirect_start_offsets_count >= max_start_offsets_count);   // non-fatal
 *   indirect_start_offsets[*indirect_start_offsets_count] = ...;          // unconditional write
 *   *indirect_start_offsets_count = *indirect_start_offsets_count + 1;
 * WARN_ON in DragonFly (sys/dev/drm/include/asm/bug.h:43-49) only kprintf's
 * a warning and returns the condition — it does NOT stop execution.  With
 * max_start_offsets_count == ARRAY_SIZE(indirect_start_offsets) == 10 (the
 * caller at gfx_v9_0.c:2126 allocates exactly 10 ints on the stack), a
 * crafted RLC v2.1 firmware with >10 indirect blocks overflows
 * indirect_start_offsets[] past the 10-int frame, smashing saved RBP / the
 * return address — CWE-121 stack-based buffer overflow.
 *
 * Also at gfx_v9_0.c:2100-2117 the inner while loop does
 *   while (register_list_format[indirect_offset] != 0xFFFFFFFF) {
 *       indirect_offset += 2;
 *       for (idx = 0; idx < unique_indirect_reg_count; idx++) { ... }
 *       BUG_ON(idx >= unique_indirect_reg_count);
 *       ...
 *       indirect_offset++;
 *   }
 * with NO check that indirect_offset < list_size, so a firmware with no
 * 0xFFFFFFFF terminator walks off the heap kmalloc() at gfx_v9_0.c:2133-2134
 * — CWE-125 OOB heap read.  And BUG_ON() expands to panic() in DragonFly
 * (sys/dev/drm/include/asm/bug.h:33-37), so >8 unique indirect IDs in the
 * firmware is an unconditional kernel panic / local DoS.
 *
 * Compile (in guest as unprivileged maxx):
 *   cc -O2 -o harness harness.c
 * Run:
 *   ./harness
 *
 * Expected output: a printout showing the overflow happens (the WARN fires
 * and the write proceeds anyway), and the BUG_ON-equivalent panic marker.
 */

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

#define MAX_START_OFFSETS 10
#define UNIQUE_INDIRECT_REG_COUNT 8
#define LIST_SIZE 1024

/* Mirror of unique_indirect_regs[8] / indirect_start_offsets[10] on the
 * stack of gfx_v9_1_init_rlc_save_restore_list(). */
static int indirect_start_offsets[MAX_START_OFFSETS];
static int indirect_start_offsets_count;
static int unique_indirect_regs[UNIQUE_INDIRECT_REG_COUNT];

static int warn_count;
static int overflow_count;
static int bug_panic;

/* Faithful transcription of gfx_v9_1_parse_ind_reg_list() with WARN_ON
 * converted to a non-fatal printf+continue (matching DragonFly semantics)
 * and BUG_ON converted to a panic marker that aborts the harness. */
static void parse_ind_reg_list(const int *register_list_format,
                              int indirect_offset, int list_size,
                              int *unique_indirect_regs,
                              int unique_indirect_reg_count,
                              int *indirect_start_offsets,
                              int *indirect_start_offsets_count,
                              int max_start_offsets_count)
{
    int idx;

    for (; indirect_offset < list_size; indirect_offset++) {
        /* WARN_ON — non-fatal: prints + continues (sys/dev/drm/include/asm/bug.h:43). */
        if (*indirect_start_offsets_count >= max_start_offsets_count) {
            warn_count++;
            /* NOTE: in the real kernel this proceeds anyway and writes OOB.
             * In the harness we deliberately DO the write into a too-small
             * (simulated) buffer so the overflow count is observable. */
        }
        /* Unconditional write past the buffer when count >= max. */
        if (*indirect_start_offsets_count >= max_start_offsets_count)
            overflow_count++;
        else
            indirect_start_offsets[*indirect_start_offsets_count] = indirect_offset;
        *indirect_start_offsets_count = *indirect_start_offsets_count + 1;

        while (register_list_format[indirect_offset] != 0xFFFFFFFF) {
            if (indirect_offset + 2 >= list_size) {
                /* Real kernel lacks this check -> reads past the heap buffer. */
                printf("[oob-read] indirect_offset=%d walks past list_size=%d "
                       "(CWE-125 at gfx_v9_0.c:2100-2101)\n",
                       indirect_offset, list_size);
                return;
            }
            indirect_offset += 2;

            for (idx = 0; idx < unique_indirect_reg_count; idx++) {
                if (unique_indirect_regs[idx] ==
                        register_list_format[indirect_offset] ||
                    !unique_indirect_regs[idx])
                    break;
            }

            /* BUG_ON — expands to panic() in DragonFly. */
            if (idx >= unique_indirect_reg_count) {
                bug_panic++;
                printf("[panic] BUG_ON(idx >= unique_indirect_reg_count) "
                       "at gfx_v9_0.c:2111 (>%d unique indirect IDs)\n",
                       unique_indirect_reg_count);
                return;
            }

            if (!unique_indirect_regs[idx])
                unique_indirect_regs[idx] = register_list_format[indirect_offset];

            indirect_offset++;
        }
    }
}

int main(void)
{
    /* Build a crafted register_list_format that has 14 indirect blocks
     * (each ending in 0xFFFFFFFF).  The first 10 fit; blocks 11-14
     * overflow indirect_start_offsets[] on the kernel stack. */
    int *fmt = calloc(LIST_SIZE, sizeof(int));
    if (!fmt) { perror("calloc"); return 1; }

    /* Each indirect block in the simplified grammar:
     *   [<start-marker> 0xFFFFFFFF]
     * plus one inner slot (so indirect_offset+=2; lookup; indirect_offset++)
     * giving 4 ints per indirect block. */
    int i, pos = 4;  /* skip reg_list_format_direct_reg_list_length */
    int block;
    for (block = 0; block < 14; block++) {
        fmt[pos++] = block + 100;   /* start marker (anything != 0xFFFFFFFF) */
        fmt[pos++] = block + 200;   /* inner register id (force unique -> BUG_ON after 8) */
        fmt[pos++] = 0;             /* dummy slot */
        fmt[pos++] = 0xFFFFFFFF;    /* terminator */
        if (pos + 8 >= LIST_SIZE) break;
    }
    int list_size = pos + 4;
    fmt[list_size - 1] = 0xFFFFFFFF;

    parse_ind_reg_list(fmt, 4 /* indirect_offset start */,
                       list_size,
                       unique_indirect_regs, UNIQUE_INDIRECT_REG_COUNT,
                       indirect_start_offsets, &indirect_start_offsets_count,
                       MAX_START_OFFSETS);

    printf("=== DF-1178 logic-level result ===\n");
    printf("WARN_ON fires (non-fatal): %d time(s)\n", warn_count);
    printf("Stack-buffer overflows past indirect_start_offsets[10]: %d slot(s) "
           "(CWE-121 at gfx_v9_0.c:2097)\n", overflow_count);
    printf("BUG_ON panics (CWE-assertion): %d\n", bug_panic);
    printf("\n");
    printf("On this guest the kernel path is HW-gated: gfx_v9_0_init_pg()\n");
    printf("(gfx_v9_0.c:2396) runs only during amdgpu attach to a real AMD\n");
    printf("Vega-class GPU (no AMD PCI device on this guest; vgapci0 is\n");
    printf("QEMU stdvga vendor 0x1234). The bug is real but unreachable here.\n");

    free(fmt);
    return 0;
}
