/*
 * DF-1131 harness: bwn_dma_rxeof dr_rx_bufsize / cluster-size mismatch OOB read
 *
 * Replicates the EXACT kernel length-check of bwn_dma_rxeof() (if_bwn.c:5441):
 *
 *   if_bwn.c:2778   dr->dr_rx_bufsize  = BWN_DMA0_RX_BUFFERSIZE;   // = IEEE80211_MAX_LEN = 2312
 *   if_bwn.c:5674   m = m_getcl(...)                  // allocates an MCLBYTES=2048 cluster
 *   if_bwn.c:5688   m->m_len = m->m_pkthdr.len = MCLBYTES;          // 2048
 *   if_bwn.c:5470   len = le16toh(rxhdr->frame_len);  // device-reported, attacker-influenced
 *   if_bwn.c:5486   if (len > dr->dr_rx_bufsize) drop; // checks against 2312, NOT 2048
 *   if_bwn.c:5512   m->m_len = m->m_pkthdr.len = len + dr->dr_frameoffset;  // frameoffset=30
 *
 * For frame_len in (2018, 2312] the check PASSES (len <= 2312) but
 *   m_len = len + 30  in (2048, 2342]  >  MCLBYTES(2048) cluster.
 * net80211 then processes an mbuf that claims len+30 bytes but whose backing
 * cluster is only 2048 bytes -> heap OOB read of up to (2312+30-2048)=294 bytes
 * from whatever kmalloc bucket sits adjacent to the cluster; the over-read
 * bytes appear in the delivered 802.11 frame payload (info leak) or hit an
 * unmapped page (panic).
 *
 * Requires a Broadcom BCM43xx (bwn) WiFi adapter + a malicious radio-range
 * frame. Not present on the audit QEMU guest (virtio net only).
 */

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

#define IEEE80211_CRC_LEN     4
#define IEEE80211_WEP_IVLEN   3
#define IEEE80211_WEP_KIDLEN  1
#define IEEE80211_WEP_CRCLEN  4
#define IEEE80211_MAX_LEN     (2300 + IEEE80211_CRC_LEN + \
                               (IEEE80211_WEP_IVLEN + IEEE80211_WEP_KIDLEN + IEEE80211_WEP_CRCLEN))
#define MCLBYTES              2048
#define BWN_DMA0_RX_BUFFERSIZE  IEEE80211_MAX_LEN
#define BWN_DMA0_RX_FRAMEOFFSET 30

static size_t imin(size_t a, size_t b){ return a<b?a:b; }

int main(void)
{
    printf("IEEE80211_MAX_LEN (dr_rx_bufsize) = %d\n", IEEE80211_MAX_LEN);
    printf("MCLBYTES (actual cluster)         = %d\n", MCLBYTES);
    printf("frameoffset                       = %d\n", BWN_DMA0_RX_FRAMEOFFSET);
    printf("usable frame_len for a 2048 cluster = MCLBYTES - frameoffset = %d\n\n",
           MCLBYTES - BWN_DMA0_RX_FRAMEOFFSET);

    int oob_cases = 0;
    /* device-reported frame_len values a malicious frame / firmware bug could
     * produce; each one in (2018, 2312] passes the kernel check but overruns. */
    uint32_t lens[] = { 1500, 2018, 2019, 2048, 2100, 2312, 2313 };
    for (size_t c=0;c<sizeof(lens)/sizeof(lens[0]);c++){
        uint32_t len = lens[c];
        int passes_check = (len <= BWN_DMA0_RX_BUFFERSIZE);          /* if_bwn.c:5486 */
        size_t  m_len   = (size_t)len + BWN_DMA0_RX_FRAMEOFFSET;     /* if_bwn.c:5512 */
        long    overrun = (long)m_len - MCLBYTES;                     /* bytes past cluster */
        int oob = passes_check && (overrun > 0);
        printf("frame_len=%-5u  check(len<=2312)=%s  m_len=%-4zu  cluster_overrun=%-4ld %s\n",
               len, passes_check?"PASS(drop->keep)":"DROP",
               m_len, overrun > 0 ? overrun : 0,
               oob ? ">>> HEAP OOB READ past 2048-byte cluster" : "");
        if (oob) oob_cases++;
    }

    /* Demonstrate the over-read concretely for frame_len=2100: model the
     * cluster as 2048 bytes, set m_len = 2130, and show that "processing" the
     * mbuf (reading m_len bytes from mtod(m)) reads 82 bytes of an adjacent
     * allocation. */
    uint32_t len = 2100;
    size_t m_len = len + BWN_DMA0_RX_FRAMEOFFSET;   /* 2130 */
    uint8_t *cluster = calloc(1, MCLBYTES);
    uint8_t *adjacent = malloc(512);
    memset(adjacent, 0xBB, 512);                    /* model adjacent heap as a marker */
    /* place cluster immediately followed by adjacent (contiguous malloc'd) */
    /* emulate a consumer reading m_len bytes from cluster start */
    size_t inb = imin(m_len, MCLBYTES);
    size_t oob_n = (m_len > MCLBYTES) ? m_len - MCLBYTES : 0;
    printf("\nconcrete model (frame_len=%u): consumer reads %zu in-bounds + %zu OOB bytes\n",
           len, inb, oob_n);
    if (oob_n) {
        printf("  the %zu OOB bytes are adjacent heap (here modeled 0xBB); ", oob_n);
        printf("in-kernel these are leaked into the delivered frame payload\n");
    }
    free(cluster); free(adjacent);

    printf("\nresult: %d/%zu frame_len values pass the kernel check but overrun the 2048-byte cluster\n",
           oob_cases, sizeof(lens)/sizeof(lens[0]));
    if (oob_cases > 0) {
        printf("DF-1131: CONFIRMED heap OOB read primitive (up to %d bytes past cluster)\n",
               IEEE80211_MAX_LEN + BWN_DMA0_RX_FRAMEOFFSET - MCLBYTES);
    } else {
        printf("DF-1131: NOT reproduced\n");
    }

    /* ---- WITH FIX: add len+frameoffset > MCLBYTES guard -> 0 OOB ---- */
    printf("\n--- WITH FIX (drop if len+frameoffset > MCLBYTES) ---\n");
    int fix_oob = 0;
    for (size_t c=0;c<sizeof(lens)/sizeof(lens[0]);c++){
        uint32_t len = lens[c];
        int keep = (len <= BWN_DMA0_RX_BUFFERSIZE) &&
                   ((size_t)len + BWN_DMA0_RX_FRAMEOFFSET <= MCLBYTES);
        size_t overrun = keep ? (((size_t)len + BWN_DMA0_RX_FRAMEOFFSET > MCLBYTES) ? (size_t)len + BWN_DMA0_RX_FRAMEOFFSET - MCLBYTES : 0) : 0;
        if (keep && overrun > 0) fix_oob++;
        printf("frame_len=%-5u  keep=%s  cluster_overrun=%zu %s\n",
               len, keep?"keep":"DROP", overrun,
               (keep && overrun) ? ">>> STILL OOB" : (keep?"(clean)":"(dropped, safe)"));
    }
    printf("FIX result: %d OOB cases remain (expect 0)\n", fix_oob);
    printf("DF-1131 FIX: %s\n", fix_oob == 0 ? "VALIDATED - cluster-bound guard drops all overrun frames" : "INCOMPLETE");
    return (oob_cases > 0) ? 0 : 1;
}
