DF-0640 / 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 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 | /* * DF-0640 harness: ieee80211_crypto_decap minlen check ignores hdrlen * * Source: sys/netproto/802_11/wlan/ieee80211_crypto.c:584-634 * * The bug: * Line 587-590: IEEE80211_WEP_MINLEN = sizeof(struct ieee80211_frame)=24 + * IEEE80211_WEP_HDRLEN=4 + IEEE80211_WEP_CRCLEN=4 = 32 * Line 598: if (m->m_pkthdr.len < IEEE80211_WEP_MINLEN) return NULL; * * But hdrlen (from ieee80211_hdrspace) varies: * - QoS frame: hdrlen = 26 or 28 (with DATAPAD) * - 4-addr frame: hdrlen = 30 or 32 * * The check uses HARDCODED 24, not the caller-supplied hdrlen. * * IMPACT 1 (OOB read / panic): * Line 613: m_copydata(m, hdrlen+3, 1, &keyid) * For 4-addr hdrlen=30/32 with m_pkthdr.len in [32, hdrlen+3]: * m_copydata reads past chain end -> KASSERT panic or NULL deref. * * IMPACT 2 (double-free): * Line 624-625: m_pullup(m, hdrlen+cip->ic_header) for CCMP/TKIP (ic_header=8) * For QoS frame with m_pkthdr.len in [hdrlen+4, hdrlen+7]: * m_copydata succeeds (reads keyid within bounds) but m_pullup fails * (chain too short for hdrlen+8). m_pullup FREES the mbuf and returns NULL. * Function returns NULL. Caller still holds original freed m pointer -> * caller does m_freem(m) -> DOUBLE-FREE. * * Caller (ieee80211_hostap.c:873): key = ieee80211_crypto_decap(ni, m, hdrspace); * if (key == NULL) goto out; * ... * out: if (m != NULL) m_freem(m); // DOUBLE-FREE! * * This requires WiFi hardware (or at least a wlan vap). On QEMU without WiFi, * this harness demonstrates the minlen check logic and double-free path. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdint.h> #define IEEE80211_WEP_IVLEN 3 #define IEEE80211_WEP_KIDLEN 1 #define IEEE80211_WEP_HDRLEN (IEEE80211_WEP_IVLEN + IEEE80211_WEP_KIDLEN) /* 4 */ #define IEEE80211_WEP_CRCLEN 4 /* sizeof(struct ieee80211_frame) = 24 on this arch */ #define IEEE80211_FRAME_LEN 24 /* The BUGGY macro (hardcodes 24 instead of using hdrlen) */ #define IEEE80211_WEP_MINLEN_BUGGY (IEEE80211_FRAME_LEN + IEEE80211_WEP_HDRLEN + IEEE80211_WEP_CRCLEN) /* 32 */ /* The CORRECT macro (uses hdrlen) */ #define IEEE80211_WEP_MINLEN_FIXED(hdrlen) ((hdrlen) + IEEE80211_WEP_HDRLEN + IEEE80211_WEP_CRCLEN) /* Simulated mbuf */ struct mbuf { int m_len; /* data length */ int m_pkthdr_len; /* total packet length */ uint8_t *m_data; /* data pointer */ int freed; /* track if mbuf has been freed */ }; /* Replicates m_copydata (simplified) */ static int m_copydata(struct mbuf *m, int off, int len, uint8_t *buf) { if (off + len > m->m_pkthdr_len) { printf(" m_copydata: OOB! off=%d+len=%d > pkthdr.len=%d\n", off, len, m->m_pkthdr_len); return -1; /* would panic in kernel */ } /* In kernel with INVARIANTS: KASSERT panic */ return 0; } /* Replicates m_pullup (simplified) */ static struct mbuf *m_pullup(struct mbuf *m, int len) { if (m->m_pkthdr_len < len) { /* m_pullup fails: FREES the mbuf chain and returns NULL */ printf(" m_pullup: fails (pkthdr.len=%d < requested=%d) -> FREES mbuf, returns NULL\n", m->m_pkthdr_len, len); m->freed = 1; return NULL; } return m; } /* Replicates ieee80211_crypto_decap (line 584-634) */ static int crypto_decap(struct mbuf *m, int hdrlen) { uint8_t keyid; int cip_header = 8; /* CCMP/TKIP */ printf(" crypto_decap(hdrlen=%d, pkthdr.len=%d):\n", hdrlen, m->m_pkthdr_len); /* BUGGY minlen check (uses hardcoded 24, not hdrlen) */ if (m->m_pkthdr_len < IEEE80211_WEP_MINLEN_BUGGY) { printf(" minlen check: pkthdr.len=%d < MINLEN=%d -> rejected (correct)\n", m->m_pkthdr_len, IEEE80211_WEP_MINLEN_BUGGY); return -1; /* correctly rejected */ } printf(" minlen check: pkthdr.len=%d >= MINLEN=%d -> PASSED\n", m->m_pkthdr_len, IEEE80211_WEP_MINLEN_BUGGY); /* CORRECT minlen check would use hdrlen */ int correct_min = IEEE80211_WEP_MINLEN_FIXED(hdrlen); if (m->m_pkthdr_len < correct_min) { printf(" [BUG] CORRECT minlen would be %d (hdrlen=%d) -> SHOULD reject but DIDN'T\n", correct_min, hdrlen); } /* Line 613: m_copydata(m, hdrlen + IEEE80211_WEP_IVLEN, ...) */ printf(" m_copydata at offset hdrlen+3 = %d\n", hdrlen + 3); if (m_copydata(m, hdrlen + IEEE80211_WEP_IVLEN, 1, &keyid) < 0) { printf(" -> OOB READ / PANIC!\n"); return -2; /* OOB panic */ } /* Line 624-625: m_pullup(m, hdrlen + cip->ic_header) */ printf(" m_pullup for hdrlen+cip_header = %d + %d = %d\n", hdrlen, cip_header, hdrlen + cip_header); struct mbuf *m2 = m_pullup(m, hdrlen + cip_header); if (m2 == NULL) { printf(" -> m_pullup failed, mbuf FREED, function returns NULL\n"); printf(" -> CALLER still holds original m -> m_freem(m) -> DOUBLE-FREE!\n"); return -3; /* double-free */ } return 0; } int main(void) { printf("=== DF-0640: ieee80211_crypto_decap minlen ignores hdrlen ===\n\n"); printf("NOTE: Requires WiFi hardware — bug confirmed by source trace.\n"); printf(" Harness demonstrates the minlen bypass and double-free path.\n\n"); printf("IEEE80211_WEP_MINLEN (buggy) = %d (hardcodes sizeof(ieee80211_frame)=%d)\n\n", IEEE80211_WEP_MINLEN_BUGGY, IEEE80211_FRAME_LEN); /* Test case 1: QoS frame, hdrlen=26, pkthdr.len=34 */ /* Buggy check: 34 >= 32 -> passes. Correct: 34 >= 34 -> barely passes */ printf("[Test 1] QoS frame (hdrlen=26, len=34):\n"); struct mbuf m1 = {.m_pkthdr_len = 34, .m_len = 34, .freed = 0}; crypto_decap(&m1, 26); printf("\n"); /* Test case 2: 4-addr QoS frame, hdrlen=32, pkthdr.len=33 */ /* Buggy check: 33 >= 32 -> passes! Correct: 33 >= 40 -> should reject */ /* m_copydata at offset 35 > 33 -> OOB read / panic */ printf("[Test 2] 4-addr QoS frame (hdrlen=32, len=33):\n"); printf(" -> Buggy check passes (33 >= 32), correct would reject (33 < 40)\n"); struct mbuf m2 = {.m_pkthdr_len = 33, .m_len = 33, .freed = 0}; int result = crypto_decap(&m2, 32); if (result == -2) printf(" -> IMPACT 1: OOB read -> kernel panic\n\n"); /* Test case 3: QoS frame, hdrlen=26, pkthdr.len=30 */ /* Buggy: 30 >= 32 -> FAILS. But what about len=33? */ /* Buggy: 33 >= 32 passes. Correct: 33 >= 34 fails. */ /* m_copydata at 29 succeeds (29+1=30 <= 33). */ /* m_pullup(26+8=34): 33 < 34 -> fails -> FREES mbuf -> double-free */ printf("[Test 3] QoS frame (hdrlen=26, len=33):\n"); printf(" -> Buggy check passes (33 >= 32), correct would reject (33 < 34)\n"); struct mbuf m3 = {.m_pkthdr_len = 33, .m_len = 33, .freed = 0}; result = crypto_decap(&m3, 26); if (result == -3) printf(" -> IMPACT 2: DOUBLE-FREE (caller will m_freem the freed mbuf)\n\n"); printf("=== SUMMARY ===\n"); printf("hdrlen from ieee80211_hdrspace varies (QoS=26, 4-addr=30/32).\n"); printf("Buggy MINLEN uses hardcoded 24, admitting frames too short for ops.\n"); printf("Impact 1: m_copydata OOB -> panic (INVARIANTS) or NULL deref\n"); printf("Impact 2: m_pullup fail -> mbuf freed -> caller double-free\n"); printf(" (mbuf double-free is a well-known code-exec primitive)\n"); printf("Fix: use hdrlen in the minlen check.\n"); return 0; } |