/*
 * DF-0731 — rssadapt_tx_complete unconditional NULL-deref proof (code harness)
 * ---------------------------------------------------------------------------
 *
 * CLAIM (findings/DF-0731, sys/netproto/802_11/wlan/ieee80211_rssadapt.c):
 *
 *   322: static void
 *   323: rssadapt_tx_complete(const struct ieee80211vap *vap,
 *   324:     const struct ieee80211_node *ni, int success, void *arg1, void *arg2)
 *   325: {
 *   326:     struct ieee80211_rssadapt_node *ra = ni->ni_rctls;
 *   327:     int pktlen = *(int *)arg1, rssi = *(int *)arg2;   <-- UNCONDITIONAL DEREF
 *   ...
 *   338: }
 *
 * rssadapt_tx_complete is the .ir_tx_complete entry of the "rssadapt"
 * ieee80211_ratectl (ieee80211_rssadapt.c:99-111) and is reached via the
 * net80211 dispatch inline
 *
 *   sys/netproto/802_11/ieee80211_ratectl.h:98-103
 *       static __inline void
 *       ieee80211_ratectl_tx_complete(const struct ieee80211vap *vap,
 *           const struct ieee80211_node *ni, int status, void *arg1, void *arg2)
 *       { vap->iv_rate->ir_tx_complete(vap, ni, status, arg1, arg2); }
 *
 * EVERY in-tree WiFi driver that reports a TX-completion to the ratectl layer
 * passes NULL (or the null-pointer constant 0) as arg2 (the rssi pointer):
 *
 *     sys/bus/u4b/wlan/if_urtwn.c:1041-1046       ... &ntries, NULL);
 *     sys/dev/netif/iwm/if_iwm.c:3561-3568        ... &failack, NULL);
 *     sys/dev/netif/ral/rt2661.c:928-942          ... &retrycnt, NULL);
 *     sys/dev/netif/ral/rt2860.c:1157-1161        ... &retrycnt, NULL);
 *     sys/dev/netif/ral/rt2560.c:982-1008         ... &retrycnt, NULL);
 *     sys/dev/netif/wpi/if_wpi.c:2127-2131        ... &ackfailcnt, NULL);
 *     sys/dev/netif/iwn/if_iwn.c:3330-3808        ... &ackfailcnt, NULL);
 *     sys/dev/netif/bwn/bwn/if_bwn.c:6012-6078    ... &retrycnt, 0);   // 0==NULL
 *
 * Therefore, as soon as the rssadapt ratectl algorithm is selected
 * (ieee80211_ratectl_set(vap, IEEE80211_RATECTL_RSSADAPT)) and a frame is
 * transmitted, the very first TX completion dereferences a NULL kernel
 * pointer -> fatal trap 12 (page fault) / kernel panic -> instant DoS.
 *
 * REACHABILITY: LATENT on the default X86_64_GENERIC guest:
 *   - wlan_rssadapt is `optional wlan_rssadapt` (sys/conf/files:1655), NOT in
 *     the default kernel;
 *   - default ratectl is AMRR (ieee80211_ratectl.c:121-122);
 *   - no WiFi radio on the audit guest (ifconfig -l => vtnet0 lo0).
 * So it cannot be triggered live here; this harness is the deterministic proof.
 *
 * The harness reproduces the EXACT control flow of rssadapt_tx_complete and
 * instruments the deref at line 327. Instead of actually crashing the process
 * (a NULL page-fault), it records "DEREF_OF_NULL" the moment the buggy path
 * reaches `*(int *)arg2` with arg2==NULL, which is precisely the in-kernel
 * fault condition. -DFIX_NULL_CHECK adds the proposed early-return guard so
 * the same harness proves the fix: the deref is never reached.
 *
 * NULL-deref at a fixed kernel address is a pure DoS — no corruption primitive,
 * so there is NO escalation chain (Phase 6 N/A for this finding).
 */

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

/* ---- Mirror the relevant fields used by rssadapt_tx_complete ------------ */
#define IEEE80211_RATE_SIZE        8
#define IEEE80211_RSSADAPT_BKTS    3
struct ieee80211_rateset { uint8_t rs_nrates; uint8_t rs_rates[15]; };
struct ieee80211_rssadapt_node {
    void *ra_rs;
    struct ieee80211_rateset ra_rates;
    int   ra_rix;
    int   ra_ticks;
    int   ra_last_raise;
    int   ra_raise_interval;
    uint32_t ra_nfail;
    uint32_t ra_nok;
    uint32_t ra_pktrate;
    uint16_t ra_rate_thresh[IEEE80211_RSSADAPT_BKTS][IEEE80211_RATE_SIZE];
};

