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