DF-4903 / sim_tailread.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 | /* * DF-4903 logic harness — userland replica of the exact kernel flow in * sys/dev/powermng/powernow/powernow.c that leaks one uninitialised heap * word into the world-readable sysctl machdep.powernow.frequency.current. * * Replicates, with identical struct layout and (in)expressions: * - powernow_init() (powernow.c:366-397): kmalloc(M_DEVBUF, M_WAITOK) * [NOT zeroed], n_states from PST, k8_powernow_setperf(k8_get_curfreq()) * - k8_get_curfreq() (powernow.c:204-224): returns -1 when the CPU's * current fid/vid (from MSR 0xc0010042) is NOT in the decoded table * - k8_powernow_setperf() (powernow.c:226-313): `unsigned int freq` * receives -1 -> UINT_MAX; the `state_table[i].freq >= freq` match * loop never fires; i exits == n_states; line 310-311 then indexes * state_table[n_states] -- past the initialised prefix -> the * uninitialised 4-byte `freq` lands in the global `cpuspeed`. * * This is a LOGIC SIMULATION only (no AMD K8 hardware / no kernel MSR). * The heap is poisoned before the "decode" so the uninit read is visible. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdint.h> #define POWERNOW_MAX_STATES 16 struct k8pnow_state { int freq; uint8_t fid; uint8_t vid; }; struct k8pnow_cpu_state { struct k8pnow_state state_table[POWERNOW_MAX_STATES]; unsigned int n_states; unsigned int vst, mvs, pll, rvo, irt; int low; }; /* globals, as in powernow.c:125-126 */ static struct k8pnow_cpu_state *k8pnow_current_state = NULL; int cpuspeed; /* fake MSR status: CPU sits at cfid=0x0a/cvid=0x12 -- NOT in the table */ static uint64_t boot_fidvid_status = ((uint64_t)0x12 << 32) | 0x0a; /* k8_get_curfreq -- powernow.c:204-224 */ static int k8_get_curfreq(void) { unsigned int i; uint64_t status; int cfid, cvid; struct k8pnow_cpu_state *cstate; status = boot_fidvid_status; if ((status >> 31) & 1) /* PN8_STA_PENDING */ return 1; cfid = status & 0x3f; cvid = (status >> 32) & 0x1f; cstate = k8pnow_current_state; for (i = 0; i < cstate->n_states; i++) if (cstate->state_table[i].fid == cfid && cstate->state_table[i].vid == cvid) return (cstate->state_table[i].freq); /* Not reached */ /* <-- powernow.c:222 */ return -1; /* <-- powernow.c:223 */ } /* k8_powernow_setperf -- powernow.c:226-313 (MSR writes stubbed out) */ static int k8_powernow_setperf(unsigned int freq) { unsigned int i; /* <-- powernow.c:229 */ int cfid, cvid, fid = 0, vid = 0; struct k8pnow_cpu_state *cstate; uint64_t status = boot_fidvid_status; cfid = status & 0x3f; cvid = (status >> 32) & 0x1f; cstate = k8pnow_current_state; for (i = 0; i < cstate->n_states; i++) { /* <-- 245 */ if (cstate->state_table[i].freq >= freq) { /* int vs unsigned! */ fid = cstate->state_table[i].fid; vid = cstate->state_table[i].vid; break; } } /* loop never matched: freq == 0xFFFFFFFF > every MHz value. * i == n_states here, fid == vid == 0. (MSR transition phases * stubbed: assume HW settles to cfid==0/cvid==0 as requested.) */ cfid = fid; cvid = vid; if (cfid == fid || cvid == vid) /* <-- powernow.c:310 */ cpuspeed = cstate->state_table[i].freq; /* <-- 311: i==n_states! */ return 0; } int main(void) { struct k8pnow_cpu_state *cstate; unsigned int i; /* powernow_init(): kmalloc(sizeof, M_DEVBUF, M_WAITOK) -- NOT zeroed * (powernow.c:366); poison the allocation to emulate live heap data */ cstate = malloc(sizeof(struct k8pnow_cpu_state)); memset(cstate, 0xA5, sizeof(*cstate)); /* k8pnow_decode_pst() fills only n_states entries (say 3 of 16); * the remaining 13 stay at 0xA5A5A5A5 = live-heap garbage */ for (i = 0; i < 3; i++) { cstate->state_table[i].freq = 800 + 100 * (i + 4); cstate->state_table[i].fid = i + 4; cstate->state_table[i].vid = 0x10 + i; } cstate->n_states = 3; k8pnow_current_state = cstate; /* powernow_init():392 -- k8_powernow_setperf(k8_get_curfreq()) */ int cur = k8_get_curfreq(); printf("k8_get_curfreq() = %d (boot cfid/cvid not in table)\n", cur); k8_powernow_setperf(cur); /* -1 -> UINT_MAX */ printf("cpuspeed after init = 0x%08x (%d)\n", cpuspeed, cpuspeed); if (cpuspeed == 0xA5A5A5A5) { printf("LEAK CONFIRMED (logic): uninitialised heap word read at\n" "state_table[n_states=3] landed in the world-readable\n" "sysctl machdep.powernow.frequency.current\n"); return 0; } printf("unexpected value -- logic not reproduced\n"); return 1; } |