/*
 * overflow_proof.c -- code-level proof for DF-0291
 *
 * STATUS: This is a USERSPACE MODEL of the vulnerable kernel code path in
 *         sys/netproto/802_11/wlan/ieee80211_ioctl.c, NOT an in-kernel trigger.
 *
 * WHY A MODEL: The vulnerable SIOCS80211 / SIOCG80211 ioctls are only wired to
 *   *wlan vap* interfaces (ieee80211.c:567  ifp->if_ioctl = ieee80211_ioctl).
 *   A wlan vap can only be created through wlan_clone_create()
 *   (ieee80211_dragonfly.c:80), which at :92 does:
 *        ic = ieee80211_find_com(cp.icp_parent);
 *        if (ic == NULL) return ENXIO;          // <-- needs a RADIO parent
 *   The KVM audit guest has NO wifi radio hardware and no attached radio
 *   driver (only vtnet0/lo0 exist), so ieee80211_find_com() always returns NULL
 *   and no vap can ever exist -> the ioctls are unreachable from userspace on
 *   THIS guest.  (Verified: see env.txt -- ifconfig wlan create wlandev <any>
 *   -> "SIOCIFCREATE2: Device not configured" for every radio name.)
 *
 *   The overflow is, however, REAL in the shipped kernel source.  This program
 *   reproduces the exact vulnerable logic (the missing bounds check) against
 *   buffers laid out exactly like the kernel structs, so the byte counts and
 *   the corruption are concrete and undeniable.  Compile & run on the guest.
 *
 * References (audited master DEV tree):
 *   setmlme_assoc_adhoc          ieee80211_ioctl.c:1568-1608
 *     missing >IEEE80211_NWID_LEN check at :1580 (only checks ssid_len == 0)
 *     heap overflow #1 into iv_des_ssid[0].ssid[32]  at :1595
 *     heap overflow #2 into sr->sr_ssid[0].ssid[32]  at :1600
 *   sibling IOC_SSID SET handler :2671-2683 (correctly bounds ireq->i_len at :2673)
 *   GET IOC_SSID handler          :805-818
 *     stack overflow into char tmpssid[32] at :810 (uses corrupted iv_des_ssid[0].len)
 *   struct ieee80211req_mlme      ieee80211_ioctl.h:301-313  (im_ssid_len is uint8_t -> max 255)
 *   iv_des_ssid                   ieee80211_var.h:400  (struct ieee80211_scan_ssid iv_des_ssid[1])
 *   struct ieee80211_scan_req     ieee80211_ioctl.h:768-791 (sr_ssid[3], each ssid[32])
 *
 * Build: cc -O2 -Wall -o overflow_proof overflow_proof.c
 */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#define IEEE80211_NWID_LEN   32        /* sys/netproto/802_11/_ieee80211.h */
#define MAX_ATTACKER_LEN     255       /* im_ssid_len is uint8_t */

/* ---- struct models that matter (field SIZES, the kernel's real layout) ---- */

struct ieee80211_scan_ssid {            /* ieee80211_scan.h:76 */
	int          len;
	unsigned char ssid[IEEE80211_NWID_LEN];
};

/* model of struct ieee80211vap region around iv_des_ssid[0] */
struct vap_model {
	char                     pad_before[64];
	struct ieee80211_scan_ssid iv_des_ssid[1];   /* ieee80211_var.h:400 (array of 1) */
	unsigned char            heap_guard[256];    /* adjacent vap fields / next slab object */
};

/* model of struct ieee80211_scan_req */
struct scan_req_model {
	int   sr_flags;
	struct {
		int           len;
		unsigned char ssid[IEEE80211_NWID_LEN];
	} sr_ssid[3];                              /* ieee80211_ioctl.h:790  sr_ssid[3] */
	unsigned char heap_guard[256];              /* past end of kmalloc'd scan_req */
};

#define CANARY 0xCD

static void fill_canary(unsigned char *p, size_t n) { memset(p, CANARY, n); }
static int first_clobbered(const unsigned char *p, size_t n) {
	for (size_t i = 0; i < n; i++) if (p[i] != CANARY) return (int)i;
	return -1;
}

/*
 * Mirror of setmlme_assoc_adhoc (ieee80211_ioctl.c:1568).  The ONLY guard is
 * the zero-length check at :1580; there is NO ssid_len > IEEE80211_NWID_LEN
 * check (the sibling IOC_SSID SET handler HAS one at :2673).
 */
static void setmlme_assoc_adhoc_model(struct vap_model *vap,
                                      struct scan_req_model *sr,
                                      int ssid_len,
                                      const unsigned char *ssid)
{
	if (ssid_len == 0) {                 /* :1580 */
		printf("  setmlme_assoc_adhoc: ssid_len==0 -> EINVAL (return)\n");
		return;
	}
	/* :1593-1595 */
	memset(vap->iv_des_ssid[0].ssid, 0, IEEE80211_NWID_LEN);
	vap->iv_des_ssid[0].len = ssid_len;
	memcpy(vap->iv_des_ssid[0].ssid, ssid, ssid_len);   /* HEAP OVERFLOW #1 (:1595) */
	/* :1600-1601 */
	memcpy(sr->sr_ssid[0].ssid, ssid, ssid_len);          /* HEAP OVERFLOW #2 (:1600) */
	sr->sr_ssid[0].len = ssid_len;
}

