/*
 * DF-0350 — Code-level harness for the unbounded mesh route-table growth
 *            + attacker-controlled lifetime in ieee80211_hwmp.c / ieee80211_mesh.c
 *
 * WHY A HARNESS (not a runtime PoC)
 *   This KVM audit guest has NO WiFi radio: `ifconfig -l` shows only
 *   `vtnet0 lo0`; no wlan vap, no ath/iwm/iwn kld bound to hardware, and no
 *   netgraph 802.11 injection node (sys/netgraph* has no ng_80211/ng_wlan).
 *   The runtime mesh RX path that reaches hwmp_recv_preq() therefore CANNOT
 *   be exercised on this guest — identical to the already-settled findings
 *   DF-0393 (Mesh ID heap overflow), DF-0594 (TKIP RX underflow) and
 *   DF-0616 (netmap RX overflow), all resolved via faithful in-process
 *   harnesses because the live 802.11/netmap RX path is unreachable without
 *   the relevant hardware. We follow that precedent.
 *
 *   This harness embeds the VERBATIM allocation/insertion/lifetime code from:
 *     - mesh_rt_add_locked()   sys/netproto/802_11/wlan/ieee80211_mesh.c:194-228
 *     - ieee80211_mesh_rt_update() sys/netproto/802_11/wlan/ieee80211_mesh.c:266-303
 *     - the PREQ-originator handling in hwmp_recv_preq()
 *                              sys/netproto/802_11/wlan/ieee80211_hwmp.c:1055-1097
 *   and drives it with an attacker-shaped frame stream (distinct spoofed
 *   originator MACs + maximal preq_lifetime) to prove the two claims:
 *     (1) the route table grows WITHOUT ANY BOUND  (no cap on ms_routes), and
 *     (2) preq->preq_lifetime (attacker uint32) flows UNVALIDATED into
 *         rt->rt_lifetime, letting a single PREQ pin ~150 B for ~49 days.
 *
 * FAITHFULNESS
 *   - struct ieee80211_mesh_route is reconstructed field-for-field from
 *     sys/netproto/802_11/ieee80211_mesh.h:420-440 (incl. the HWMP priv).
 *   - struct ieee80211_mesh_state carries the TAILQ ms_routes
 *     (ieee80211_mesh.h:525) and the route lock (a no-op in userspace).
 *   - mesh_rt_add_locked() body is copied character-for-character, including
 *     the `#if defined(__DragonFly__)` kmalloc path (modelled with malloc(3)).
 *   - ieee80211_mesh_rt_update() body is copied character-for-character.
 *   - The PREQ-originator block from hwmp_recv_preq() is modelled: find-or-add
 *     the originator route, then update its lifetime with the attacker value.
 *
 * BUILD
 *   cc -O2 -Wall -o harness harness.c            # BUG PRESENT (unbounded)
 *   cc -O2 -Wall -DFIXED -o harness_fixed harness.c   # WITH CAP+CLAMP FIX
 *
 * RUN
 *   ./harness [Nattack]            # default Nattack=100000 distinct originators
 */

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

/* ---- Constants reproduced from the kernel headers ---- */
#define IEEE80211_ADDR_LEN         6
#define IEEE80211_MESHID_LEN       32      /* ieee80211.h:200 */
#define M_80211_MESH_RT            "M_80211_MESH_RT"
#define M_INTWAIT                  0x0000  /* placeholder: "wait ok"          */
#define M_ZERO                     0x0100  /* ieee80211_mesh.c uses M_ZERO    */

/* ---- Fix constants — MUST match fix.diff applied to sys/. The harness is
 *      compiled with -DFIXED to demonstrate the fix closes the bug. ---- */
#define IEEE80211_MESH_RT_MAX              4096     /* per-vap route cap            */
#define IEEE80211_MESH_RT_LIFETIME_MAX_MS  (60*1000)/* clamp: 60 s (was 49 days)   */
/* the harness references these as _FIX / _MAX_MS to avoid clashing with the
 * unpatched build; alias them: */
#define IEEE80211_MESH_RT_MAX_FIX          IEEE80211_MESH_RT_MAX

/* HWMP private route data (ieee80211_hwmp.c:139-146): hr_seq, hr_preqid,
 * hr_origseq (+ pad). mpp_privlen = sizeof(struct ieee80211_hwmp_route). */
struct ieee80211_hwmp_route {
    uint32_t hr_seq;
    uint32_t hr_preqid;
    uint32_t hr_origseq;
};

