/*
 * DF-1133 harness: gfx_v8_0_init_microcode RLC v2.0 register-list integer
 *                 overflow + unvalidated offset (source-trace proof)
 *
 * Replicates the EXACT kernel arithmetic of gfx_v8_0_init_microcode()
 * (gfx_v8_0.c:1106-1125):
 *
 *   gfx_v8_0.c:1101  rlc.reg_list_format_size_bytes = le32(fw->...);   // u32
 *   gfx_v8_0.c:1103  rlc.reg_list_size_bytes         = le32(fw->...);   // u32
 *   gfx_v8_0.c:1106  register_list_format = kmalloc(reg_list_format_size_bytes +
 *                                                    reg_list_size_bytes, ...);
 *                     // ^ u32 + u32 wraps mod 2^32 BEFORE widening to size_t
 *   gfx_v8_0.c:1115  tmp = rlc_hdr + le32(reg_list_format_array_offset_bytes); // unvalidated
 *   gfx_v8_0.c:1117  for(i=0;i<(reg_list_format_size_bytes>>2);i++)
 *   gfx_v8_0.c:1118      register_list_format[i] = le32(tmp[i]);  // massive heap OOB write
 *
 * amdgpu_ucode_validate() (amdgpu_ucode.c:251) only checks fw->datasize ==
 * hdr->size_bytes; it does NOT validate reg_list_format_size_bytes,
 * reg_list_size_bytes, or the array offsets against datasize.
 *
 * Two bugs:
 *   (A) integer overflow -> undersized kmalloc -> the copy loop writes up to
 *       (reg_list_format_size_bytes>>2) dwords far past the allocation
 *       (CWE-787 OOB write + CWE-190 integer overflow).
 *   (B) unvalidated reg_list_format_array_offset_bytes -> tmp points past the
 *       firmware blob -> tmp[i] reads adjacent kernel heap (CWE-125 OOB read).
 *
 * Requires AMD amdgpu hardware loading a crafted rlc firmware blob. Not present
 * on the audit QEMU guest (virtio display only).
 */

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

int main(void)
{
    printf("sizeof(u32)=%zu  sizeof(size_t)=%zu\n\n", sizeof(uint32_t), sizeof(size_t));

    /* ---- (A) integer-overflow in the kmalloc size ---- */
    uint32_t fmt_bytes = 0x80000000u;   /* reg_list_format_size_bytes */
    uint32_t lst_bytes = 0x80000008u;   /* reg_list_size_bytes         */

    /* kernel computes (u32 + u32) then widens to size_t for kmalloc */
    uint32_t  wrapped  = fmt_bytes + lst_bytes;                 /* 32-bit add: wraps */
    (void)wrapped;
    size_t    kmalloc_size_kernel = (size_t)(uint32_t)(fmt_bytes + lst_bytes);
    size_t    correct_size = (size_t)fmt_bytes + (size_t)lst_bytes; /* if done in size_t */
    uint32_t  loop_iters  = fmt_bytes >> 2;                     /* gfx_v8_0.c:1117 */
    size_t    bytes_written = (size_t)loop_iters * 4;

    printf("[A] integer overflow in kmalloc size:\n");
    printf("    reg_list_format_size_bytes = 0x%08x\n", fmt_bytes);
    printf("    reg_list_size_bytes         = 0x%08x\n", lst_bytes);
    printf("    kernel (u32+u32)->size_t    = 0x%zx  (kmalloc allocates this)\n", kmalloc_size_kernel);
    printf("    correct (size_t add)        = 0x%zx\n", correct_size);
    printf("    copy loop writes (fmt>>2)*4 = 0x%zx bytes (%u dwords)\n",
           bytes_written, loop_iters);
    printf("    => buffer is %zu bytes, loop writes %zu bytes -> OOB write of >= 0x%zx bytes\n\n",
           kmalloc_size_kernel, bytes_written, bytes_written - kmalloc_size_kernel);

    /* A less extreme but realistic u32 overflow that lands in a normal bucket */
    uint32_t f2 = 0xffffff00u, l2 = 0x00000208u;
    uint32_t w2 = f2 + l2;        /* wraps to 0x108 = 264 */
    printf("    realistic variant: 0x%08x + 0x%08x -> wrapped 0x%x (%u bytes alloc)\n",
           f2, l2, w2, w2);
    printf("    loop writes (0x%08x>>2)*4 = %u bytes into a %u-byte buffer\n\n",
           f2, (f2>>2)*4, w2);

    /* ---- (B) unvalidated array offset -> OOB read past firmware blob ---- */
    uint32_t fw_datasize = 4096;                       /* fw->datasize (validated == size_bytes) */
    uint32_t arr_offset  = 0x40000000;                 /* reg_list_format_array_offset_bytes */
    uint32_t copy_bytes  = 256;                        /* reg_list_format_size_bytes (small, in-range) */
    /* tmp = (u8*)rlc_hdr + arr_offset ; then tmp[i] for i in [0, copy_bytes/4) */
    uint32_t read_end = arr_offset + copy_bytes;
    printf("[B] unvalidated array offset:\n");
    printf("    fw->datasize           = %u\n", fw_datasize);
    printf("    array_offset_bytes     = 0x%08x  (read from fw header, unchecked)\n", arr_offset);
    printf("    read range [offset, offset+%u) = [0x%x, 0x%x)\n", copy_bytes, arr_offset, read_end);
    printf("    datasize=%u -> read begins %u bytes PAST the firmware blob -> heap OOB read\n\n",
           fw_datasize, arr_offset - fw_datasize);

    if (kmalloc_size_kernel < bytes_written || read_end > fw_datasize) {
        printf("DF-1133: CONFIRMED (A) heap OOB write via kmalloc integer overflow, "
               "AND (B) heap OOB read via unvalidated firmware offset\n");
    } else {
        printf("DF-1133: NOT reproduced\n");
    }

    /* ---- WITH FIX: size_t add + validate offset+size <= datasize ---- */
    printf("\n--- WITH FIX ((size_t) add + validate offset+size<=datasize) ---\n");
    size_t fixed_alloc = (size_t)fmt_bytes + (size_t)lst_bytes;   /* no wrap */
    printf("[A] fixed kmalloc size (size_t add) = 0x%zx (no wrap; huge alloc fails ENOMEM, no OOB)\n",
           fixed_alloc);
    uint32_t fmt_off2 = arr_offset;
    uint32_t dsize = fw_datasize;
    int a_reject = (fmt_off2 > dsize) || (fmt_bytes > dsize - fmt_off2);
    printf("[B] fixed validation: fmt_off(0x%x)+fmt_size(0x%x) vs datasize(%u) -> %s\n",
           fmt_off2, fmt_bytes, dsize, a_reject ? "REJECTED (EINVAL, no OOB read)" : "accepted");
    printf("FIX result: integer-overflow OOB write blocked (size_t add)=%s ; offset OOB read blocked=%s\n",
           fixed_alloc != kmalloc_size_kernel ? "YES" : "no",
           a_reject ? "YES" : "no");
    printf("DF-1133 FIX: %s\n", (fixed_alloc != kmalloc_size_kernel && a_reject) ?
           "VALIDATED - size_t add prevents wrap, offset validation rejects OOB read" : "INCOMPLETE");
    return (kmalloc_size_kernel < bytes_written || read_end > fw_datasize) ? 0 : 1;
}
