⬢ DragonFlyBSD Kernel Audit
DF-5706 / guard_harness.c
← back to finding ↓ download raw
/*
 * Guard-page containment rig for sys/libkern/memcchr.c (compiled VERBATIM
 * as df_memcchr from the pristine kernel body, lines 36-117 byte-identical).
 * The usable data page is flanked by PROT_NONE guard pages on BOTH sides.
 *
 * Property under test (memcchr.c:83-91 loads):
 *   every longword load is naturally aligned (8 divides the page size) and
 *   its granule holds >= 1 in-bounds byte of [begin, begin+n)  =>  the
 *   over-read (up to 7 bytes below begin / past end) NEVER leaves the
 *   buffer's valid page.
 *
 * Any load escaping the valid page => SIGSEGV => the harness dies with the
 * current case recorded in journal.txt (I/O-line-buffered).  Clean exit =
 * containment held over the whole sweep.
 *
 * Content modes additionally verify the done-filter (memcchr.c:114):
 *   0: all bytes = c                          -> must return NULL
 *   1: mismatch at the LAST in-range byte      -> must return &buf[n-1]
 *   2: canaries != c everywhere OUTSIDE
 *      [begin, end), all c inside             -> must return NULL
 *      (this is the killer case: the word walk's last granule load reads
 *      non-c bytes past end, TESTBYTE walks past end, done-filter must
 *      reject it and return NULL)
 *   3: random content (expected recomputed)    -> must match reference
 *   4: mismatch at the FIRST in-range byte     -> must return begin
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <sys/mman.h>
#include <unistd.h>

void *df_memcchr(const void *, int, size_t);

#define PS 4096
static unsigned char *P;	/* the single usable page, guarded both sides */

static FILE *journal;
static char jline[256];

static uint64_t s_state = 0x243f6a8885a308d3ULL;
static uint64_t prng(void)
{
	s_state ^= s_state << 13; s_state ^= s_state >> 7; s_state ^= s_state << 17;
	return s_state;
}

static long cases, nullrets, ptrrets, wrong;

static void jwrite(void) __attribute__((__unused__));

static void
fill(int c, int mode, int s, int l)
{
	int i;

	for (i = 0; i < PS; i++)
		P[i] = (mode == 3) ? (unsigned char)prng() : (unsigned char)c;
	if (mode == 2) {
		for (i = 0; i < s; i++)
			P[i] = (unsigned char)~c;
		for (i = s + l; i < PS; i++)
			P[i] = (unsigned char)~c;
	}
	if (mode == 1 && l > 0)
		P[s + l - 1] = (unsigned char)~c;	/* last in-range byte */
	if (mode == 4 && l > 0)
		P[s] = (unsigned char)~c;		/* first in-range byte */
}

static void
one(int s, int l, int mode, int c)
{
	void *r, *exp;

	snprintf(jline, sizeof(jline), "s=%d l=%d mode=%d c=%d\n", s, l, mode, c);
	fill(c, mode, s, l);
	r = df_memcchr(P + s, c, (size_t)l);

	switch (mode) {
	case 0:
	case 2:
		exp = NULL;
		break;
	case 1:
		exp = (l > 0) ? (void *)(P + s + l - 1) : NULL;
		break;
	case 4:
		exp = (l > 0) ? (void *)(P + s) : NULL;
		break;
	default: {
		int i;

		exp = NULL;
		for (i = 0; i < l; i++)
			if (P[s + i] != (unsigned char)c) {
				exp = P + s + i;
				break;
			}
	}
	}

	cases++;
	if (r == NULL)
		nullrets++;
	else
		ptrrets++;
	if (r != exp) {
		printf("WRONG-RETURN s=%d l=%d mode=%d c=%d: got %p want %p\n",
		    s, l, mode, c, r, exp);
		wrong++;
	}
}

static void
jwrite(void)
{
	fclose(journal);
	fprintf(stderr, "DIED IN: %s", jline);
}

int
main(void)
{
	unsigned char *a = mmap(NULL, 3 * PS, PROT_READ | PROT_WRITE,
	    MAP_PRIVATE | MAP_ANON, -1, 0);
	int lens[64], nl, s, k, mode;

	if (a == MAP_FAILED) { perror("mmap"); return 2; }
	P = a + PS;
	if (mprotect(a, PS, PROT_NONE) || mprotect(a + 2 * PS, PS, PROT_NONE)) {
		perror("mprotect"); return 2;
	}
	journal = fopen("journal.txt", "w");
	if (!journal) { perror("fopen"); return 2; }
	setvbuf(journal, NULL, _IOLBF, 0);

	for (s = 0; s < PS; s++) {
		nl = 0;
		for (k = 0; k <= 24; k++)		/* small lengths incl. 0 */
			lens[nl++] = k;
		for (k = 0; k <= 9; k++) {		/* page-edge-critical:
						   end lands at PS-k */
			int l = PS - s - k;
			if (l >= 0 && l <= PS - s)
				lens[nl++] = l;
		}
		if (137 <= PS - s)  lens[nl++] = 137;
		if (1024 <= PS - s) lens[nl++] = 1024;
		for (k = 0; k < nl; k++) {
			int l = lens[k];
			if (l < 0 || l > PS - s)
				continue;
			for (mode = 0; mode < 5; mode++) {
				one(s, l, mode, 0xff);	/* ext2's c */
				if ((s & 63) == 0)
					one(s, l, mode, 0x00);
			}
		}
		if ((s & 1023) == 1023)
			fprintf(journal, "progress s=%d cases=%ld\n", s, cases);
	}
	fprintf(journal, "done cases=%ld\n", cases);
	fclose(journal);

	printf("guard sweep: %ld calls (NULL=%ld ptr=%ld), wrong returns=%ld\n",
	    cases, nullrets, ptrrets, wrong);
	if (wrong != 0) {
		printf("RESULT: FAIL - done-filter returned a bad pointer\n");
		return 1;
	}
	printf("RESULT: PASS - no load escaped the valid page (both-side"
	    " guards); done-filter never leaked a past-end mismatch\n");
	return 0;
}