/* ---- struct ieee80211_mesh_route reproduced VERBATIM (field order & sizes)
 *      from sys/netproto/802_11/ieee80211_mesh.h:420-440 (x86_64 layout).
 *   TAILQ_ENTRY(ieee80211_mesh_route) rt_next;   // 2 ptrs = 16 B
 *   struct ieee80211vap          *rt_vap;          // ptr  8 B
 *   ieee80211_rte_lock_t         rt_lock;          // lock (modelled: int)
 *   struct callout               rt_discovery;     // callout (modelled: int)
 *   int                          rt_updtime;       // 4 B
 *   uint8_t                      rt_dest[6];
 *   uint8_t                      rt_mesh_gate[6];
 *   uint8_t                      rt_nexthop[6];
 *   uint32_t                     rt_metric;
 *   uint16_t                     rt_nhops;
 *   uint16_t                     rt_flags;
 *   uint32_t                     rt_lifetime;
 *   uint32_t                     rt_lastmseq;
 *   uint32_t                     rt_ext_seq;
 *   void                        *rt_priv;
 */
struct ieee80211_mesh_route {
    struct ieee80211_mesh_route *rt_next_le_next;  /* TAILQ_ENTRY next */
    struct ieee80211_mesh_route *rt_next_le_prev;  /* TAILQ_ENTRY prev (TAILQ uses ptr-to-ptr; we model as simple list) */
    void                        *rt_vap;
    int                          rt_lock_dummy;
    int                          rt_discovery_dummy;
    int                          rt_updtime;
    uint8_t                      rt_dest[IEEE80211_ADDR_LEN];
    uint8_t                      rt_mesh_gate[IEEE80211_ADDR_LEN];
    uint8_t                      rt_nexthop[IEEE80211_ADDR_LEN];
    uint32_t                     rt_metric;
    uint16_t                     rt_nhops;
    uint16_t                     rt_flags;
    uint32_t                     rt_lifetime;
    uint32_t                     rt_lastmseq;
    uint32_t                     rt_ext_seq;
    void                        *rt_priv;          /* -> appended HWMP priv  */
};

#define IEEE80211_MESHRT_FLAGS_VALID   0x02
#define ALIGN(x) (((x) + sizeof(void *) - 1) & ~(sizeof(void *) - 1))

/* ---- struct ieee80211_mesh_state (the mesh vap state) modelled with a
 *      simple singly-linked list standing in for TAILQ_HEAD ms_routes
 *      (ieee80211_mesh.h:525). ms_ppath->mpp_privlen = sizeof(HWMP priv). */
struct ieee80211_mesh_proto_path_min {
    size_t mpp_privlen;
};

struct ieee80211_mesh_state {
    struct ieee80211_mesh_route      *ms_routes_head;   /* TAILQ ms_routes   */
    int                               ms_routes_count;  /* list length       */
    struct ieee80211_mesh_proto_path_min *ms_ppath;     /* -> privlen provider*/
};

/* ---- The two macros referenced by the verbatim bodies ---- */
#define MESH_ROUTE_LIFETIME_MAX(a, b)   ((a) > (b) ? (a) : (b))

/* ---- stubs for the kernel-internal helpers the verbatim bodies call.
 *      `ticks` is a global counter; ticks_to_msecs is 1:1 here so the
 *      lifetime arithmetic is faithful in units of "msec". */
static uint64_t g_ticks = 0;
#define ticks g_ticks
static uint64_t ticks_to_msecs(uint64_t t) { return t; }

/* list helpers standing in for TAILQ (head/tail semantics irrelevant to the
 * growth/lifetime claims — only membership & count matter) */
static void TAILQ_INSERT_TAIL_mesh(struct ieee80211_mesh_state *ms,
                                   struct ieee80211_mesh_route *rt)
{
    rt->rt_next_le_next = NULL;
    if (ms->ms_routes_head == NULL) {
        ms->ms_routes_head = rt;
        rt->rt_next_le_prev = NULL;
    } else {
        struct ieee80211_mesh_route *p = ms->ms_routes_head;
        while (p->rt_next_le_next) p = p->rt_next_le_next;
        p->rt_next_le_next = rt;
        rt->rt_next_le_prev = p;
    }
    ms->ms_routes_count++;
}

static struct ieee80211_mesh_route *
mesh_rt_find_locked(struct ieee80211_mesh_state *ms,
                    const uint8_t dest[IEEE80211_ADDR_LEN])
{
    struct ieee80211_mesh_route *rt;
    for (rt = ms->ms_routes_head; rt != NULL; rt = rt->rt_next_le_next)
        if (memcmp(dest, rt->rt_dest, IEEE80211_ADDR_LEN) == 0)
            return rt;
    return NULL;
}

