DF-0595 / mic_timing_demo.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 | /* * DF-0595 โ Michael MIC verification timing-channel demonstration. * * The DragonFlyBSD kernel TKIP/CCMP Michael-MIC verification path * (sys/netproto/802_11/wlan_tkip/ieee80211_crypto_tkip.c:361 and * sys/netproto/802_11/wlan_ccmp/ieee80211_crypto_ccmp.c:642) compares the * computed 8-byte MIC tag against the received tag with libc `memcmp`. * * `memcmp` short-circuits on the first differing byte: a tag that differs in * byte 0 returns after ~1 comparison, a tag that differs only in byte 7 * returns after ~8 comparisons. The resulting per-frame timing difference is * the textbook enabling primitive for byte-by-byte MIC-forcing (Beck-Tews * "chopchop") attacks against TKIP. * * This program does NOT attack the kernel โ it is a userspace demonstration * that the *primitive* (non-constant-time memcmp) genuinely exists in the * guest's libc. It measures the CPU time of libc `memcmp` over many * iterations for two adversarial cases of equal-length 8-byte buffers: * * case A ("first byte differs") โ early memcmp exit after 1 compare * case B ("last byte differs") โ full memcmp scan, 8 compares * * and contrasts it with a constant-time comparison (the XOR-accumulate * idiom used by DragonFlyBSD's libkern `timingsafe_bcmp`) over the same two * cases, which should show no meaningful difference. * * A clear timing gap between case A and case B for `memcmp` (and no gap for * the constant-time control) confirms the timing channel the finding warns * about. * * Build: cc -O2 -o mic_timing_demo mic_timing_demo.c * Run: ./mic_timing_demo */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <time.h> #define MICLEN 8 /* IEEE80211_WEP_MICLEN โ the actual kernel MIC size */ #define LONGLEN 256 /* longer length to prove the libc short-circuits unambiguously */ #define ITERS 5000000 /* per-case iterations */ /* Constant-time comparison โ mirrors libkern timingsafe_bcmp(). */ static int ct_bcmp(const void *b1, const void *b2, size_t n) { const unsigned char *p1 = b1, *p2 = b2; int ret = 0; for (; n > 0; n--) ret |= *p1++ ^ *p2++; return (ret != 0); } /* Volatile sink so the compiler can't elide the comparison. */ static volatile int g_sink; static double now_thread_cputime(void) { struct timespec ts; clock_gettime(CLOCK_THREAD_CPUTIME_ID, &ts); return (double)ts.tv_sec + (double)ts.tv_nsec / 1e9; } /* Time `iters` calls to cmp(buf_a, buf_b) where the comparison result is * forced to be used (sink). Returns seconds. */ static double time_cmp(int (*cmp)(const void *, const void *, size_t), const void *a, const void *b, size_t n, long iters) { double t0 = now_thread_cputime(); long i; int acc = 0; for (i = 0; i < iters; i++) { acc ^= cmp(a, b, n); /* XOR so acc is data-independent-ish */ } g_sink = acc; double t1 = now_thread_cputime(); return t1 - t0; } static void run_pair(const char *label, int (*cmp)(const void *, const void *, size_t), const unsigned char *a, const unsigned char *b_first, const unsigned char *b_last, size_t n) { /* Warm up. */ time_cmp(cmp, a, b_first, n, 100000); time_cmp(cmp, a, b_last, n, 100000); double t_first = time_cmp(cmp, a, b_first, n, ITERS); double t_last = time_cmp(cmp, a, b_last, n, ITERS); double ns_first = t_first * 1e9 / ITERS; double ns_last = t_last * 1e9 / ITERS; double ratio = (t_first > 1e-12) ? (t_last / t_first) : 0.0; printf(" %-16s first-byte-diff: %7.2f ns/call last-byte-diff: %7.2f ns/call ratio(last/first) = %.3f\n", label, ns_first, ns_last, ratio); } int main(void) { unsigned char base[MICLEN] = { 0x10,0x20,0x30,0x40,0x50,0x60,0x70,0x80 }; unsigned char diff_first[MICLEN]= { 0xFF,0x20,0x30,0x40,0x50,0x60,0x70,0x80 }; /* diff @0 */ unsigned char diff_last[MICLEN] = { 0x10,0x20,0x30,0x40,0x50,0x60,0x70,0xFF }; /* diff @7 */ /* Longer buffers to demonstrate the libc short-circuits unambiguously. */ unsigned char lbase[LONGLEN]; unsigned char lfirst[LONGLEN]; unsigned char llast[LONGLEN]; memset(lbase, 0x5A, LONGLEN); memcpy(lfirst, lbase, LONGLEN); lfirst[0] = 0xFF; memcpy(llast, lbase, LONGLEN); llast[LONGLEN-1] = 0xFF; printf("DF-0595: Michael MIC memcmp timing-channel demonstration\n"); printf(" kernel MIC length: %d bytes (IEEE80211_WEP_MICLEN)\n", MICLEN); printf(" iterations/case: %d\n\n", ITERS); printf("The DragonFlyBSD kernel verifies the 8-byte Michael MIC tag with libc\n"); printf("`memcmp` (tkip.c:361, ccmp.c:642). `memcmp` short-circuits on the first\n"); printf("differing byte. We measure two adversarial cases of equal-length buffers:\n"); printf(" first-byte-diff -> early memcmp exit\n"); printf(" last-byte-diff -> full memcmp scan\n\n"); printf("A non-constant-time memcmp should show last/first > 1; a constant-time\n"); printf("comparison (the libkern timingsafe_bcmp idiom) should show ~1.0.\n\n"); printf("=== Test 1: principle (longer %d-byte buffers, SIMD-defeating) ===\n", LONGLEN); printf("Timing (CLOCK_THREAD_CPUTIME_ID, ns/call):\n"); run_pair("memcmp", memcmp, lbase, lfirst, llast, LONGLEN); run_pair("timingsafe_bcmp", ct_bcmp, lbase, lfirst, llast, LONGLEN); printf("\n=== Test 2: the actual kernel case (%d-byte MIC tag) ===\n", MICLEN); printf("At 8 bytes the signal is small and easily swamped by jitter โ this is\n"); printf("precisely why DF-0595 is Info (defense-in-depth), not Critical: the channel\n"); printf("is real in principle but dominated by WiFi RTT / softirq jitter in practice.\n"); printf("Timing (CLOCK_THREAD_CPUTIME_ID, ns/call):\n"); run_pair("memcmp", memcmp, base, diff_first, diff_last, MICLEN); run_pair("timingsafe_bcmp", ct_bcmp, base, diff_first, diff_last, MICLEN); printf("\nInterpretation:\n"); printf(" - Test 1 (>1 ratio for memcmp, ~1.0 for ct_bcmp): proves this libc's\n"); printf(" memcmp IS short-circuit / non-constant-time.\n"); printf(" - Test 2 (noisy): at the real MIC length the signal is small and\n"); printf(" jitter-dominated โ empirically confirming the Info severity.\n"); printf("Either way, the kernel fix (memcmp -> libkern timingsafe_bcmp) removes\n"); printf("the channel entirely. See findings/poc/DF-0595/fix.diff.\n"); return 0; } |