DF-3684 / pp_clocks_stack.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 146 147 148 | /* * DF-3684 harness: verbatim reproduction of smu7_get_sclks()/smu7_get_mclks() * from sys/dev/drm/amd/powerplay/hwmgr/smu7_hwmgr.c:4613-4675, driven with a * VBIOS dependency count of 255, writing into a `struct amd_pp_clocks` laid * out exactly as in the kernel (dm_pp_interface.h:161-167: clock[16], * latency[16]) and placed on the stack of a caller modelled on * dm_pp_get_clock_levels_by_type() (amdgpu_dm_pp_smu.c:296-316, where * `struct amd_pp_clocks pp_clks = { 0 };` is a STACK variable followed by * `struct amd_pp_simple_clock_info validation_clks`). * * Expected: writes clock[16..254] and latency[16..254] => 956 bytes past each * 64-byte array, i.e. ~956 bytes past the end of the 132-byte struct on the * caller's stack frame (kernel stack buffer overflow with attacker-controlled * u32 clock values). */ #include <stdio.h> #include <stdint.h> #include <string.h> #include <stdlib.h> #include <stddef.h> #define MAX_NUM_CLOCKS 16 struct amd_pp_clocks { uint32_t count; uint32_t clock[MAX_NUM_CLOCKS]; uint32_t latency[MAX_NUM_CLOCKS]; }; /* model of the phm_ppt_v1_clock_voltage_dependency_table (count + entries) */ struct dep_table { uint32_t count; struct { uint32_t clk; uint32_t vddci; uint32_t mvdd; } entries[255]; }; /* ---- verbatim loops (smu7_hwmgr.c:4625-4627 and 4662-4667) ---- */ static void smu7_get_sclks_body(struct amd_pp_clocks *clocks, struct dep_table *dep) { int i; for (i = 0; i < (int)dep->count; i++) clocks->clock[i] = dep->entries[i].clk * 10; clocks->count = dep->count; } static void smu7_get_mclks_body(struct amd_pp_clocks *clocks, struct dep_table *dep) { int i; for (i = 0; i < (int)dep->count; i++) { clocks->clock[i] = dep->entries[i].clk * 10; clocks->latency[i] = 35; /* smu7_get_mem_latency() value */ } clocks->count = dep->count; } /* ---- end verbatim ---- */ /* caller frame model: mirrors dm_pp_get_clock_levels_by_type()'s locals */ struct caller_frame { struct amd_pp_clocks pp_clks; struct { uint32_t engine_max_clock, memory_max_clock; } validation_clks; uint64_t canary; uint64_t retaddr_model; }; /* Phase 1: same geometry, but the "frame" sits inside a padded heap block so * the overflow can be accounted safely (post[] is 2 KB: the overflow needs * ~956 bytes past the frame). */ static int accounting(struct dep_table *dep) { struct { uint64_t pad[8]; struct caller_frame f; uint64_t post[300]; } s; int i, oob = 0; memset(&s, 0, sizeof(s)); s.f.canary = 0x4141414141414141ULL; s.f.retaddr_model = 0xdeadbeef00000000ULL; for (i = 0; i < 8; i++) s.post[i] = 0xB0B0B0B0B0B0B0B0ULL; smu7_get_sclks_body(&s.f.pp_clks, dep); smu7_get_mclks_body(&s.f.pp_clks, dep); printf("sizeof(amd_pp_clocks)=%zu, dep->count=%u, max array elems=%d\n", sizeof(struct amd_pp_clocks), dep->count, MAX_NUM_CLOCKS); printf("caller frame offsets: pp_clks@%zu validation_clks@%zu canary@%zu retaddr@%zu\n", offsetof(struct caller_frame, pp_clks), offsetof(struct caller_frame, validation_clks), offsetof(struct caller_frame, canary), offsetof(struct caller_frame, retaddr_model)); for (i = MAX_NUM_CLOCKS; i < (int)dep->count; i++) { uint32_t expect = dep->entries[i].clk * 10; if (s.f.pp_clks.clock[i] == expect) { if (!oob) printf("first OOB clock[] index: %d (offset +%d bytes past array end)\n", i, (i - MAX_NUM_CLOCKS) * 4); oob++; } } printf("OOB u32 writes past clock[16] and latency[16]: %d each (%d bytes each)\n", oob, oob * 4); { char *end_of_struct = (char *)&s.f.pp_clks + sizeof(s.f.pp_clks); int past = (int)((char *)&s.f.pp_clks.clock[dep->count - 1] + 4 - end_of_struct); printf("bytes written past end of amd_pp_clocks struct: %d\n", past); } printf("validation_clks.engine_max_clock corrupted to: 0x%x\n", s.f.validation_clks.engine_max_clock); printf("caller-frame canary: 0x%llx (expected 0x4141414141414141)\n", (unsigned long long)s.f.canary); printf("return-address model slot: 0x%llx (expected 0xdeadbeef00000000)\n", (unsigned long long)s.f.retaddr_model); return oob; } /* Phase 2: the real thing - pp_clks as a genuine stack local like * dm_pp_get_clock_levels_by_type(); expect the smash to kill the process. */ static int live_smash(struct dep_table *dep) { struct caller_frame f; /* ON THE STACK */ volatile uint64_t marker = 0x5A5A5A5A5A5A5A5AULL; memset((void *)&f, 0, sizeof(f)); printf("phase 2: overflowing a real stack-local amd_pp_clocks (marker=%llx)...\n", (unsigned long long)marker); smu7_get_sclks_body(&f.pp_clks, dep); smu7_get_mclks_body(&f.pp_clks, dep); printf("UNEXPECTED: survived the stack smash\n"); return 1; } int main(void) { struct dep_table dep; int i; setbuf(stdout, NULL); /* unbuffered: prints survive the stack smash */ /* crafted VBIOS: 255 MCLK dependency entries */ dep.count = 255; for (i = 0; i < 255; i++) dep.entries[i].clk = 100000 + i * 2500; /* 10kHz units */ if (accounting(&dep) < 200) return 1; printf("RESULT: STACK OVERFLOW CONFIRMED (phase 1 accounting) - clock values " "written past amd_pp_clocks into adjacent caller stack locals; " "phase 2 now performs the smash on a live stack frame\n"); return live_smash(&dep); } |