DF-3275 / df3275.c
/* * DF-3275 harness -- non-constant-time authentication compares in sppp: * * PAP (if_spppsubr.c:4330-4336): * if (name_len != strnlen(...) || * passwd_len != strnlen(sp->hisauth.secret, AUTHKEYLEN) || * bcmp(name, sp->hisauth.name, name_len) != 0 || * bcmp(passwd, sp->hisauth.secret, passwd_len) != 0) * bcmp returns at the FIRST mismatching byte: the time to reject is a * monotone function of the common-prefix length between the supplied * password and the real secret (the length equality gate does not * remove the prefix oracle -- it only fixes the compared length). * * CHAP (if_spppsubr.c:4069-4071): * if (value_len != sizeof digest || bcmp(digest, value, value_len) != 0) * early-exit over the 16-byte MD5 digest. * * Measure the rejection time as a function of the common prefix on the * live host (quiet VM): a monotone, measurable curve = the timing * channel exists. (Remote exploitation over PPP is noise-limited; the * measurement proves the code property.) */ #include <stdio.h> #include <string.h> #include <stdlib.h> #include <time.h> #define AUTHKEYLEN 16 static char secret[AUTHKEYLEN + 1]; static inline unsigned long long rdtsc(void) { unsigned long long lo, hi; __asm __volatile("rdtsc" : "=a"(lo), "=d"(hi)); return ((hi << 32) | lo); } /* verbatim compare gate (bcmp == memcmp semantics: early exit) */ static int pap_check(const char *passwd, int passwd_len) { if (passwd_len != (int)strnlen(secret, AUTHKEYLEN)) return 1; if (memcmp(passwd, secret, passwd_len) != 0) /* bcmp */ return 1; return 0; } int main(void) { const int ITER = 200000; char guess[AUTHKEYLEN + 1]; int p, i; long long t0, t1; double ns_per[AUTHKEYLEN + 1]; /* random secret, fixed length */ srandomdev ? (void)0 : srandom(12345); for (i = 0; i < AUTHKEYLEN; i++) secret[i] = 'a' + (random() % 26); secret[AUTHKEYLEN] = 0; printf("DF-3275 (PAP/CHAP early-exit bcmp timing, if_spppsubr.c" " :4330-4336/:4069-4071)\n"); printf("secret length = %d (AUTHKEYLEN gate equal-length compare)\n", AUTHKEYLEN); for (p = 0; p <= AUTHKEYLEN; p++) { memcpy(guess, secret, AUTHKEYLEN); if (p < AUTHKEYLEN) guess[p] = (guess[p] == 'z') ? 'a' : 'z'; /* mismatch at p */ t0 = rdtsc(); for (i = 0; i < ITER; i++) pap_check(guess, AUTHKEYLEN); t1 = rdtsc(); ns_per[p] = (double)(t1 - t0) / ITER; printf(" common prefix %2d: %8.1f cycles/reject\n", p, ns_per[p]); } int mono = 1; for (p = 0; p < AUTHKEYLEN; p++) if (ns_per[p + 1] <= ns_per[p] * 1.02) mono = 0; if (ns_per[AUTHKEYLEN] > ns_per[0] * 1.5 && mono) { printf("RESULT: REPRODUCED -- rejection time grows monotonically" " with the matching prefix length: byte-at-a-position timing" " oracle on the PAP secret (and on the CHAP digest)\n"); return 0; } printf("RESULT: weak/no measurable delta on this host (rerun on a" " quieter machine); code property unchanged (verbatim bcmp)\n"); return 1; } |