DF-1209 / harness.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 | /* * 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; } |