/* ============================================================================
 * VERBATIM vulnerable code from sys/netproto/802_11/wlan/ieee80211_mesh.c:194-228
 *   mesh_rt_add_locked() — the SINGLE choke point for all PREQ/PREP/RANN adders.
 *   (ms_ppath->mpp_privlen == sizeof(struct ieee80211_hwmp_route) under HWMP.)
 *   The ONLY change vs. the kernel: kmalloc() is modelled with malloc(3), and
 *   under -DFIXED we insert the cap check BEFORE the kmalloc.
 * ============================================================================ */
static struct ieee80211_mesh_route *
mesh_rt_add_locked(struct ieee80211_mesh_state *ms,
                   const uint8_t dest[IEEE80211_ADDR_LEN])
{
    struct ieee80211_mesh_route *rt;

    /* broadcast guard from ieee80211_mesh.c:201 (not relevant to the attack) */

#ifdef FIXED
    /* ===== BEGIN FIX (part 1): cap the per-vap route table size. ===== */
    if (ms->ms_routes_count >= IEEE80211_MESH_RT_MAX_FIX) {
        return NULL;     /* caller logs is_mesh_rtaddfailed++ and returns */
    }
    /* ===== END FIX (part 1) ===== */
#endif

#if defined(__DragonFly__)
    /* kernel: rt = kmalloc(ALIGN(sizeof(struct ieee80211_mesh_route)) +
     *                       ms->ms_ppath->mpp_privlen, M_80211_MESH_RT,
     *                       M_INTWAIT | M_ZERO);
     * modelled here as malloc(3) (size is identical; flags elided). */
    rt = malloc(ALIGN(sizeof(struct ieee80211_mesh_route)) +
        ms->ms_ppath->mpp_privlen);
#else
    rt = NULL;
#endif
    if (rt != NULL) {
        rt->rt_vap = NULL;
        memcpy(rt->rt_dest, dest, IEEE80211_ADDR_LEN);
        rt->rt_priv = (void *)ALIGN((uintptr_t)(rt + 1));
        /* lock/callout init elided (no-ops in userspace) */
        rt->rt_updtime = ticks;        /* create time */
        TAILQ_INSERT_TAIL_mesh(ms, rt);
    }
    return rt;
}

/* ============================================================================
 * VERBATIM vulnerable code from sys/netproto/802_11/wlan/ieee80211_mesh.c:266-303
 *   ieee80211_mesh_rt_update() — sets rt->rt_lifetime from the attacker value.
 *   Under -DFIXED we clamp new_lifetime to a sane max (kills the 49-day pin).
 * ============================================================================ */
static int
ieee80211_mesh_rt_update(struct ieee80211_mesh_route *rt, int new_lifetime)
{
    int timesince, now;
    uint32_t lifetime = 0;

    /* KASSERT(rt != NULL) elided */

    now = ticks;

    /* dont clobber a proxy entry gated by us (ieee80211_mesh.c:278-281) */
    if (rt->rt_flags & 0x04 /*PROXY*/ && rt->rt_nhops == 0) {
        return (int)rt->rt_lifetime;
    }

#ifdef FIXED
    /* ===== BEGIN FIX (part 2): clamp attacker-supplied lifetime. =====
     * NB: the parameter is `int` but the caller passes preq->preq_lifetime
     * (uint32). Treat as unsigned so 0xFFFFFFFF (-1 as int) is clamped to the
     * max rather than wedged to 0. */
    {
        uint32_t u = (uint32_t)new_lifetime;
        if (u > (uint32_t)IEEE80211_MESH_RT_LIFETIME_MAX_MS)
            u = (uint32_t)IEEE80211_MESH_RT_LIFETIME_MAX_MS;
        new_lifetime = (int)u;
    }
    /* ===== END FIX (part 2) ===== */
#endif

    timesince = (int)ticks_to_msecs(now - rt->rt_updtime);
    rt->rt_updtime = now;
    if (timesince >= (int)rt->rt_lifetime) {
        if (new_lifetime != 0) {
            rt->rt_lifetime = new_lifetime;
        } else {
            rt->rt_flags &= ~IEEE80211_MESHRT_FLAGS_VALID;
            rt->rt_lifetime = 0;
        }
    } else {
        /* update what is left of lifetime */
        rt->rt_lifetime = rt->rt_lifetime - timesince;
        rt->rt_lifetime  = MESH_ROUTE_LIFETIME_MAX(
            new_lifetime, rt->rt_lifetime);
    }
    lifetime = rt->rt_lifetime;

    return (int)lifetime;
}

