/*
 * 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;
}
