/*
 * 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);
}