/* Per-call instrumentation of the line-327 deref. */
static int g_deref_of_null = 0;     /* set if the buggy path derefs a NULL arg */
static int g_lower_rate_called = 0;
static int g_raise_rate_called = 0;

/* Instrumented stand-in for `*(int *)arg` at ieee80211_rssadapt.c:327.
 * Faithfully models the kernel load: if the pointer is NULL this is exactly
 * the in-kernel page-fault condition; we record it rather than SIGSEGV. */
static int
deref_int_arg(void *arg, const char *which)
{
    if (arg == NULL) {
        g_deref_of_null = 1;
        printf("  [deref] *(int *)%s with %s==NULL  ->  in-kernel: fatal trap 12 / panic\n",
               which, which);
        return 0;           /* keep the harness running to report the result */
    }
    return *(int *)arg;
}

static void
rssadapt_lower_rate(struct ieee80211_rssadapt_node *ra, int pktlen, int rssi)
{
    (void)ra; (void)pktlen; (void)rssi;
    g_lower_rate_called++;
}
static void
rssadapt_raise_rate(struct ieee80211_rssadapt_node *ra, int pktlen, int rssi)
{
    (void)ra; (void)pktlen; (void)rssi;
    g_raise_rate_called++;
}

/*
 * Reproduce rssadapt_tx_complete (ieee80211_rssadapt.c:322-338). The two
 * deref_int_arg() calls correspond to line 327's
 *     int pktlen = *(int *)arg1, rssi = *(int *)arg2;
 * Under -DFIX_NULL_CHECK we add the proposed guard (matches fix.diff).
 */
static void
rssadapt_tx_complete(struct ieee80211_rssadapt_node *ra, int success,
                     void *arg1, void *arg2)
{
#ifdef FIX_NULL_CHECK
    if (arg1 == NULL || arg2 == NULL)        /* the FIX */
        return;
#endif
    int pktlen = deref_int_arg(arg1, "arg1(pktlen)");   /* == *(int *)arg1 */
    int rssi   = deref_int_arg(arg2, "arg2(rssi)");     /* == *(int *)arg2 */

    if (success) {
        ra->ra_nok++;
        if ((ra->ra_rix + 1) < ra->ra_rates.rs_nrates)
            rssadapt_raise_rate(ra, pktlen, rssi);
    } else {
        ra->ra_nfail++;
        rssadapt_lower_rate(ra, pktlen, rssi);
    }
}

int
main(void)
{
    struct ieee80211_rssadapt_node ra;
    memset(&ra, 0, sizeof(ra));
    ra.ra_rates.rs_nrates = 4;
    ra.ra_rix = 0;
    int pktlen = 1500;

    printf("DF-0731 rssadapt_tx_complete NULL-arg2 deref harness\n");
    printf("Build mode: %s\n",
#ifdef FIX_NULL_CHECK
           "FIXED (NULL-check guard present, models fix.diff)"
#else
           "BUGGY (matches master ieee80211_rssadapt.c:322-338)"
#endif
          );
    printf("Call: rssadapt_tx_complete(ra, success=0, arg1=&pktlen, arg2=NULL)\n");
    printf("       (every in-tree driver: urtwn/ral/wpi/iwn/iwm pass NULL, bwn passes 0)\n\n");

    /* Most-common driver callsite: arg2 == NULL. */
    rssadapt_tx_complete(&ra, /*success*/ 0, &pktlen, /*arg2*/ NULL);

    printf("\n=== VERDICT ===\n");
#ifdef FIX_NULL_CHECK
    if (!g_deref_of_null) {
        printf("NO DEREF: NULL-arg guard returned early; line-327 load never reached.\n");
        printf("rssi callbacks reached: lower=%d raise=%d (must be 0/0).\n",
               g_lower_rate_called, g_raise_rate_called);
        printf("BUG FIXED: rssadapt_tx_complete no longer derefs NULL arg2/arg1.\n");
        return 0;                   /* fixed => exit 0 */
    }
    printf("UNEXPECTED: fix did not prevent the deref.\n");
    return 2;
#else
    if (g_deref_of_null) {
        printf("NULL-DEREF: line 327 reached *(int *)arg2 with arg2==NULL.\n");
        printf("In-kernel this is: fatal trap 12 (page fault on NULL) / panic -> DoS.\n");
        printf("BUG PRESENT: rssadapt_tx_complete unconditionally derefs arg2.\n");
        return 1;                   /* bug demonstrated => non-zero */
    }
    printf("UNEXPECTED: buggy build did not reach the NULL deref.\n");
    return 2;
#endif
}
