/*
 * guardfuzz.c -- precise overread detector for the kernel fnmatch source
 * (compiled verbatim via fnmatch_k.c + shim.h).
 *
 * For every case, pattern and string are copied so that the terminating
 * NUL is the LAST readable byte of a page, immediately followed by a
 * PROT_NONE page.  Any read past the NUL (the entire class of
 * rangematch()/_kfnmatch() overruns) triggers SIGSEGV, caught with
 * siglongjmp and reported with the exact case.
 *
 * Corpus:
 *   - exhaustive: patterns len 1..3 x strings len 1..3 over a 12-symbol
 *     adversarial alphabet, all 9 flag combos        (~32M calls)
 *   - 500k random cases, lengths 1..24, alphabet incl. high bytes
 *
 * build: cc -O2 -o guardfuzz guardfuzz.c fnmatch_k.c
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <setjmp.h>
#include <unistd.h>
#include <sys/mman.h>
#include <fnmatch.h>

int _kfnmatch(const char *, const char *, int, int);

static size_t PG;
static char *ppage, *spage;	/* readable pages; next page PROT_NONE */
static sigjmp_buf jb;
static volatile sig_atomic_t in_call;

static void fault(int sig, siginfo_t *si, void *uc)
{
	(void)sig; (void)si; (void)uc;
	if (in_call)
		siglongjmp(jb, 1);
	_exit(99);		/* fault outside the guarded call: fatal */
}


/* run one case; returns 1 if it faulted (overread) */
static int gencall(const char *p, const char *s, int f)
{
	size_t plen = strlen(p), slen = strlen(s);

	memcpy(ppage + PG - 1 - plen, p, plen + 1);  /* NUL at last byte */
	memcpy(spage + PG - 1 - slen, s, slen + 1);

	if (sigsetjmp(jb, 1) == 0) {
		in_call = 1;
		(void)_kfnmatch(ppage + PG - 1 - plen, spage + PG - 1 - slen,
		    f, 0);
		in_call = 0;
		return 0;
	}
	in_call = 0;
	return 1;
}

static const char alpha[] = "*?[]-\\!^ab/.";
static const char ralpha[] = "*?[]-\\!^ab/.\x80\xbf\xd0\xff]";
static const int flags_all[] = { 0, FNM_PATHNAME, FNM_PERIOD,
    FNM_PATHNAME | FNM_PERIOD, FNM_NOESCAPE, FNM_CASEFOLD,
    FNM_PATHNAME | FNM_PERIOD | FNM_NOESCAPE, FNM_LEADING_DIR,
    FNM_PATHNAME | FNM_LEADING_DIR | FNM_PERIOD };
#define NFLAGS 9

static long nfault = 0, ncase = 0;
static char curpbuf[8];

static void cb_str(const char *s, int len)
{
	int f;
	(void)len;
	for (f = 0; f < NFLAGS; f++) {
		ncase++;
		if (gencall(curpbuf, s, flags_all[f])) {
			nfault++;
			if (nfault <= 20)
				printf("OVERREAD pattern=\"%s\" string=\"%s\" "
				    "flags=%#x\n", curpbuf, s, flags_all[f]);
		}
	}
}

static long enum_syms(int maxlen, const char *tab, int na,
    void (*cb)(const char *, int))
{
	char buf[8];
	long total = 0;

	for (int len = 1; len <= maxlen; len++) {
		long count = 1;
		for (int i = 0; i < len; i++)
			count *= na;
		for (long idx = 0; idx < count; idx++) {
			long x = idx;
			for (int i = len - 1; i >= 0; i--) {
				buf[i] = tab[x % na];
				x /= na;
			}
			buf[len] = 0;
			cb(buf, len);
			total++;
		}
	}
	return total;
}

static void cb_pat(const char *p, int len)
{
	(void)len;
	snprintf(curpbuf, sizeof(curpbuf), "%s", p);
	enum_syms(3, alpha, 12, cb_str);
}

int main(void)
{
	struct sigaction sa;
	long nrand = 500000;

	PG = 4096;		/* guest pagesize; assert below */
	{
		size_t q = (size_t)sysconf(_SC_PAGESIZE);
		if (q)
			PG = q;
	}
	ppage = mmap(NULL, PG * 2, PROT_READ | PROT_WRITE,
	    MAP_PRIVATE | MAP_ANON, -1, 0);
	spage = mmap(NULL, PG * 2, PROT_READ | PROT_WRITE,
	    MAP_PRIVATE | MAP_ANON, -1, 0);
	if (ppage == MAP_FAILED || spage == MAP_FAILED) {
		perror("mmap");
		return 2;
	}
	if (mprotect(ppage + PG, PG, PROT_NONE) ||
	    mprotect(spage + PG, PG, PROT_NONE)) {
		perror("mprotect");
		return 2;
	}

	memset(&sa, 0, sizeof(sa));
	sa.sa_sigaction = fault;
	sa.sa_flags = SA_SIGINFO;
	sigaction(SIGSEGV, &sa, NULL);
	sigaction(SIGBUS, &sa, NULL);

	enum_syms(3, alpha, 12, cb_pat);	/* 1884 patterns x 1884 strings */
	printf("[exhaustive] %ld cases, %ld overreads\n", ncase, nfault);

	srandom(4242);
	for (long i = 0; i < nrand; i++) {
		char pat[32], str[32];
		int nr = (int)(sizeof(ralpha)) - 1;
		int plen = 1 + random() % 24;
		int slen = 1 + random() % 24;
		for (int j = 0; j < plen; j++)
			pat[j] = ralpha[random() % nr];
		pat[plen] = 0;
		for (int j = 0; j < slen; j++)
			str[j] = ralpha[random() % nr];
		str[slen] = 0;
		int f = flags_all[random() % NFLAGS];
		ncase++;
		if (gencall(pat, str, f)) {
			nfault++;
			if (nfault <= 20)
				printf("OVERREAD pattern=\"%s\" string=\"%s\" "
				    "flags=%#x\n", pat, str, f);
		}
	}
	printf("[random] total %ld cases, %ld overreads\n", ncase, nfault);
	return nfault ? 1 : 0;
}
