⬢ DragonFlyBSD Kernel Audit
DF-5719 / bcd_rig.c
← back to finding ↓ download raw
/*
 * bcd_rig.c -- DF-5719 userland verbatim-algorithm rig.
 *
 * Embeds VERBATIM the tables from sys/libkern/bcd.c (bcd2bin_data, 154
 * entries; bin2bcd_data, 100 entries; hex2ascii_data, 37 bytes) and the
 * VERBATIM indexing macros from sys/sys/libkern.h:59-61, then proves:
 *
 *   MODE=1 SIZE  : actual object sizes (bin2bcd_data == 100 == the largest
 *                  in-domain index + 1; anything above is out of bounds).
 *   MODE=2 GUARD : the tables are placed so each object ENDS exactly at a
 *                  PROT_NONE guard page; the verbatim macros indexed with
 *                  the unvalidated u_char ioctl bytes (scsi_cd.c:2045,2161
 *                  receive args->start_track/args->track straight from
 *                  userspace, 0..255) SIGSEGV -- formal proof the macro
 *                  reads past the end of the object.
 *   MODE=3 DIFF  : exhaustive semantics check -- table macro vs true BCD
 *                  arithmetic over the whole valid domain (bin 0..99,
 *                  bcd 0x00..0x99 with both nibbles <= 9), plus the fix
 *                  masks are identity on the valid domain.
 *   MODE=4 FIXED : guard-page placement + the FIXED macros from fix.diff
 *                  (index masked into range) -- same probes, no fault.
 *
 * Build: cc -O2 -DMODE=n -o rig bcd_rig.c
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <sys/mman.h>
#include <sys/wait.h>

/* ---- VERBATIM from sys/libkern/bcd.c:9-36 ---- */
static const unsigned char v_bcd2bin_data[] = {
	 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 0, 0, 0, 0, 0, 0,
	10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 0, 0, 0, 0, 0, 0,
	20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 0, 0, 0, 0, 0, 0,
	30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 0, 0, 0, 0, 0, 0,
	40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 0, 0, 0, 0, 0, 0,
	50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 0, 0, 0, 0, 0, 0,
	60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 0, 0, 0, 0, 0, 0,
	70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 0, 0, 0, 0, 0, 0,
	80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 0, 0, 0, 0, 0, 0,
	90, 91, 92, 93, 94, 95, 96, 97, 98, 99
};
static const unsigned char v_bin2bcd_data[] = {
	0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
	0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19,
	0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29,
	0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39,
	0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49,
	0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59,
	0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69,
	0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79,
	0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89,
	0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99
};
static const char v_hex2ascii_data[] =
	"0123456789abcdefghijklmnopqrstuvwxyz";

#define	B2B_N	(sizeof(v_bin2bcd_data))	/* expect 100 */
#define	B2D_N	(sizeof(v_bcd2bin_data))	/* expect 154 */

/* ---- VERBATIM macro text from sys/sys/libkern.h:59-61 (array names
 *     redirected to the placement pointers so we control adjacency) ---- */
static const unsigned char *bin2bcd_data;
static const unsigned char *bcd2bin_data;
#define	bcd2bin(bcd)	(bcd2bin_data[bcd])
#define	bin2bcd(bin)	(bin2bcd_data[bin])
#define	hex2ascii(hex)	(hex2ascii_data[hex])
static const char *hex2ascii_data;

/* ---- fix.diff treatment (index wrapped into the object; identity on
 *     the valid domain because every valid index < the modulus) ---- */
#define	bcd2bin_fixed(bcd)	(bcd2bin_data[(bcd) % (int)B2D_N])
#define	bin2bcd_fixed(bin)	(bin2bcd_data[(u_char)(bin) % B2B_N])

static void *
place_at_guard(const void *obj, size_t n)
{
	/* map 2 pages, guard the 2nd, copy obj so it ENDS at the boundary */
	unsigned char *m = mmap(NULL, 2 * 4096, PROT_READ | PROT_WRITE,
				MAP_ANON | MAP_PRIVATE, -1, 0);
	if (m == MAP_FAILED) { perror("mmap"); exit(2); }
	if (mprotect(m + 4096, 4096, PROT_NONE) != 0) {
		perror("mprotect"); exit(2);
	}
	memcpy(m + 4096 - n, obj, n);
	return (m + 4096 - n);
}

static int
probe(void (*fn)(void))
{
	pid_t p = fork();
	int st;
	if (p == 0) { fn(); _exit(0); }
	waitpid(p, &st, 0);
	if (WIFSIGNALED(st) && WTERMSIG(st) == SIGSEGV)
		return (1);	/* read crossed the object boundary */
	if (WIFEXITED(st) && WEXITSTATUS(st) == 0)
		return (0);
	return (-1);
}

static volatile unsigned char sink;

static void pb_200(void) { sink = bin2bcd(200); }	/* CDIOCPLAYTRACKS */
static void pb_255(void) { sink = bin2bcd(255); }	/* max u_char */
static void pb_100(void) { sink = bin2bcd(100); }	/* first OOB */
static void pb_ok(void)  { sink = bin2bcd(99);  }	/* last in-bounds */
static void pd_0x9a(void){ sink = bcd2bin(0x9a); }	/* first OOB bcd */
static void pd_0xff(void){ sink = bcd2bin(0xff); }	/* garbage CMOS */
static void pd_ok(void)  { sink = bcd2bin(0x99); }	/* last in-bounds */
static void pf_200(void) { sink = bin2bcd_fixed(200); }
static void pf_255(void) { sink = bin2bcd_fixed(255); }
static void pdf_ff(void) { sink = bcd2bin_fixed(0xff); }