/* ============================================================================
 * VERBATIM attacker-reachable block from hwmp_recv_preq()
 *   sys/netproto/802_11/wlan/ieee80211_hwmp.c:1055-1097
 *     rtorig = ieee80211_mesh_rt_find(vap, preq->preq_origaddr);
 *     if (rtorig == NULL) rtorig = ieee80211_mesh_rt_add(vap, preq->preq_origaddr);
 *     ...
 *     ieee80211_mesh_rt_update(rtorig, preq->preq_lifetime);
 *   preq->preq_lifetime = le32dec(iefrm_t) at ieee80211_hwmp.c:457 (uint32).
 * ============================================================================ */
static struct ieee80211_mesh_route *
hwmp_recv_preq_origadd(struct ieee80211_mesh_state *ms,
                       const uint8_t origaddr[IEEE80211_ADDR_LEN],
                       uint32_t preq_lifetime)
{
    struct ieee80211_mesh_route *rtorig;

    rtorig = mesh_rt_find_locked(ms, origaddr);        /* ieee80211_hwmp.c:1055 */
    if (rtorig == NULL) {                              /* ieee80211_hwmp.c:1056 */
        rtorig = mesh_rt_add_locked(ms, origaddr);     /* ieee80211_hwmp.c:1057 */
        if (rtorig == NULL) {                          /* ieee80211_hwmp.c:1058 */
            /* caller: vap->iv_stats.is_mesh_rtaddfailed++; return; */
            return NULL;
        }
    }
    /* ieee80211_hwmp.c:1090-1097 (HWMP_SEQ_GT branch taken for fresh originator):
     *   hrorig->hr_seq = preq->preq_origseq;
     *   ...
     *   ieee80211_mesh_rt_update(rtorig, preq->preq_lifetime);   <-- attacker value */
    ieee80211_mesh_rt_update(rtorig, (int)preq_lifetime);
    rtorig->rt_flags = IEEE80211_MESHRT_FLAGS_VALID;    /* ieee80211_hwmp.c:1102 */
    return rtorig;
}

/* ---- build a distinct spoofed originator MAC for PREQ #i ---- */
static void make_origaddr(uint8_t out[IEEE80211_ADDR_LEN], uint64_t i)
{
    /* unicast, locally-administered OUI so they all differ. Pack i
     * little-endian into bytes 1..5 (2^40 distinct values, far beyond any
     * plausible flood). */
    out[0] = 0x02;
    out[1] = (uint8_t)((i >> 32) & 0xff);
    out[2] = (uint8_t)((i >> 24) & 0xff);
    out[3] = (uint8_t)((i >> 16) & 0xff);
    out[4] = (uint8_t)((i >>  8) & 0xff);
    out[5] = (uint8_t)((i >>  0) & 0xff);
}

