/*
 * DF-5788 rigger v2 - maximize mount-free vs walk-deref interleavings.
 *
 * Strategy (per the VERDICT analysis): the walk-phase UAF needs a
 * kern_getcwd() walk that dereferences nch.mount AFTER that mount was
 * freed by umount -f.  Freed mounts keep their poison (deterministic
 * values) until slab reuse, so ANY walk that touches a freed-but-not-
 * yet-reused mount trips the detection rig.
 *
 * Threads:
 *   walker  - hammer getcwd() forever (walks the deep chain in T2 while
 *             the fd points deep).
 *   flapper - flip chdir() deep<->outside at max rate.  Dropping the fd
 *             pin is what allows umount to complete; the walker thread
 *             races this swap (unlocked fd_ncdir read).
 *   churn   - umount -f T2; remount; recreate deep chain; repeat as fast
 *             as possible (clean umounts take ~ms each).
 */
#include <sys/param.h>
#include <sys/mount.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <errno.h>
#include <time.h>

static char g_base[256];
static char g_deep[1024];
static volatile int g_stop;
static volatile long n_getcwd, n_enoent, n_err, n_chdir_ok, n_cycles;

#define NMID 3
static const char *mid[NMID] = {
	"mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm"
	"mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm",	/* 110 */
	"nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn"
	"nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn",
	"oooooooooooooooooooooooooooooooooooooooooooooooooooo"
	"oooooooooooooooooooooooooooooooooooooooooooooooooooo",
};

static void mkdir_p(const char *p);

static int
setup_mounts(void)
{
	char p[1024];
	int i;

	snprintf(p, sizeof(p), "%s/m1", g_base);
	mkdir_p(p);
	if (mount("tmpfs", p, 0, NULL) && errno != EBUSY)
		return -1;
	snprintf(g_deep, sizeof(g_deep), "%s/m1", g_base);
	for (i = 0; i < NMID; i++) {
		strlcat(g_deep, "/", sizeof(g_deep));
		strlcat(g_deep, mid[i], sizeof(g_deep));
		mkdir_p(g_deep);
	}
	mkdir_p(g_deep);
	return 0;
}

static void
teardown_mounts(void)
{
	char p[1024];

	snprintf(p, sizeof(p), "%s/m1", g_base);
	unmount(p, MNT_FORCE);
}

static void
mkdir_p(const char *p)
{
	char tmp[1024];
	char *q;

	snprintf(tmp, sizeof(tmp), "%s", p);
	for (q = tmp + 1; *q; q++) {
		if (*q == '/') {
			*q = 0;
			mkdir(tmp, 0777);
			*q = '/';
		}
	}
	if (mkdir(tmp, 0777) == 0 || errno == EEXIST)
		;
}

static void *
thr_walker(void *arg)
{
	char buf[MAXPATHLEN];

	while (!g_stop) {
		if (getcwd(buf, sizeof(buf)) == NULL) {
			if (errno == ENOENT)
				n_enoent++;
			else
				n_err++;
		}
		n_getcwd++;
	}
	return NULL;
}

static void *
thr_flapper(void *arg)
{
	char out[256];

	snprintf(out, sizeof(out), "%s/out", g_base);
	mkdir_p(out);
	while (!g_stop) {
		if (chdir(g_deep) == 0)
			n_chdir_ok++;
		chdir(out);
	}
	return NULL;
}

static void *
thr_churn(void *arg)
{
	while (!g_stop) {
		teardown_mounts();
		if (setup_mounts() == 0)
			n_cycles++;
	}
	return NULL;
}

int
main(int argc, char **argv)
{
	pthread_t t[3];
	int secs = 60, i;

	if (argc > 1)
		snprintf(g_base, sizeof(g_base), "%s", argv[1]);
	else
		snprintf(g_base, sizeof(g_base), "/tmp/df5788");
	if (argc > 2)
		secs = atoi(argv[2]);

	mkdir_p(g_base);
	if (setup_mounts() != 0) {
		perror("setup_mounts");
		return 2;
	}
	printf("base=%s deep=%.60s...\n", g_base, g_deep);
	g_stop = 0;
	for (i = 0; i < 3; i++) {
		switch (i) {
		case 0: pthread_create(&t[i], NULL, thr_walker, NULL); break;
		case 1: pthread_create(&t[i], NULL, thr_flapper, NULL); break;
		case 2: pthread_create(&t[i], NULL, thr_churn, NULL); break;
		}
	}
	sleep(secs);
	g_stop = 1;
	for (i = 0; i < 3; i++)
		pthread_join(t[i], NULL);
	printf("getcwd=%ld enoent=%ld err=%ld chdir_ok=%ld cycles=%ld\n",
	       n_getcwd, n_enoent, n_err, n_chdir_ok, n_cycles);
	teardown_mounts();
	printf("DONE rc=0\n");
	return 0;
}
