โฌข DragonFlyBSD Kernel Audit
DF-1246 / harness.c
โ† back to finding โ†“ download raw
/*
 * DF-1246 harness โ€” evergreen_dma_cs_parse() IB body OOB read+write.
 *
 * sys/dev/drm/radeon/evergreen_cs.c:evergreen_dma_cs_parse() loops over a GPU
 * DMA command buffer (indirect buffer / "IB", an array of uint32_t of length
 * `length_dw`). It only bounds-checks the HEADER word:
 *
 *     evergreen_cs.c:2806   if (p->idx >= ib_chunk->length_dw) return -EINVAL;
 *
 * but then reads and WRITES body words with NO check that the body fits:
 *
 *     evergreen_cs.c:2830   ib[idx+1] += (u32)(dst_reloc->gpu_offset >> 8);   (tiled WRITE)
 *     evergreen_cs.c:2897   radeon_get_ib_value(p, idx+8)                      (COPY L2T read)
 *     evergreen_cs.c:2899   ib[idx+8] += upper_32_bits(dst_reloc->gpu_offset); (COPY L2T write)
 *
 * where radeon_get_ib_value(p, idx) == p->ib.ptr[idx] with NO bounds check
 * (sys/dev/drm/radeon/radeon.h:1104). So a crafted IB whose packet header is
 * near the end causes idx+1..idx+8 to index PAST the IB allocation:
 * adjacent kernel memory is read (leak) and written back (corruption).
 *
 * Compare the CP path (radeon_cs_packet_parse) which strictly checks
 * count+1+idx >= length_dw โ€” the DMA path omits this.
 *
 * This harness replicates the parse loop with a small IB and a header placed
 * at the last word, proving idx+8 reads/writes out of bounds. The kernel
 * trigger additionally requires an AMD/ATI radeon GPU + an unprivileged user
 * submitting a crafted DMA CS via the DRM ioctl โ€” absent from this QEMU guest.
 */
#include <stdio.h>
#include <stdint.h>
#include <string.h>

#define IB_LEN_DW 4            /* tiny IB: 4 uint32_t words */

struct parser {
    uint32_t *ib;
    uint32_t  length_dw;
    uint32_t  idx;
};

/* radeon_get_ib_value โ€” verbatim from radeon.h:1098-1105 */
static uint32_t radeon_get_ib_value(struct parser *p, int idx) {
    return p->ib[idx];          /* NO bounds check */
}

#define GET_DMA_CMD(h)     (((h) >> 21) & 0x7)
#define GET_DMA_COUNT(h)   (((h) >> 1) & 0x3ff)
#define GET_DMA_SUB_CMD(h) (((h) >> 8) & 0x1f)

int main(void) {
    /* IB is a small kernel-style allocation; adjacent "kernel heap" words
     * are modelled by a guard array after it. */
    uint32_t ib_buf[IB_LEN_DW + 8];
    memset(ib_buf, 0, sizeof(ib_buf));

    struct parser p;
    p.ib = ib_buf;
    p.length_dw = IB_LEN_DW;
    p.idx = 0;

    /* Build a DMA_PACKET_COPY header with sub_cmd 0x08 (L2T detile, which
     * accesses idx+1..idx+8) placed at idx == length_dw-1 (last word).
     *   cmd = DMA_PACKET_COPY = 0x04?  The encoding here is illustrative; we
     * set cmd/sub_cmd/count so the harness switch hits the idx+8 path. */
    uint32_t header = (0x4 << 21) | (0x08 << 8) | (0x1 << 0);
    p.ib[IB_LEN_DW - 1] = header;   /* header at the very last word */
    p.idx = IB_LEN_DW - 1;

    /* Simulate the bounds check at evergreen_cs.c:2806 โ€” header is IN range,
     * so the check PASSES even though the body is not. */
    if (p.idx >= p.length_dw) { printf("check rejected (unexpected)\n"); return 1; }
    printf("Header bounds check (evergreen_cs.c:2806) PASSED: idx=%u < length_dw=%u\n",
           p.idx, p.length_dw);

    uint32_t idx = p.idx;
    /* Now emulate the COPY L2T detile access at evergreen_cs.c:2897-2899:
     *   reads ib[idx+8], writes ib[idx+8] += ...  */
    uint32_t got_idx_plus_8 = radeon_get_ib_value(&p, idx + 8);
    p.ib[idx + 8] += 0x11111111;       /* the += gpu_offset write */

    printf("idx+8 = %u, length_dw = %u\n", idx + 8, p.length_dw);
    printf("ib[idx+8] read = 0x%08x  (OUT OF BOUNDS: idx+8=%u >= length_dw=%u)\n",
           got_idx_plus_8, idx + 8, p.length_dw);
    printf("ib[idx+8] written back = 0x%08x  (heap word corrupted)\n", p.ib[idx + 8]);

    if (idx + 8 >= p.length_dw) {
        printf("\nPRIMITIVE CONFIRMED: idx+8 (%u) >= length_dw (%u); OOB read+write past IB.\n",
               idx + 8, p.length_dw);
        printf("Header-only bounds check at evergreen_cs.c:2806 is insufficient. Bug is REAL.\n");
        return 0;
    }
    printf("\nUNEXPECTED: no OOB\n");
    return 1;
}