DF-4624 / ar9285_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 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 | /* * DF-4624 verbatim-extraction harness for * ar9285GetGainBoundariesAndPdadcs() (sys/dev/netif/ath/ath_hal/ar9002/ar9285_reset.c:636-801) * * Trigger: v4k EEPROM modalHeader.xpdGain == 0 -> ar9285SetPowerCalTable's * xpd-gain mask loop (ar9285_reset.c:593-602) leaves numXpdGain == 0. * With numXpdGains == 0: * - ar9285_reset.c:719 reads uninitialized stack minPwrT4[0] * - ar9285_reset.c:796-798 tail backfill runs with k == 0 and evaluates * pPDADCValues[k-1] == pdadcValues[-1] (the caller's * `static uint8_t pdadcValues[128]` at ar9285_reset.c:570) -- a 1-byte * OOB read *below* the array, replicated into all 128 PDADC values and * programmed to the PHY by ar5416WritePdadcValues(). * * The kernel function is compiled VERBATIM (build.sh seds it out of * /usr/src on the guest). HALASSERT is compiled out, mirroring a release * kernel (AH_ASSERT undefined, ah_internal.h:665-676). * * Modes: * 1 adjacency flavor: marker byte placed at pdadc[-1]; expect all 128 * PDADC entries == marker after the call. * 2 fault flavor: pdadc placed at a fresh page start with a PROT_NONE * guard page below; expect SIGSEGV with si_addr == (base - 1). * 3 uninitialized-stack flavor: stack poisoned with two different * patterns before the call; *pMinCalPower output must track the * poison (ar9285_reset.c:719 uninit read). * 4 fixed flavor (validation): early bail on numXpdGains == 0; sentinel * PDADC values untouched, minPower sentinel untouched. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdint.h> #include <signal.h> #include <setjmp.h> #include <sys/mman.h> #include <unistd.h> /* ---- kernel/HAL type shims ---- */ typedef int HAL_BOOL; #define AH_TRUE 1 #define AH_FALSE 0 #define AH_MAX(a, b) ((a) > (b) ? (a) : (b)) #define AH_MIN(a, b) ((a) < (b) ? (a) : (b)) #define HALASSERT(x) /* no-op in release kernels */ #define OS_MEMZERO(p, n) memset((p), 0, (n)) /* constants (ah_eeprom_v14.h / ah_eeprom_v4k.h, verbatim values) */ #define AR5416_PD_GAINS_IN_MASK 4 #define AR5416_PD_GAIN_ICEPTS 5 #define AR5416_NUM_PDADC_VALUES 128 #define AR5416_MAX_PWR_RANGE_IN_HALF_DB 64 #define AR5416_MAX_RATE_POWER 63 #define AR5416_BCHAN_UNUSED 0xFF #define AR5416_4K_NUM_PD_GAINS 2 #define AR5416_4K_EEP_PD_GAIN_BOUNDARY_DEFAULT 58 #define FREQ2FBIN(x, y) ((y) ? ((x) - 2300) : (((x) - 4800) / 5)) struct ath_hal { int dummy; }; struct ieee80211_channel { uint16_t ic_freq; uint32_t ic_flags; }; #define IEEE80211_CHAN_2GHZ 0x0080 #define IEEE80211_IS_CHAN_2GHZ(_c) (!!((_c)->ic_flags & IEEE80211_CHAN_2GHZ)) typedef struct { uint16_t synth_center; uint16_t ctl_center; uint16_t ext_center; } CHAN_CENTERS; /* AR9285 is Merlin 2.0-or-later, 5416 v2.0-or-later */ #define AR_SREV_5416_V20_OR_LATER(_ah) 1 #define AR_SREV_MERLIN_20_OR_LATER(_ah) 1 typedef struct { uint8_t pwrPdg[AR5416_4K_NUM_PD_GAINS][AR5416_PD_GAIN_ICEPTS]; uint8_t vpdPdg[AR5416_4K_NUM_PD_GAINS][AR5416_PD_GAIN_ICEPTS]; } __attribute__((packed)) CAL_DATA_PER_FREQ_4K; static void ar5416GetChannelCenters_shim(struct ath_hal *ah, const struct ieee80211_channel *chan, CHAN_CENTERS *centers) { /* minimal 20MHz 2GHz model; the extraction only uses synth_center */ centers->synth_center = centers->ctl_center = chan->ic_freq; centers->ext_center = chan->ic_freq + 10; } #define ar5416GetChannelCenters(a, b, c) ar5416GetChannelCenters_shim(a, b, c) /* ---- verbatim helpers from ah.c (extracted by build.sh) ---- */ #include "ah_extracted.inc" /* ---- verbatim kernel function under test (extracted by build.sh) ---- */ #include "ar9285_extracted.inc" /* ---- fault-flavor plumbing ---- */ static sigjmp_buf fault_jmp; static volatile uintptr_t fault_addr; static void *expect_page; static int expect_fault; static void fault_handler(int sig, siginfo_t *si, void *uc) { fault_addr = (uintptr_t)si->si_addr; if (expect_fault && si->si_addr == (char *)expect_page - 1) siglongjmp(fault_jmp, 1); _exit(9); } static CAL_DATA_PER_FREQ_4K rawdata[3]; static uint8_t bChans[3]; static struct ieee80211_channel chan = { 2437, IEEE80211_CHAN_2GHZ }; static uint16_t gainBoundaries[AR5416_PD_GAINS_IN_MASK]; static void fill_dataset(void) { int i, j; /* A plausible benign 2GHz cal dataset; irrelevant when numXpdGains==0 * but kept faithful. Pier fbins increasing, none UNUSED. */ bChans[0] = 10; bChans[1] = 60; bChans[2] = 137; /* FREQ2FBIN(2437,1) */ for (i = 0; i < 3; i++) { for (j = 0; j < AR5416_4K_NUM_PD_GAINS; j++) { int k; for (k = 0; k < AR5416_PD_GAIN_ICEPTS; k++) { rawdata[i].pwrPdg[j][k] = 20 + 10 * k + j; rawdata[i].vpdPdg[j][k] = 40 + 10 * k + j; } } } } /* Poison the stack BELOW the current frame so the callee's locals * (minPwrT4/maxPwrT4) land on a known pattern: recurse ~128 KB deep * filling each frame with the poison byte, unwind, then call the * target -- its frame reuses the freshly poisoned pages. */ static void spill(uint8_t poison, int depth) { volatile uint8_t buf[8192]; memset((void *)buf, poison, sizeof(buf)); __asm__ __volatile__("" :: "r"(buf) : "memory"); if (depth > 0) spill(poison, depth - 1); } static void run_poisoned(uint8_t poison, int16_t *minPower, uint8_t *pdadc) { spill(poison, 16); ar9285GetGainBoundariesAndPdadcs(NULL, &chan, rawdata, bChans, 3, 4, minPower, gainBoundaries, pdadc, 0 /* numXpdGains == 0 !! */); } int main(int argc, char **argv) { int mode = argc > 1 ? atoi(argv[1]) : 1; int16_t minPower; int i, bad; fill_dataset(); memset(gainBoundaries, 0, sizeof(gainBoundaries)); if (mode == 1 || mode == 3 || mode == 4) { /* adjacency flavor: pdadc at page2, marker at last byte page1 */ uint8_t *pages = mmap(NULL, 2 * 4096, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0); uint8_t *pdadc; if (pages == MAP_FAILED) { perror("mmap"); return 9; } pdadc = pages + 4096; *(pdadc - 1) = 0x5A; /* marker below the array */ memset(pdadc, 0xAA, 128); /* sentinel */ if (mode == 3) { minPower = 0x7C7C; run_poisoned(0xA7, &minPower, pdadc); printf("mode3 poison=0xA7: minPower=0x%04x " "(minPwrT4[0]~=0x%02x) pdadc[0]=0x%02x\n", (uint16_t)minPower, 0xA7, pdadc[0]); { int16_t mp2 = 0x7C7C; uint8_t *pd2 = malloc(256); memset(pd2, 0xAA, 256); run_poisoned(0x12, &mp2, pd2 + 64); printf("mode3 poison=0x12: minPower=0x%04x " "(minPwrT4[0]~=0x%02x)\n", (uint16_t)mp2, 0x12); free(pd2); } if (pdadc[0] == 0x5A) printf("mode3: pdadc[0] == marker 0x5A " "(OOB read replicated)\n"); } else { minPower = 0x7C7C; ar9285GetGainBoundariesAndPdadcs(NULL, &chan, rawdata, bChans, 3, 4, &minPower, gainBoundaries, pdadc, 0 /* numXpdGains == 0 !! */); bad = 0; for (i = 0; i < 128; i++) if (pdadc[i] != 0x5A) bad++; printf("minPower=0x%04x gainBoundaries[0]=%u " "pdadc[0]=0x%02x pdadc[127]=0x%02x " "(non-marker entries: %d)\n", (uint16_t)minPower, gainBoundaries[0], pdadc[0], pdadc[127], bad); if (mode == 1 && bad == 0 && pdadc[0] == 0x5A) { printf("DF-4624 REPRODUCED: all 128 PDADC " "values sourced from pdadcValues[-1] " "(ar9285_reset.c:797 with k==0)\n"); return 0; } if (mode == 4 && bad == 128 && pdadc[0] == 0x00) { printf("DF-4624 FIX VALIDATED: pdadc zeroed, " "no [-1] read, minPower sentinel " "0x%04x preserved\n", (uint16_t)minPower); return 0; } } return 1; } if (mode == 2) { /* fault flavor: pdadc at page start, PROT_NONE guard below */ uint8_t *pages = mmap(NULL, 2 * 4096, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0); uint8_t *pdadc; struct sigaction sa; if (pages == MAP_FAILED) { perror("mmap"); return 9; } if (mprotect(pages, 4096, PROT_NONE) != 0) { perror("mprotect"); return 9; } pdadc = pages + 4096; memset(pdadc, 0xAA, 128); memset(&sa, 0, sizeof(sa)); sa.sa_sigaction = fault_handler; sa.sa_flags = SA_SIGINFO; sigaction(SIGSEGV, &sa, NULL); expect_page = pdadc; expect_fault = 1; if (sigsetjmp(fault_jmp, 1) == 0) { minPower = 0x7C7C; ar9285GetGainBoundariesAndPdadcs(NULL, &chan, rawdata, bChans, 3, 4, &minPower, gainBoundaries, pdadc, 0 /* numXpdGains == 0 !! */); printf("no fault -- not reproduced\n"); return 1; } expect_fault = 0; printf("SIGSEGV si_addr=%p expected=%p (base-1)\n", (void *)fault_addr, (void *)(pdadc - 1)); if (fault_addr == (uintptr_t)(pdadc - 1)) { printf("DF-4624 REPRODUCED: fault at exactly " "pdadcValues[-1] (1 byte below the array, " "ar9285_reset.c:797)\n"); return 0; } return 1; } fprintf(stderr, "usage: %s <1|2|3|4>\n", argv[0]); return 2; } |