int main(int argc, char **argv)
{
    uint64_t n_attack = 8000;             /* distinct spoofed PREQ originators
                                           * (> IEEE80211_MESH_RT_MAX so the
                                           * FIXED build exercises the cap) */
    uint32_t attacker_lifetime = 0xFFFFFFFFu;  /* max uint32 msec (~49 days) */
    if (argc > 1) n_attack = strtoull(argv[1], NULL, 0);
    if (argc > 2) attacker_lifetime = (uint32_t)strtoul(argv[2], NULL, 0);

    struct ieee80211_mesh_proto_path_min ppath = {
        .mpp_privlen = sizeof(struct ieee80211_hwmp_route),
    };
    struct ieee80211_mesh_state ms = { 0 };
    ms.ms_ppath = &ppath;

    printf("=== DF-0350 harness: unbounded mesh route-table growth + "
           "attacker-controlled lifetime ===\n");
    printf("build mode                     : %s\n",
#ifdef FIXED
           "FIXED (cap+clamp applied)"
#else
           "BUG PRESENT (no cap, no clamp)"
#endif
           );
    printf("preq->preq_lifetime (attacker) : 0x%08X msec (%.1f days)\n",
           attacker_lifetime, attacker_lifetime / 1000.0 / 86400.0);
    printf("attack: distinct originators   : %llu PREQ frames\n",
           (unsigned long long)n_attack);
    printf("sizeof(struct ieee80211_mesh_route) + HWMP priv = %zu + %zu = %zu "
           "bytes/entry\n",
           ALIGN(sizeof(struct ieee80211_mesh_route)),
           ppath.mpp_privlen,
           ALIGN(sizeof(struct ieee80211_mesh_route)) + ppath.mpp_privlen);
    printf("\n");

    /* Simulate the attacker flood: one PREQ per distinct spoofed originator,
     * each carrying the maximal lifetime. This is EXACTLY the runtime data
     * flow: hwmp_recv_action_meshpath -> hwmp_recv_preq -> (find||add) ->
     * ieee80211_mesh_rt_update(rtorig, preq->preq_lifetime). */
    uint64_t added = 0, rejected = 0;
    uint32_t first_rt_lifetime_seen = 0;
    int      first_seen = 0;
    for (uint64_t i = 0; i < n_attack; i++) {
        uint8_t orig[IEEE80211_ADDR_LEN];
        make_origaddr(orig, i);
        struct ieee80211_mesh_route *rt =
            hwmp_recv_preq_origadd(&ms, orig, attacker_lifetime);
        if (rt == NULL) {
            rejected++;
        } else {
            added++;
            if (!first_seen) {
                first_rt_lifetime_seen = rt->rt_lifetime;
                first_seen = 1;
            }
        }
    }

    printf("---- Result of the flood ----\n");
    printf("routes successfully added      : %llu\n", (unsigned long long)added);
    printf("routes rejected (NULL)         : %llu\n", (unsigned long long)rejected);
    printf("final ms_routes table size     : %d entries\n", ms.ms_routes_count);
    uint64_t bytes = (uint64_t)ms.ms_routes_count *
        (ALIGN(sizeof(struct ieee80211_mesh_route)) + ppath.mpp_privlen);
    printf("kernel memory pinned by table  : %llu bytes (%.1f MiB)  [per-vap]\n",
           (unsigned long long)bytes, bytes / 1024.0 / 1024.0);
    printf("\n");

    printf("---- Lifetime claim (attacker-controlled dwell) ----\n");
    printf("first added route rt_lifetime  : 0x%08X msec (%.1f days)\n",
           first_rt_lifetime_seen, first_rt_lifetime_seen / 1000.0 / 86400.0);
    printf("  (rt_lifetime == preq->preq_lifetime, UNVALIDATED: claim #2 %s)\n",
           first_rt_lifetime_seen == attacker_lifetime ? "CONFIRMED" : "refuted");
    printf("\n");

    /* extrapolate the DoS ceiling: the MAC space is 2^46 (with the 0x02 prefix),
     * so the attacker never runs out of distinct originators. At ~150 B/entry
     * the table can pin the ENTIRE kmem_map before ms_routes_count ever stops. */
    printf("---- DoS ceiling extrapolation ----\n");
    uint64_t kmem_map_bytes = 256ULL * 1024 * 1024 * 1024;  /* ~256 GB worst case */
    uint64_t entries_to_exhaust = kmem_map_bytes /
        (ALIGN(sizeof(struct ieee80211_mesh_route)) + ppath.mpp_privlen);
    printf("distinct MACs available (0x02xx) : ~2^46 = %llu\n",
           1ULL << 46);
    printf("entries to exhaust a 256 GB kmem : ~%llu (%llu MiB)\n",
           (unsigned long long)entries_to_exhaust,
           (unsigned long long)entries_to_exhaust *
               (ALIGN(sizeof(struct ieee80211_mesh_route)) + ppath.mpp_privlen)
               / (1024*1024));
    printf("=> attacker can pin memory until kmem exhaustion / kernel panic.\n");

    printf("\nVERDICT: ");
#ifdef FIXED
    if (ms.ms_routes_count > (int)(long)IEEE80211_MESH_RT_MAX_FIX) {
        printf("CAP FAILED — table exceeded the limit (BUG)\n");
        return 1;
    }
    if (first_rt_lifetime_seen > IEEE80211_MESH_RT_LIFETIME_MAX_MS) {
        printf("CLAMP FAILED — lifetime not bounded (BUG)\n");
        return 1;
    }
    printf("FIXED — table capped at %d entries (rejected %llu over-cap PREQs); "
           "lifetime clamped to %d msec (%.0f s, was %.1f days)\n",
           ms.ms_routes_count, (unsigned long long)rejected,
           IEEE80211_MESH_RT_LIFETIME_MAX_MS,
           IEEE80211_MESH_RT_LIFETIME_MAX_MS / 1000.0,
           attacker_lifetime / 1000.0 / 86400.0);
    return 0;
#else
    if (rejected == 0 && added == (uint64_t)n_attack) {
        printf("UNBOUNDED GROWTH CONFIRMED — all %llu PREQs added a new entry; "
               "no cap, no NULL, lifetime = attacker uint32.\n",
               (unsigned long long)added);
        return 0;
    }
    printf("growth was bounded unexpectedly (rejected=%llu) — re-examine.\n",
           (unsigned long long)rejected);
    return 1;
#endif
}
