DragonFlyBSD Kernel Audit
DF-1753 / harness.c
← back to finding ↓ download raw
/*
 * DF-1753 - radeon_vce.c OOB read/write of IB buffer in cs_parse/cs_reloc
 *           via unchecked multi-dword command fields.
 *
 * Vulnerable code (sys/dev/drm/radeon/radeon_vce.c):
 *   565  while (p->idx < p->chunk_ib->length_dw) {
 *   566      uint32_t len = radeon_get_ib_value(p, p->idx);
 *   567      uint32_t cmd = radeon_get_ib_value(p, p->idx + 1);  // NO bound on idx+1
 *   ...
 *   569      if ((len < 8) || (len & 3))  return -EINVAL;
 *   ...cases read p->idx+2, p->idx+3, p->idx+8, p->idx+9, p->idx+10, p->idx+11, p->idx+12
 *
 * radeon_get_ib_value (radeon.h:1098) does NO bounds check on idx against
 * chunk_ib->length_dw. The len check at 569 only enforces min 8 / multiple
 * of 4 -- NOT that p->idx + (max field offset) <= length_dw. So a command
 * whose p->idx is within length_dw but p->idx+12 is past the end reads
 * OOB from the IB kdata / ib.ptr.
 *
 * Additionally radeon_vce_cs_reloc writes p->ib.ptr[lo]/[hi] with caller-
 * supplied indices (lines 493-494) without verifying lo/hi are within
 * length_dw -- OOB write into the next radeon_sa_bo object.
 *
 * Unprivileged render-node reach. This harness simulates the OOB read.
 */
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>

uint32_t radeon_get_ib_value_nobound(uint32_t *ib, int idx)
{
    /* radeon.h:1098-1105 -- no bounds check */
    return ib[idx];
}

int main(void)
{
    int length_dw = 4;
    uint32_t *ib = calloc(length_dw + 4, sizeof(uint32_t)); /* +4 to detect OOB */
    for (int i = 0; i < length_dw + 4; i++) ib[i] = 0xDEAD0000 + i;

    printf("=== DF-1753 radeon_vce cs_parse OOB IB field read harness ===\n");
    printf("chunk_ib->length_dw = %d\n", length_dw);
    printf("\n");

    /* Simulate the parse loop. p->idx=2 is within length_dw (=4). The
     * command type 0x03000001 (encode) reads idx+8/idx+9/idx+10/idx+11/idx+12
     * -- all OOB. */
    int p_idx = 2;
    int max_off = 12;
    printf("p->idx=%d (within length_dw=%d)\n", p_idx, length_dw);
    printf("Encode case reads up to p->idx+%d = %d (vs length_dw=%d)\n",
           max_off, p_idx + max_off, length_dw);
    printf("OOB field reads:\n");
    int oob = 0;
    for (int off = 0; off <= max_off; off++) {
        int idx = p_idx + off;
        uint32_t v = radeon_get_ib_value_nobound(ib, idx);
        int is_oob = (idx >= length_dw);
        printf("  radeon_get_ib_value(p, p->idx+%d) = ib[%d] = 0x%08x%s\n",
               off, idx, v, is_oob ? "   <-- OOB" : "");
        if (is_oob) oob++;
    }
    printf("\n");
    if (oob > 0) {
        printf("VERDICT: BUG CONFIRMED. cs_parse reads IB dwords past\n"
               "        chunk_ib->length_dw (no idx+len/4 <= length_dw check).\n"
               "        Same path writes p->ib.ptr[lo]/[hi] in cs_reloc;\n"
               "        combined, OOB read + OOB write of IB on render node.\n");
        free(ib);
        return 0;
    }
    printf("VERDICT: not reproduced.\n");
    free(ib);
    return 1;
}