/*
 * DF-5809 rig: netcred lifetime race demonstrator.
 *
 * vfs_export_lookup() (sys/kern/vfs_subr.c:2297) returns a struct netcred*
 * after releasing NE_LOCK.  Every VFS_CHECKEXP caller (ufs_vfsops.c:250,
 * tmpfs, hammer2, ...) then reads np->netc_exflags and keeps
 * &np->netc_anon across the whole NFS request.  vfs_export() with
 * MNT_DELEXPORT frees those netcreds (vfs_free_addrlist -> kfree).
 *
 * This module holds a looked-up netcred exactly the way the NFS server
 * does (across a sleep) while root userland hammers
 * mountctl(MOUNTCTL_SET_EXPORT, DELEXPORT|EXPORTED) with alternating
 * ex_flags markers.  If the held pointer's content changes under us, the
 * memory was freed/reused mid-hold: use-after-free by construction.
 *
 * sysctl debug.ncrace_run=1  start/stop the looker thread
 * sysctl debug.ncrace_stat   counters
 */
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/systm.h>
#include <sys/sysctl.h>
#include <sys/mount.h>
#include <sys/proc.h>
#include <sys/thread.h>
#include <sys/kthread.h>
#include <sys/socket.h>
#include <sys/malloc.h>
#include <sys/errno.h>
#include <netinet/in.h>
#include "tmpfs.h"

static struct thread *looker_td;
static volatile int run_flag;
static u_long n_lookups, n_holds, n_hits;

static int
find_mount_cb(struct mount *mp, void *data)
{
	struct mount **out = data;

	if (strcmp(mp->mnt_stat.f_mntonname, "/mnt/t") == 0) {
		*out = mp;
		return (1);
	}
	return (0);
}

static void
looker_thread(void *arg)
{
	struct mount *mp = arg;
	struct tmpfs_mount *tmp = (struct tmpfs_mount *)mp->mnt_data;
	struct netexport *nep = &tmp->tm_export;
	struct sockaddr_in sin;
	struct netcred *np;

	memset(&sin, 0, sizeof(sin));
	sin.sin_len = sizeof(sin);
	sin.sin_family = AF_INET;
	sin.sin_addr.s_addr = htonl(0x7f000001);

	kprintf("ncrace: looker started mp=%p nep=%p\n", mp, nep);

	while (run_flag) {
		np = vfs_export_lookup(mp, nep, (struct sockaddr *)&sin);
		n_lookups++;
		if (np && np != &nep->ne_defexported) {
			/*
			 * Exactly what the NFS server does: hold the
			 * returned netcred across the request.
			 */
			u_long e1 = np->netc_exflags;
			u_long e2;
			struct ucred *anon1 = &np->netc_anon;

			tsleep(&run_flag, 0, "ncrhold", 1);
			e2 = np->netc_exflags;
			n_holds++;
			if (e1 != e2 || anon1->cr_uid != np->netc_anon.cr_uid) {
				n_hits++;
				if (n_hits <= 10)
					kprintf("ncrace: UAF-use #%lu: "
						"np=%p exflags %lx->%lx "
						"anonuid %d->%d\n",
						n_hits, np, e1, e2,
						(int)anon1->cr_uid,
						(int)np->netc_anon.cr_uid);
			}
		} else {
			tsleep(&n_lookups, 0, "ncridle", 1);
		}
	}
	kprintf("ncrace: looker exiting: lookups=%lu holds=%lu hits=%lu\n",
		n_lookups, n_holds, n_hits);
	wakeup(&looker_td);
	kthread_exit();
}

static int
sysctl_ncrace_run(SYSCTL_HANDLER_ARGS)
{
	int error, val = run_flag;

	error = sysctl_handle_int(oidp, &val, 0, req);
	if (error || req->newptr == NULL)
		return (error);
	if (val == run_flag)
		return (0);
	if (val) {
		struct mount *mp = NULL;

		mountlist_scan(find_mount_cb, &mp,
			       MNTSCAN_FORWARD | MNTSCAN_NOBUSY);
		if (mp == NULL) {
			kprintf("ncrace: /mnt/t not mounted\n");
			return (ENOENT);
		}
		run_flag = 1;
		n_lookups = n_holds = n_hits = 0;
		error = kthread_create(looker_thread, mp, &looker_td,
				       "ncrace-looker");
		if (error) {
			run_flag = 0;
			return (error);
		}
	} else {
		run_flag = 0;
		wakeup(&run_flag);
		tsleep(&looker_td, 0, "ncrstop", hz * 5);
	}
	return (0);
}

static int
sysctl_ncrace_stat(SYSCTL_HANDLER_ARGS)
{
	char buf[128];
	int error;

	ksnprintf(buf, sizeof(buf),
		  "lookups=%lu holds=%lu hits=%lu", n_lookups, n_holds, n_hits);
	error = SYSCTL_OUT(req, buf, strlen(buf) + 1);
	return (error);
}

static SYSCTL_NODE(_debug, OID_AUTO, ncrace, CTLFLAG_RW, 0, "ncrace");
SYSCTL_PROC(_debug_ncrace, OID_AUTO, run, CTLTYPE_INT | CTLFLAG_RW,
	    0, 0, sysctl_ncrace_run, "I", "start/stop race looker");
SYSCTL_PROC(_debug_ncrace, OID_AUTO, stat, CTLTYPE_STRING | CTLFLAG_RD,
	    0, 0, sysctl_ncrace_stat, "A", "race counters");

static int
ncrace_modevent(module_t mod, int type, void *data)
{
	switch (type) {
	case MOD_LOAD:
		kprintf("ncrace: loaded\n");
		return (0);
	case MOD_UNLOAD:
		if (run_flag) {
			run_flag = 0;
			wakeup(&run_flag);
			tsleep(&looker_td, 0, "ncrstop", hz * 5);
		}
		kprintf("ncrace: unloaded\n");
		return (0);
	}
	return (EOPNOTSUPP);
}

static moduledata_t ncrace_mod = { "ncrace", ncrace_modevent, 0 };
DECLARE_MODULE(ncrace, ncrace_mod, SI_SUB_DRIVERS, SI_ORDER_ANY);
