DF-1131 / harness.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 | /* * 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; } |