DF-0594 / tkip_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 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 | /* * DF-0594 โ code-level proof of the TKIP RX length-underflow defect. * * This harness is a FAITHFUL userspace reproduction of the vulnerable path in * sys/netproto/802_11/wlan_tkip/ieee80211_crypto_tkip.c. It embeds the VERBATIM * body of wep_decrypt() (lines 662-723) and replicates the EXACT signed/unsigned * arithmetic from tkip_decrypt() line 994. The KASSERT semantics are taken from * sys/sys/systm.h:94-122 (X86_64_GENERIC builds INVARIANTS โ * sys/config/X86_64_GENERIC:56 โ so KASSERT = panic; without INVARIANTS * KASSERT is a no-op). * * Why a harness, not a runtime trigger: this KVM guest has no wifi radio, no * wlan(4) interface, and no wlan kld loaded (vtnet0/lo0 only). The bug lives on * the 802.11 RX software-crypto path (ieee80211_input -> ieee80211_crypto_decap * -> tkip_decap -> tkip_decrypt -> wep_decrypt) which is unreachable here. The * harness proves the defect deterministically by exercising the exact C the * kernel executes, with the exact types, on the exact too-short input the * finding describes. * * Two builds: * cc -O2 -Wall -o tkip_harness tkip_harness.c # INVARIANTS (default kernel) * cc -O2 -Wall -DNO_INVARIANTS -o tkip_harness_noinv tkip_harness.c # production * * The INVARIANTS build demonstrates the KASSERT panic (the deterministic DoS on * the default X86_64_GENERIC kernel). The NO_INVARIANTS build places the mbuf * data at the very end of a page with a PROT_NONE guard page immediately after, * so the OOB read at wep_decrypt:717 (*pos++) page-faults โ exactly the * "cluster whose backing page ends inside the read window" scenario the finding * describes for production kernels. */ #include <stdio.h> #include <stdlib.h> #include <stdarg.h> #include <string.h> #include <stdint.h> #include <signal.h> #include <setjmp.h> #include <unistd.h> #include <sys/mman.h> /* ---- Faithful type stubs matching the kernel headers --------------------- */ typedef uint8_t u8; typedef uint16_t u16; typedef uint32_t u32; typedef uint32_t __u32; typedef unsigned int u_int; /* ic_header/ic_trailer/ic_miclen are u_int * (sys/netproto/802_11/ieee80211_crypto.h:177-179) */ /* Constants from sys/netproto/802_11/ieee80211.h:1265-1283 */ #define IEEE80211_WEP_IVLEN 3 #define IEEE80211_WEP_KIDLEN 1 #define IEEE80211_WEP_EXTIVLEN 4 #define IEEE80211_WEP_CRCLEN 4 #define IEEE80211_WEP_MICLEN 8 #define IEEE80211_WEP_EXTIV 0x20 /* The tkip cipher descriptor values (ic_header/ic_trailer/ic_miclen), verbatim * from sys/netproto/802_11/wlan_tkip/ieee80211_crypto_tkip.c:64-79. */ static const struct { u_int ic_header; /* IVLEN + KIDLEN + EXTIVLEN = 8 */ u_int ic_trailer; /* CRCLEN = 4 */ u_int ic_miclen; /* MICLEN = 8 */ } tkip = { IEEE80211_WEP_IVLEN + IEEE80211_WEP_KIDLEN + IEEE80211_WEP_EXTIVLEN, IEEE80211_WEP_CRCLEN, IEEE80211_WEP_MICLEN, }; /* ---- Minimal mbuf stub. The signedness is the heart of the bug: * m_pkthdr.len is `int` (sys/sys/mbuf.h:159), m_len is `int` (:86). * ic_header/ic_trailer/ic_miclen are `u_int` (ieee80211_crypto.h:177-179). * ------------------------------------------------------------------------ */ struct pkthdr { int len; }; struct mbuf { struct mbuf *m_next; int m_len; /* int, not size_t */ uint8_t *m_data; struct pkthdr m_pkthdr; /* m_pkthdr.len is int */ }; #define mtod(m, t) ((t)((m)->m_data)) /* ---- KASSERT semantics, faithful to sys/sys/systm.h:94-122 --------------- */ #ifdef NO_INVARIANTS #define KASSERT(exp, msg) do { } while (0) /* exactly kernel :117-119 */ #else /* kernel :95-96 with INVARIANTS: if (__predict_false(!(exp))) panic msg; */ #define KASSERT(exp, msg) do { if (!(exp)) panic msg; } while (0) #endif /* panic() โ the kernel calls panic(); here we print the kernel-style message * and abort, mirroring a kernel panic. */ static void panic(const char *fmt, ...) __attribute__((noreturn)); static void panic(const char *fmt, ...) { va_list ap; va_start(ap, fmt); fprintf(stderr, "panic: "); vfprintf(stderr, fmt, ap); va_end(ap); fprintf(stderr, "cpuid = 0\n"); fprintf(stderr, "Trace begins at wep_decrypt+ (DF-0594 KASSERT site)\n"); abort(); } /* ---- crc32_table verbatim from ieee80211_crypto_tkip.c:397-450 ---------- */ static const __u32 crc32_table[256] = { 0x00000000L, 0x77073096L, 0xee0e612cL, 0x990951baL, 0x076dc419L, 0x706af48fL, 0xe963a535L, 0x9e6495a3L, 0x0edb8832L, 0x79dcb8a4L, 0xe0d5e91eL, 0x97d2d988L, 0x09b64c2bL, 0x7eb17cbdL, 0xe7b82d07L, 0x90bf1d91L, 0x1db71064L, 0x6ab020f2L, 0xf3b97148L, 0x84be41deL, 0x1adad47dL, 0x6ddde4ebL, 0xf4d4b551L, 0x83d385c7L, 0x136c9856L, 0x646ba8c0L, 0xfd62f97aL, 0x8a65c9ecL, 0x14015c4fL, 0x63066cd9L, 0xfa0f3d63L, 0x8d080df5L, 0x3b6e20c8L, 0x4c69105eL, 0xd56041e4L, 0xa2677172L, 0x3c03e4d1L, 0x4b04d447L, 0xd20d85fdL, 0xa50ab56bL, 0x35b5a8faL, 0x42b2986cL, 0xdbbbc9d6L, 0xacbcf940L, 0x32d86ce3L, 0x45df5c75L, 0xdcd60dcfL, 0xabd13d59L, 0x26d930acL, 0x51de003aL, 0xc8d75180L, 0xbfd06116L, 0x21b4f4b5L, 0x56b3c423L, 0xcfba9599L, 0xb8bda50fL, 0x2802b89eL, 0x5f058808L, 0xc60cd9b2L, 0xb10be924L, 0x2f6f7c87L, 0x58684c11L, 0xc1611dabL, 0xb6662d3dL, 0x76dc4190L, 0x01db7106L, 0x98d220bcL, 0xefd5102aL, 0x71b18589L, 0x06b6b51fL, 0x9fbfe4a5L, 0xe8b8d433L, 0x7807c9a2L, 0x0f00f934L, 0x9609a88eL, 0xe10e9818L, 0x7f6a0dbbL, 0x086d3d2dL, 0x91646c97L, 0xe6635c01L, 0x6b6b51f4L, 0x1c6c6162L, 0x856530d8L, 0xf262004eL, 0x6c0695edL, 0x1b01a57bL, 0x8208f4c1L, 0xf50fc457L, 0x65b0d9c6L, 0x12b7e950L, 0x8bbeb8eaL, 0xfcb9887cL, 0x62dd1ddfL, 0x15da2d49L, 0x8cd37cf3L, 0xfbd44c65L, 0x4db26158L, 0x3ab551ceL, 0xa3bc0074L, 0xd4bb30e2L, 0x4adfa541L, 0x3dd895d7L, 0xa4d1c46dL, 0xd3d6f4fbL, 0x4369e96aL, 0x346ed9fcL, 0xad678846L, 0xda60b8d0L, 0x44042d73L, 0x33031de5L, 0xaa0a4c5fL, 0xdd0d7cc9L, 0x5005713cL, 0x270241aaL, 0xbe0b1010L, 0xc90c2086L, 0x5768b525L, 0x206f85b3L, 0xb966d409L, 0xce61e49fL, 0x5edef90eL, 0x29d9c998L, 0xb0d09822L, 0xc7d7a8b4L, 0x59b33d17L, 0x2eb40d81L, 0xb7bd5c3bL, 0xc0ba6cadL, 0xedb88320L, 0x9abfb3b6L, 0x03b6e20cL, 0x74b1d29aL, 0xead54739L, 0x9dd277afL, 0x04db2615L, 0x73dc1683L, 0xe3630b12L, 0x94643b84L, 0x0d6d6a3eL, 0x7a6a5aa8L, 0xe40ecf0bL, 0x9309ff9dL, 0x0a00ae27L, 0x7d079eb1L, 0xf00f9344L, 0x8708a3d2L, 0x1e01f268L, 0x6906c2feL, 0xf762575dL, 0x806567cbL, 0x196c3671L, 0x6e6b06e7L, 0xfed41b76L, 0x89d32be0L, 0x10da7a5aL, 0x67dd4accL, 0xf9b9df6fL, 0x8ebeeff9L, 0x17b7be43L, 0x60b08ed5L, 0xd6d6a3e8L, 0xa1d1937eL, 0x38d8c2c4L, 0x4fdff252L, 0xd1bb67f1L, 0xa6bc5767L, 0x3fb506ddL, 0x48b2364bL, 0xd80d2bdaL, 0xaf0a1b4cL, 0x36034af6L, 0x41047a60L, 0xdf60efc3L, 0xa867df55L, 0x316e8eefL, 0x4669be79L, 0xcb61b38cL, 0xbc66831aL, 0x256fd2a0L, 0x5268e236L, 0xcc0c7795L, 0xbb0b4703L, 0x220216b9L, 0x5505262fL, 0xc5ba3bbeL, 0xb2bd0b28L, 0x2bb45a92L, 0x5cb36a04L, 0xc2d7ffa7L, 0xb5d0cf31L, 0x2cd99e8bL, 0x5bdeae1dL, 0x9b64c2b0L, 0xec63f226L, 0x756aa39cL, 0x026d930aL, 0x9c0906a9L, 0xeb0e363fL, 0x72076785L, 0x05005713L, 0x95bf4a82L, 0xe2b87a14L, 0x7bb12baeL, 0x0cb61b38L, 0x92d28e9bL, 0xe5d5be0dL, 0x7cdcefb7L, 0x0bdbdf21L, 0x86d3d2d4L, 0xf1d4e242L, 0x68ddb3f8L, 0x1fda836eL, 0x81be16cdL, 0xf6b9265bL, 0x6fb077e1L, 0x18b74777L, 0x88085ae6L, 0xff0f6a70L, 0x66063bcaL, 0x11010b5cL, 0x8f659effL, 0xf862ae69L, 0x616bffd3L, 0x166ccf45L, 0xa00ae278L, 0xd70dd2eeL, 0x4e048354L, 0x3903b3c2L, 0xa7672661L, 0xd06016f7L, 0x4969474dL, 0x3e6e77dbL, 0xaed16a4aL, 0xd9d65adcL, 0x40df0b66L, 0x37d83bf0L, 0xa9bcae53L, 0xdebb9ec5L, 0x47b2cf7fL, 0x30b5ffe9L, 0xbdbdf21cL, 0xcabac28aL, 0x53b39330L, 0x24b4a3a6L, 0xbad03605L, 0xcdd70693L, 0x54de5729L, 0x23d967bfL, 0xb3667a2eL, 0xc4614ab8L, 0x5d681b02L, 0x2a6f2b94L, 0xb40bbe37L, 0xc30c8ea1L, 0x5a05df1bL, 0x2d02ef8dL }; /* =========================================================================== * VERBATIM copy of wep_decrypt() from * sys/netproto/802_11/wlan_tkip/ieee80211_crypto_tkip.c lines 662-723. * S_SWAP (a local #define at kernel :610) is hoisted to file scope only so the * function body is byte-for-byte the audited code. Nothing else is altered. * =========================================================================== */ #define S_SWAP(a,b) do { u8 t = S[a]; S[a] = S[b]; S[b] = t; } while(0) static int wep_decrypt(u8 *key, struct mbuf *m, u_int off, size_t data_len) { u32 i, j, k, crc; u8 S[256]; u8 *pos, icv[4]; size_t buflen; /* Setup RC4 state */ for (i = 0; i < 256; i++) S[i] = i; j = 0; for (i = 0; i < 256; i++) { j = (j + S[i] + key[i & 0x0f]) & 0xff; S_SWAP(i, j); } /* Apply RC4 to data and compute CRC32 over decrypted data */ crc = ~0; i = j = 0; pos = mtod(m, uint8_t *) + off; buflen = m->m_len - off; for (;;) { if (buflen > data_len) buflen = data_len; data_len -= buflen; for (k = 0; k < buflen; k++) { i = (i + 1) & 0xff; j = (j + S[i]) & 0xff; S_SWAP(i, j); *pos ^= S[(S[i] + S[j]) & 0xff]; crc = crc32_table[(crc ^ *pos) & 0xff] ^ (crc >> 8); pos++; } m = m->m_next; if (m == NULL) { KASSERT(data_len == 0, ("out of buffers with data_len %zu\n", data_len)); break; } pos = mtod(m, uint8_t *); buflen = m->m_len; } crc = ~crc; /* Encrypt little-endian CRC32 and verify that it matches with the * received ICV */ icv[0] = crc; icv[1] = crc >> 8; icv[2] = crc >> 16; icv[3] = crc >> 24; for (k = 0; k < 4; k++) { i = (i + 1) & 0xff; j = (j + S[i]) & 0xff; S_SWAP(i, j); if ((icv[k] ^ S[(S[i] + S[j]) & 0xff]) != *pos++) { /* ICV mismatch - drop frame */ return -1; } } return 0; } /* ===================== end of verbatim wep_decrypt ======================= */ #undef S_SWAP /* ---- harness plumbing (NOT from the kernel) ------------------------------ */ /* The exact arithmetic from tkip_decrypt() at * sys/netproto/802_11/wlan_tkip/ieee80211_crypto_tkip.c:992-994. Operand types * are byte-for-byte so the usual arithmetic conversions match the kernel: * m->m_pkthdr.len is int, tkip.ic_header/ic_trailer are u_int, the whole RHS is * u_int, so the int LHS is converted to unsigned and the subtraction wraps. */ static size_t tkip_decrypt_data_len(int m_pkthdr_len, int hdrlen) { /* line 994, verbatim expression: */ return m_pkthdr_len - (hdrlen + tkip.ic_header + tkip.ic_trailer); } #ifdef NO_INVARIANTS static sigjmp_buf oob_jmp; static volatile sig_atomic_t got_sigsegv; static void sigsegv_handler(int sig, siginfo_t *si, void *uc) { (void)sig; (void)uc; got_sigsegv = 1; fprintf(stderr, " [SIGSEGV at %p โ OOB READ past mbuf data end]\n", si->si_addr); siglongjmp(oob_jmp, 1); } #endif int main(void) { /* The trigger frame from the finding: 24-byte 802.11 data header * (Protected) + 8-byte TKIP IV/EIV (ExtIV set). No payload/ICV/MIC. * Exactly IEEE80211_WEP_MINLEN = 32 bytes โ the WEP-only floor enforced by * the upper layer (ieee80211_crypto.c:598). TKIP actually needs >= 36. */ const int hdrlen = 24; /* 3-address data hdr */ const int frame_len = hdrlen + (int)tkip.ic_header; /* 24 + 8 = 32 */ const u_int decrypt_need = hdrlen + tkip.ic_header + tkip.ic_trailer; /* 36 */ struct mbuf mbuf; u8 rc4key[16] = {0}; /* key bytes irrelevant โ underflow precedes outcome */ fprintf(stderr, "=== DF-0594 TKIP RX length-underflow proof ===\n"); fprintf(stderr, "kernel: sys/netproto/802_11/wlan_tkip/ieee80211_crypto_tkip.c\n"); fprintf(stderr, "cipher: ic_header=%u ic_trailer=%u ic_miclen=%u (u_int)\n", tkip.ic_header, tkip.ic_trailer, tkip.ic_miclen); fprintf(stderr, "frame: m_pkthdr.len=%d (int), hdrlen=%d, total=%d\n", frame_len, hdrlen, frame_len); fprintf(stderr, "WEP floor 32 (ieee80211_crypto.c:598 IEEE80211_WEP_MINLEN)\n"); fprintf(stderr, "TKIP need %u (hdrlen + ic_header + ic_trailer)\n", decrypt_need); fprintf(stderr, "build: %s\n", #ifdef NO_INVARIANTS "NO_INVARIANTS (production kernel โ KASSERT compiled out)" #else "INVARIANTS (default X86_64_GENERIC โ KASSERT = panic)" #endif ); fprintf(stderr, "\n"); /* ---- Step 1: show the line-994 arithmetic underflows ----------------- */ size_t data_len = tkip_decrypt_data_len(frame_len, hdrlen); fprintf(stderr, "[line 994] data_len = m_pkthdr.len - (hdrlen + ic_header + ic_trailer)\n"); fprintf(stderr, " = (int)%d - (u_int)(%d + %u + %u)\n", frame_len, hdrlen, tkip.ic_header, tkip.ic_trailer); fprintf(stderr, " = (u_int)%u - (u_int)%u [int promoted to u_int]\n", (u_int)frame_len, (u_int)decrypt_need); fprintf(stderr, " = 0x%016zx (size_t) <-- SIGNED/UNSIGNED WRAP (CWE-190)\n", data_len); if (data_len <= (size_t)frame_len) { fprintf(stderr, "RESULT: no underflow (unexpected)\n"); return 2; } fprintf(stderr, "RESULT: data_len %zu > frame_len %d => UNDERFLOW CONFIRMED\n", data_len, frame_len); #ifdef NO_INVARIANTS /* ---- Step 2: prove the OOB read at wep_decrypt:717 ----------------- * Place the frame at the very end of a writable page, with a PROT_NONE * guard page immediately after. The ICV check loop does *pos++ starting at * offset 32 == m_len, i.e. the first read is one byte PAST the data โ the * first byte of the guard page -> SIGSEGV. This is exactly the "cluster * whose backing page ends inside the read window" page-fault the finding * describes for production (non-INVARIANTS) kernels. */ long ps = sysconf(_SC_PAGESIZE); size_t mapsz = (size_t)ps * 2; uint8_t *region = mmap(NULL, mapsz, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); if (region == MAP_FAILED) { perror("mmap"); return 2; } /* guard page: unmap PROT on the second page */ if (mprotect(region + ps, ps, PROT_NONE) != 0) { perror("mprotect"); return 2; } uint8_t *data = region + ps - frame_len; /* data ends exactly at page end */ memset(data, 0, frame_len); mbuf.m_next = NULL; mbuf.m_len = frame_len; mbuf.m_data = data; mbuf.m_pkthdr.len = frame_len; struct sigaction sa, oldsa; memset(&sa, 0, sizeof(sa)); sa.sa_sigaction = sigsegv_handler; sa.sa_flags = SA_SIGINFO; sigemptyset(&sa.sa_mask); sigaction(SIGSEGV, &sa, &oldsa); fprintf(stderr, "\n[NO_INVARIANTS] calling verbatim wep_decrypt with the " "frame buffer ending at a page boundary (+guard page):\n"); fprintf(stderr, " m_data=%p m_data+m_len=%p guard page starts at %p\n", (void*)data, (void*)(data + frame_len), (void*)(region + ps)); int rc; if (sigsetjmp(oob_jmp, 1) == 0) { rc = wep_decrypt(rc4key, &mbuf, hdrlen + tkip.ic_header, data_len); fprintf(stderr, "[wep_decrypt returned %d โ no fault (data was in-bounds " "padding on this allocator)]\n", rc); if (got_sigsegv) return 2; /* If no fault, it means the read landed in the same page's tail * padding; the OOB read still happened but was absorbed. Report that * honestly. */ fprintf(stderr, "RESULT: OOB read occurred but was absorbed by in-page " "padding (no page fault this run); defect still present " "(CWE-125/CWE-787) โ repeat with a guard page boundary.\n"); } else { fprintf(stderr, "RESULT: SIGSEGV in wep_decrypt ICV check โ OOB READ " "CONFIRMED (CWE-125/CWE-787), page-fault on read past mbuf end.\n"); } sigaction(SIGSEGV, &oldsa, NULL); #else /* ---- Step 2: prove the KASSERT panic (INVARIANTS build) ------------- */ static uint8_t backing[64]; memset(backing, 0, sizeof(backing)); mbuf.m_next = NULL; mbuf.m_len = frame_len; mbuf.m_data = backing; mbuf.m_pkthdr.len = frame_len; fprintf(stderr, "\n[INVARIANTS] calling verbatim wep_decrypt; " "KASSERT(data_len==0) at ieee80211_crypto_tkip.c:698 fires:\n"); fflush(stderr); int rc = wep_decrypt(rc4key, &mbuf, hdrlen + tkip.ic_header, data_len); fprintf(stderr, "[wep_decrypt returned %d] (UNEXPECTED โ KASSERT should " "have panicked)\n", rc); return 2; /* unreachable on INVARIANTS build */ #endif return 0; } |