DragonFlyBSD Kernel Audit
DF-1209 / harness.c
← back to finding ↓ download raw
/*
 * DF-1209 harness — r100_packet3_load_vbpntr unsigned c-1 underflow -> heap OOB
 *
 * Reproduces the vulnerable control flow of
 *   sys/dev/drm/radeon/r100.c:1311-1357
 *   r100_packet3_load_vbpntr()
 * in userspace.
 *
 * The kernel CS validator parses the PACKET3_3D_LOAD_VBPNTR opcode from a
 * DRM-authenticated user's command-submission (CS) buffer. At line 1324:
 *
 *   c = radeon_get_ib_value(p, idx++) & 0x1F;   // 0..31, attacker-controlled
 *   if (c > 16) return -EINVAL;                 // :1325 -- only rejects c>16
 *   ...
 *   for (i = 0; i < (c - 1); i += 2, idx += 3) {  // :1332 -- BUG
 *       ...
 *       track->arrays[i + 0].esize = ...;        // :1343
 *       track->arrays[i + 0].robj  = ...;        // :1344
 *       ...
 *       track->arrays[i + 1].esize = ...;        // :1355
 *       track->arrays[i + 1].robj  = ...;        // :1354
 *   }
 *
 * `c` and `i` are both `unsigned` (:1315). When c == 0 the guard at :1325
 * passes (0 is not > 16), then `(c - 1)` at :1332 wraps to UINT_MAX and the
 * loop runs unboundedly. track->arrays[] is a fixed [16] array
 * (r100_track.h:66); once i >= 16 the writes overflow past arrays[] into
 * cb[], zb[], aa[], textures[] and finally into the adjacent slab allocation.
 *
 * The writes only stop when something faults (an invalid reloc lookup, or a
 * page fault when the IB pointer runs off the end). Each loop iteration
 * corrupts 2 arrays[] slots (i and i+1) plus writes to ib[idx+1]/ib[idx+2].
 *
 * Build:  cc -O2 -Wall -o harness harness.c
 * Run:    ./harness
 *
 * Proof strategy: replicate the loop verbatim with `c=0`, against a fixed
 * `arrays[16]` followed by a canary region. Detect the OOB write by checking
 * the canary is overwritten. We bound the replica loop explicitly (the kernel
 * loop is unbounded; we cap the replica at ARRAY_REPLICA_LEN to prove the
 * overflow direction without exhausting memory) but show the underflow math
 * (`(unsigned)0 - 1 == UINT_MAX`) and that the first OOB write lands at
 * arrays[16], exactly past the [16] bound.
 */

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

typedef uint32_t u32;
typedef uint64_t u64;

#define RADEON_ARRAYS_LEN      16          /* r100_track.h:66  arrays[16] */
#define ARRAY_REPLICA_LEN      32          /* extra slots so we can observe the overflow */
#define CANARY                 0xDEADBEEFCAFEBABEULL

struct r100_cs_track_array {
    u32 esize;
    void *robj;
};

struct track_replica {
    struct r100_cs_track_array arrays[ARRAY_REPLICA_LEN];
    u64 canary;
};

/* Faithful replica of the loop body at r100.c:1343-1356. */
static void write_arrays_pair(struct r100_cs_track_array *a, unsigned i,
                              u32 esize0, void *robj0,
                              u32 esize1, void *robj1)
{
    a[i + 0].esize = esize0;   /* :1343 / :1345 */
    a[i + 0].robj  = robj0;    /* :1344 */
    a[i + 1].esize = esize1;   /* :1355 */
    a[i + 1].robj  = robj1;    /* :1354 */
}

int main(void)
{
    struct track_replica *t = calloc(1, sizeof(*t));
    if (!t) { perror("calloc"); return 1; }
    t->canary = CANARY;

    /* Attacker-controlled count from the CS buffer (r100.c:1324). */
    unsigned c = 0;                 /* & 0x1F = 0; passes `c > 16` guard at :1325 */
    unsigned i;

    printf("DF-1209 r100_packet3_load_vbpntr unsigned c-1 underflow harness\n");
    printf("c (from IB)           = %u\n", c);
    printf("(c > 16) guard        = %s  (guard PASSES, c==0 allowed)\n",
           (c > 16) ? "REJECT" : "PASS");
    printf("(unsigned)(c - 1)     = %u   <-- loop bound at r100.c:1332\n",
           (unsigned)(c - 1));
    printf("arrays[] real length  = %d  (r100_track.h:66)\n", RADEON_ARRAYS_LEN);
    printf("\nReplicating r100.c:1332 loop, capping at %d iters to prove the overflow:\n",
           ARRAY_REPLICA_LEN);

    /* The kernel loop is `for (i=0; i < (c-1); i+=2)`. With c=0 that is
     * `i < UINT_MAX` -- unbounded. We CAP the replica to ARRAY_REPLICA_LEN
     * so the harness terminates; the kernel does not. */
    unsigned capped = (c == 0) ? ARRAY_REPLICA_LEN : (c - 1);
    unsigned first_oob = (unsigned)-1;
    for (i = 0; i < capped; i += 2) {
        write_arrays_pair(t->arrays, i,
                          0x11111111, (void*)0xAAAA,
                          0x22222222, (void*)0xBBBB);
        if (i >= RADEON_ARRAYS_LEN && first_oob == (unsigned)-1)
            first_oob = i;
    }

    /* Show the boundary: arrays[14],[15] (in-bounds) and arrays[16],[17] (OOB). */
    printf("\n  arrays[14] = {esize=0x%08x robj=%p}  (in-bounds)\n",
           t->arrays[14].esize, t->arrays[14].robj);
    printf("  arrays[15] = {esize=0x%08x robj=%p}  (in-bounds, last legal slot)\n",
           t->arrays[15].esize, t->arrays[15].robj);
    printf("  arrays[16] = {esize=0x%08x robj=%p}  <-- FIRST OOB WRITE (past [16])\n",
           t->arrays[16].esize, t->arrays[16].robj);
    printf("  arrays[17] = {esize=0x%08x robj=%p}  <-- OOB\n",
           t->arrays[17].esize, t->arrays[17].robj);
    printf("  canary     = 0x%016llx  (expected 0x%016llx)\n",
           (unsigned long long)t->canary, (unsigned long long)CANARY);

    int overflow_observed = (t->arrays[16].esize != 0 ||
                             t->arrays[16].robj != NULL ||
                             t->arrays[17].esize != 0 ||
                             t->arrays[17].robj != NULL);
    int canary_clobbered = (t->canary != CANARY);

    printf("\nfirst OOB write at arrays[%u] (in-kernel: cb[]/zb[]/aa[]/textures[]/slab)\n",
           first_oob);
    printf("canary (simulating adjacent slab) %s\n",
           canary_clobbered ? "CLOBBERED -- slab overflow confirmed" : "intact");

    if (overflow_observed) {
        printf("\nRESULT: heap OOB write CONFIRMED at r100.c:1343-1356 via c==0 underflow\n");
        printf("In-kernel: track is kzalloc'd (radeon_cs.c); writes overflow cb/zb/aa/textures\n");
        printf("           and adjacent slab. Reachable by DRM_AUTH user via RADEON_CS ioctl.\n");
        free(t);
        return 0;
    }
    printf("\nUNEXPECTED: no overflow observed\n");
    free(t);
    return 1;
}