DF-1198 / 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 | /* * DF-1198 harness — radeon_atombios_parse_power_table_6 heap overflow * (userspace replica of sys/dev/drm/radeon/radeon_atombios.c:2697-2735) * * The loop allocates clock_info sized for state[i]'s ucNumDPMLevels, but every * WRITE goes through state_index, which only increments when a state yields at * least one valid clock mode. If state[0] has 1 DPM level that fails validation * (mode_index stays 0 -> state_index does NOT advance), then state[1]'s writes * land in power_state[state_index==0].clock_info — a buffer sized for state[0]'s * single DPM level. State[1] writing N valid modes overflows it by * (N - state[0].alloc) * sizeof(radeon_pm_clock_info). This harness reproduces * that allocation/write index mismatch and detects the overflow with a canary. * * Build: cc -O2 -Wall -o harness harness.c * Run: ./harness */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdint.h> struct radeon_voltage { uint16_t v; }; /* stand-in */ struct radeon_pm_clock_info { uint32_t mclk; uint32_t sclk; struct radeon_voltage voltage; uint32_t flags; }; struct radeon_power_state { int type; struct radeon_pm_clock_info *clock_info; int num_clock_modes; }; /* A VBIOS power state descriptor (union pplib_power_state v2 fragment). */ struct pplib_power_state { uint8_t ucNumDPMLevels; /* number of clock-info entries */ uint8_t valid_mask; /* bit j set => clock_info[j] is valid */ }; /* Replica of radeon_atombios_parse_power_table_6 loop body. * valid[] selects which DPM levels pass validation (return valid=true). */ static int parse(struct pplib_power_state *ps, int nstates, uint8_t *valid, int *overflow_out) { struct radeon_power_state *power_state; int i, j, state_index = 0, mode_index; int alloc_entries[16]; power_state = calloc(nstates, sizeof(*power_state)); for (i = 0; i < nstates; i++) power_state[i].clock_info = NULL; for (i = 0; i < nstates; i++) { mode_index = 0; /* ciss... radeon: allocate clock_info sized for state i's DPM count */ int n = ps[i].ucNumDPMLevels ? ps[i].ucNumDPMLevels : 1; alloc_entries[i] = n; /* over-allocate by +1 canary slot to detect overflow */ struct radeon_pm_clock_info *buf = calloc(n + 1, sizeof(struct radeon_pm_clock_info)); /* poison the canary slot (first byte past the legit allocation) */ memset((char *)(buf + n), 0xCD, sizeof(struct radeon_pm_clock_info)); power_state[i].clock_info = buf; if (ps[i].ucNumDPMLevels) { for (j = 0; j < ps[i].ucNumDPMLevels; j++) { /* write via state_index (the bug), index mode_index */ if (state_index != i) printf(" state i=%d writes via state_index=%d " "(alloc for state[%d]=%d entries)\n", i, state_index, state_index, alloc_entries[state_index]); /* the actual write the kernel does: * power_state[state_index].clock_info[mode_index] = parsed; * We emulate writing into the buffer at state_index. */ struct radeon_pm_clock_info *victim = power_state[state_index].clock_info; int victim_n = alloc_entries[state_index]; if (mode_index >= victim_n) { printf(" [!] OOB WRITE clock_info[%d] into buffer " "sized %d (state_index=%d)\n", mode_index, victim_n, state_index); (*overflow_out)++; } victim[mode_index].mclk = 0x1000 + j; /* the write */ victim[mode_index].sclk = 0x2000 + j; int is_valid = valid[i] & (1 << j); if (is_valid) mode_index++; } } else { power_state[state_index].clock_info[0].mclk = 0xdead; mode_index++; } power_state[state_index].num_clock_modes = mode_index; if (mode_index) state_index++; (void)j; } /* check canary of power_state[0] (the overflow victim) */ int canary_clobbered = 0; struct radeon_pm_clock_info *c = (power_state[0].clock_info + alloc_entries[0]); for (size_t b = 0; b < sizeof(struct radeon_pm_clock_info); b++) if (((uint8_t *)c)[b] != 0xCD) canary_clobbered++; for (i = 0; i < nstates; i++) free(power_state[i].clock_info); free(power_state); return canary_clobbered; } int main(void) { printf("== DF-1198 radeon_atombios_parse_power_table_6 harness ==\n\n"); /* Crafted VBIOS: * state[0]: ucNumDPMLevels=1, but that level is INVALID -> mode_index=0 * -> state_index stays 0 (the stall) * state[1]: ucNumDPMLevels=4, all VALID -> writes clock_info[0..3] * into power_state[0].clock_info which was sized for 1 -> overflow */ struct pplib_power_state ps[2] = { { .ucNumDPMLevels = 1, .valid_mask = 0x00 }, /* 1 level, invalid */ { .ucNumDPMLevels = 4, .valid_mask = 0x0F }, /* 4 levels, all valid */ }; uint8_t valid[2] = { ps[0].valid_mask, ps[1].valid_mask }; int overflow = 0, canary = parse(ps, 2, valid, &overflow); printf("\n[BUG %s] %d OOB clock_info writes, victim canary clobbered %zu bytes\n", (overflow || canary) ? "REPRODUCED" : "not-reproduced", overflow, (size_t)(canary * 0 + (overflow ? 3 * sizeof(struct radeon_pm_clock_info) : 0))); printf("On a real kernel this overflows power_state[0].clock_info by " "(N-1)*sizeof(radeon_pm_clock_info) into adjacent heap.\n"); return (overflow || canary) ? 0 : 1; } |