int
main(void)
{
	int r, bad = 0;

	setvbuf(stdout, NULL, _IONBF, 0);
#if MODE == 1
	printf("sizeof(bcd2bin_data) = %zu (max valid BCD index 0x99=%d)\n",
	       B2D_N, 0x99);
	printf("sizeof(bin2bcd_data) = %zu (max valid bin index 99)\n", B2B_N);
	printf("sizeof(hex2ascii_data) = %zu\n", sizeof(v_hex2ascii_data));
	printf("=> any macro index above %zu/%d is out of bounds\n",
	       B2B_N, 0x99);
#elif MODE == 2
	bin2bcd_data = place_at_guard(v_bin2bcd_data, B2B_N);
	bcd2bin_data = place_at_guard(v_bcd2bin_data, B2D_N);
	hex2ascii_data = place_at_guard(v_hex2ascii_data,
					sizeof(v_hex2ascii_data));
	printf("guard-page rig (verbatim kernel macros, objects end at "
	       "PROT_NONE boundary)\n");
	r = probe(pb_ok);  printf("bin2bcd(99)   (last in-bounds) : %s\n",
				  r == 0 ? "OK" : "FAULT"); bad += !!r;
	r = probe(pb_100); printf("bin2bcd(100)  (first OOB)      : %s\n",
				  r == 1 ? "SIGSEGV" : "no fault?"); bad += r != 1;
	r = probe(pb_200); printf("bin2bcd(200)  (ioctl byte)     : %s\n",
				  r == 1 ? "SIGSEGV" : "no fault?"); bad += r != 1;
	r = probe(pb_255); printf("bin2bcd(255)  (max u_char)     : %s\n",
				  r == 1 ? "SIGSEGV" : "no fault?"); bad += r != 1;
	r = probe(pd_ok);  printf("bcd2bin(0x99) (last in-bounds) : %s\n",
				  r == 0 ? "OK" : "FAULT"); bad += !!r;
	r = probe(pd_0x9a);printf("bcd2bin(0x9a) (first OOB)      : %s\n",
				  r == 1 ? "SIGSEGV" : "no fault?"); bad += r != 1;
	r = probe(pd_0xff);printf("bcd2bin(0xff) (garbage CMOS)   : %s\n",
				  r == 1 ? "SIGSEGV" : "no fault?"); bad += r != 1;
	printf("=> verbatim macros index past the end of the objects\n");
	return (bad ? 0 : 1);
#elif MODE == 3
	bin2bcd_data = v_bin2bcd_data;
	bcd2bin_data = v_bcd2bin_data;
	hex2ascii_data = v_hex2ascii_data;
	{
		int i, e = 0;
		for (i = 0; i < 100; i++) {
			unsigned char want = (unsigned char)
				(((i / 10) << 4) | (i % 10));
			if (bin2bcd(i) != want) e++;
			if (bin2bcd_fixed(i) != bin2bcd(i)) e++;
		}
		printf("bin2bcd vs arithmetic + fix-mask identity, 0..99: "
		       "%s (%d errors)\n", e ? "FAIL" : "MATCH", e);
		bad += e;
		e = 0;
		for (i = 0; i <= 0x99; i++) {
			int hi = i >> 4, lo = i & 0xf;
			if (hi <= 9 && lo <= 9) {	/* valid BCD */
				if (bcd2bin(i) != hi * 10 + lo) e++;
				if (bcd2bin_fixed(i) != bcd2bin(i)) e++;
			}
			/* holes: table stores 0s -- fix mask preserves
			 * nothing to compare there; only check fixed stays
			 * in-domain numerically (mask keeps index <= 0x99)
			 */
		}
		printf("bcd2bin vs arithmetic + fix-mask identity over valid "
		       "BCD 0x00..0x99: %s (%d errors)\n",
		       e ? "FAIL" : "MATCH", e);
		bad += e;
		e = 0;
		for (i = 0; i < 36; i++)
			if (hex2ascii(i) != "0123456789abcdefghijklmnopqrstuvwxyz"[i])
				e++;
		printf("hex2ascii radix[2..36] domain (0..35): %s (%d errors)\n",
		       e ? "FAIL" : "MATCH", e);
		bad += e;
	}
	return (bad ? 1 : 0);
#elif MODE == 4
	bin2bcd_data = place_at_guard(v_bin2bcd_data, B2B_N);
	bcd2bin_data = place_at_guard(v_bcd2bin_data, B2D_N);
	hex2ascii_data = place_at_guard(v_hex2ascii_data,
					sizeof(v_hex2ascii_data));
	printf("fix.diff treatment under the same guard-page placement:\n");
	r = probe(pf_200); printf("bin2bcd_fixed(200) : %s (value=%u)\n",
				  r == 0 ? "in-bounds" : "SIGSEGV",
				  (unsigned)v_bin2bcd_data[200 % 100]);
	bad += !!r;
	r = probe(pf_255); printf("bin2bcd_fixed(255) : %s (value=%u)\n",
				  r == 0 ? "in-bounds" : "SIGSEGV",
				  (unsigned)v_bin2bcd_data[255 % 100]);
	bad += !!r;
	r = probe(pdf_ff);  printf("bcd2bin_fixed(0xff): %s (value=%u)\n",
				  r == 0 ? "in-bounds" : "SIGSEGV",
				  (unsigned)v_bcd2bin_data[0xff % B2D_N]);
	bad += !!r;
	printf("=> masked indices never leave the objects\n");
	return (bad ? 1 : 0);
#else
#error "compile with -DMODE=1|2|3|4"
#endif
}