⬢ DragonFlyBSD Kernel Audit
DF-3576 / iwn_timing_div0.c
← back to finding ↓ download raw
/*
 * DF-3576 PoC harness: division by zero in iwn_set_timing()
 * sys/dev/netif/iwn/if_iwn.c:5583-5604 (DragonFlyBSD)
 *
 * The arithmetic below is copied verbatim from iwn_set_timing()
 * (if_iwn.c:5590-5598):
 *
 *      memset(&cmd, 0, sizeof cmd);
 *      memcpy(&cmd.tstamp, ni->ni_tstamp.data, sizeof (uint64_t));
 *      cmd.bintval = htole16(ni->ni_intval);
 *      cmd.lintval = htole16(10);
 *      val = (uint64_t)ni->ni_intval * IEEE80211_DUR_TU;
 *      mod = le64toh(cmd.tstamp) % val;          <-- divisor == 0
 *
 * ni_intval == 0 happens when the station associates to an AP whose
 * beacon/probe-response carries bintval == 0:
 *   - ieee80211_input.c:545 parses bintval from the frame
 *   - ieee80211_input.c:700-707 marks IEEE80211_BPARSE_BINTVAL_INVALID
 *   - BUT the STA scan consumer never checks that bit:
 *     ieee80211_scan_sta.c:314  ise->se_intval = sp->bintval;
 *   - ieee80211_node.c:818 (ieee80211_sta_join) ni->ni_intval = se->se_intval;
 *   - on IEEE80211_S_RUN, iwn_newstate -> iwn_run (if_iwn.c:7237)
 *     -> iwn_set_timing -> "% 0" -> #DE -> kernel panic.
 *
 * This userland model executes the exact expression with ni_intval == 0.
 * Success criterion: process dies with SIGFPE (Floating point exception),
 * exit status 136 (128+8), proving the kernel would take a #DE on this path.
 *
 * Build: cc -O2 -o iwn_timing_div0 iwn_timing_div0.c
 * Run:   ./iwn_timing_div0
 */
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

/* if_iwnreg.h: struct iwn_cmd_timing (relevant prefix) */
struct iwn_cmd_timing {
	uint64_t	tstamp;
	uint16_t	bintval;
	uint16_t	lintval;
	uint32_t	binitval;
} __attribute__((__packed__));

#define le16toh(x)	(x)
#define le64toh(x)	(x)
#define htole16(x)	(x)
#define IEEE80211_DUR_TU	1024

/* verbatim arithmetic of iwn_set_timing(), if_iwn.c:5590-5598 */
static void
iwn_set_timing_model(uint16_t ni_intval, const uint8_t ni_tstamp[8])
{
	struct iwn_cmd_timing cmd;
	uint64_t val, mod;

	memset(&cmd, 0, sizeof cmd);
	memcpy(&cmd.tstamp, ni_tstamp, sizeof (uint64_t));
	cmd.bintval = htole16(ni_intval);
	cmd.lintval = htole16(10);

	/* Compute remaining time until next beacon. */
	val = (uint64_t)ni_intval * IEEE80211_DUR_TU;
	printf("ni_intval=%u -> val=%llu ; taking modulo\n",
	    ni_intval, (unsigned long long)val);
	fflush(stdout);
	mod = le64toh(cmd.tstamp) % val;		/* <-- divide by zero */
	cmd.binitval = (uint32_t)(val - mod);

	printf("survived: binitval=%u (BUG NOT REPRODUCED)\n", cmd.binitval);
}

int
main(int argc, char *argv[])
{
	uint8_t tstamp[8];

	memset(tstamp, 0xAB, sizeof tstamp);

	/* control: a normal AP (bintval=100) must not fault */
	iwn_set_timing_model(100, tstamp);
	printf("control (ni_intval=100) OK\n");

	/* malicious AP: beacon carried bintval == 0 */
	iwn_set_timing_model(0, tstamp);

	printf("UNEXPECTED: survived ni_intval=0\n");
	exit(1);
}