⬢ DragonFlyBSD Kernel Audit
DF-3160 / fifo_open_race.c
← back to finding ↓ download raw
/*
 * DF-3160 PoC attempt: lost-wakeup race in fifo_open()'s reader/writer
 * rendezvous (sys/vfs/fifofs/fifo_vnops.c:295-335).
 *
 * The writer's path: check `fi_readers == 0` under fifo_lock, RELEASE
 * fifo_lock, RELEASE vn lock, tsleep("fifoow").  A reader arriving in the
 * gap between the unlock and the tsleep-enqueue sends wakeup(&fi_writers)
 * to nobody - lost.  The writer then sleeps until the NEXT readers 0->1
 * transition, i.e. stuck for however long the reader keeps its fd open.
 *
 * Writer (this mode "w"): blocking open(O_WRONLY) in a loop, measure
 *   elapsed.  Normal return: microseconds (a reader is open ~100% of the
 *   time; the writer can only even *enter* tsleep during the reader's
 *   close/reopen gap, and is then rescued by the very next 0->1 wakeup
 *   unless the wakeup is lost).  elapsed >> 1ms == lost wakeup.
 * Reader (mode "r"): open(O_RDONLY|O_NONBLOCK)/close at max rate,
 *   holding the fd 1000ms on every 8th cycle (to let a lost wakeup
 *   become visible as a slow writer open).
 *
 * usage: ./fifo_open_race r <path> | ./fifo_open_race w <path> [seconds]
 */
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>

static double
now(void)
{
	struct timespec ts;

	clock_gettime(CLOCK_MONOTONIC, &ts);
	return (ts.tv_sec + ts.tv_nsec / 1e9);
}

int
main(int argc, char **argv)
{
	const char *path;
	int fd, i, hits = 0, iters = 0;
	double t0, el, maxel = 0;
	int runtime = argc > 3 ? atoi(argv[3]) : 60;
	if (argc < 3) {
		fprintf(stderr, "usage: %s r|w <fifo> [seconds]\n", argv[0]);
		exit(2);
	}
	path = argv[2];

	if (argv[1][0] == 'r') {
		int holdms = argc > 3 ? atoi(argv[3]) : 1000;
		int every = argc > 4 ? atoi(argv[4]) : 8;

		i = 0;
		for (;;) {
			fd = open(path, O_RDONLY | O_NONBLOCK);
			if (fd < 0) {
				if (errno == ENXIO)
					continue;
				perror("reader open");
				exit(1);
			}
			if (++i % every == 0)
				usleep(holdms * 1000);	/* hold fd open */
			close(fd);
		}
		/* not reached */
	}

	/* writer */
	signal(SIGPIPE, SIG_IGN);
	t0 = now();
	while (now() - t0 < runtime) {
		double s = now();

		fd = open(path, O_WRONLY);
		el = now() - s;
		if (fd < 0) {
			if (errno == EINTR)
				continue;
			perror("writer open");
			exit(1);
		}
		close(fd);
		++iters;
		if (el > maxel)
			maxel = el;
		if (el > 0.050) {
			printf("LOST-WAKEUP-SUSPECT: open took %.1f ms "
			       "(iter %d)\n", el * 1000, iters);
			fflush(stdout);
			++hits;
		}
	}
	printf("writer done: %d opens, %d slow (>50ms), max %.1f ms\n",
	       iters, hits, maxel * 1000);
	return (hits ? 0 : 1);
}