/*
 * Mirror of the GET IOC_SSID INIT/SCAN branch (ieee80211_ioctl.c:805-817).
 * Models the kernel stack frame: char tmpssid[32] (:801) followed by whatever
 * lives above it on the stack (return addr, saved regs, locals).  We over-size
 * the allocation so the demo does not crash, but the LOGICAL 32-byte boundary
 * and the byte count past it are exactly what the kernel experiences.
 */
static void get_ssid_model(int corrupted_len, const unsigned char *src)
{
	unsigned char *frame = calloc(1, 512);   /* oversized so the demo won't crash */
	char *tmpssid = (char *)frame;           /* :801  char tmpssid[IEEE80211_NWID_LEN]; */
	unsigned char *stack_above = frame + IEEE80211_NWID_LEN;
	fill_canary(stack_above, 512 - IEEE80211_NWID_LEN);

	/* :809-810 */
	/* (ireq->i_len = vap->iv_des_ssid[0].len;) */
	memcpy(tmpssid, src, corrupted_len);     /* STACK OVERFLOW (:810) */

	int past = corrupted_len - IEEE80211_NWID_LEN;
	int at = first_clobbered(stack_above, 512 - IEEE80211_NWID_LEN);
	printf("  GET IOC_SSID: memcpy %d bytes into 32-byte stack tmpssid -> "
	       "%d bytes written past end (first clobber @ stack+%d)\n",
	       corrupted_len, past > 0 ? past : 0, at);
	free(frame);
}

int main(void)
{
	struct vap_model      *vap = calloc(1, sizeof(*vap));
	struct scan_req_model *sr  = calloc(1, sizeof(*sr));
	unsigned char          attacker[MAX_ATTACKER_LEN];

	printf("=== DF-0291 code-level overflow proof (userspace model) ===\n");
	printf("IEEE80211_NWID_LEN = %d ; attacker im_ssid_len (uint8_t max) = %d\n",
	       IEEE80211_NWID_LEN, MAX_ATTACKER_LEN);
	printf("\n");

	/* attacker-controlled payload (in the kernel the first 32 bytes are the
	 * user-supplied im_ssid, bytes 33..255 are kernel-stack residue because
	 * mlme.im_ssid is the LAST field of struct ieee80211req_mlme and the
	 * source pointer is &mlme.im_ssid). */
	memset(attacker, 0x41, sizeof(attacker));

	fill_canary(vap->heap_guard, sizeof(vap->heap_guard));
	fill_canary(sr->heap_guard, sizeof(sr->heap_guard));

	printf("[1] SET-path: SIOCS80211 -> setmlme_assoc_adhoc, im_ssid_len = %d\n",
	       MAX_ATTACKER_LEN);
	setmlme_assoc_adhoc_model(vap, sr, MAX_ATTACKER_LEN, attacker);

	int g1 = first_clobbered(vap->heap_guard, sizeof(vap->heap_guard));
	int g2 = first_clobbered(sr->heap_guard, sizeof(sr->heap_guard));
	int o1 = MAX_ATTACKER_LEN - IEEE80211_NWID_LEN;
	printf("  HEAP OVERFLOW #1 (iv_des_ssid[0].ssid, :1595): %d bytes past the "
	       "32-byte field into ieee80211vap heap (guard first clobbered @+%d)\n",
	       o1, g1);
	printf("  HEAP OVERFLOW #2 (sr->sr_ssid[0].ssid, :1600): %d bytes past the "
	       "32-byte field; spilled into sr_ssid[1]/sr_ssid[2] and beyond "
	       "(post-struct guard first clobbered @+%d)\n",
	       o1, g2);
	printf("\n");

	printf("[2] GET-path amplification: SIOCG80211 IOC_SSID reads the corrupted "
	       "iv_des_ssid[0].len = %d\n", vap->iv_des_ssid[0].len);
	get_ssid_model(vap->iv_des_ssid[0].len, vap->iv_des_ssid[0].ssid);
	printf("\n");

	printf("[3] Contrast -- sibling IOC_SSID SET handler (:2671) DOES guard:\n");
	printf("    if (ireq->i_val != 0 || ireq->i_len > IEEE80211_NWID_LEN) return EINVAL;\n");
	printf("    setmlme_assoc_adhoc (:1580) has ONLY: if (ssid_len == 0) return EINVAL;\n");
	printf("    -> the adhoc path is the ONLY unguarded sink.\n");
	printf("\n");

	printf("CONCLUSION: the overflow logic is real (up to %d bytes past a 32-byte\n"
	       "buffer on BOTH the heap set-path and the stack get-path). On the KVM\n"
	       "audit guest it is UNREACHABLE from userspace because no wlan vap can\n"
	       "be created without a wifi radio parent (see env.txt / VERDICT.md).\n",
	       o1);

	free(vap);
	free(sr);
	return 0;
}
