/*
 * harness.c -- KLD harness for the pass-2 audit of
 * sys/libiconv/iconv_xlat.c (finding DF-5765: unsynchronized cspair
 * lifetime handoff in iconv_xlat_open/iconv_xlat_close).
 *
 * The production files iconv.c and iconv_xlat.c are compiled into this
 * module VERBATIM (copied from /usr/src/sys/libiconv by build.sh, md5s
 * recorded in setup.log).  This harness plays the role the VFS mount
 * path plays in production: it calls the production iconv_open() /
 * iconv_convstr() / iconv_close() exports in a loop on the SAME charset
 * pair that the production kern.iconv.add sysctl (driven from userland
 * by hammer(8), as root) keeps registering and -- on its copyin error
 * path (iconv.c:429-430 -> bad: iconv_unregister_cspair at :440) --
 * UNCONDITIONALLY freeing.
 *
 * The race (all production lines):
 *
 *   racer thread                          sysctl kern.iconv.add thread
 *   -----------------                      ---------------------------
 *   iconv_open(to,from)                    iconv_sysctl_add()
 *     iconv_lookupcs(to,from,&csp)           iconv_register_cspair()   [pair now visible]
 *       [WALKS iconv_cslist WITH NO LOCK]    csp->cp_data = kmalloc(256)
 *     -> finds the just-added pair           copyin(ia_data=0x1000) -> EFAULT
 *     ICONV_CONVERTER_OPEN                   bad:
 *       -> iconv_xlat_open                     iconv_unregister_cspair(csp)
 *            kobj_create(M_WAITOK)  <-- SLEEPS / wide window
 *                                              TAILQ_REMOVE + kfree(csp)
 *                                              kfree(csp->cp_data)
 *            dp->d_table = csp->cp_data   [DANGLING]
 *            csp->cp_refcount++           [WRITE INTO FREED MEMORY]
 *            return "success"
 *   iconv_convstr(h, ...)                    [READS FREED d_table]
 *   iconv_close(h)                           [WRITES FREED d_csp->refcount--]
 *
 * iconv_open()/iconv_close() never take iconv_lock (iconv.c:233-273);
 * iconv_unregister_cspair() runs under it (iconv.c:420,440-441) but the
 * refcount claim at iconv_xlat.c:63 cannot see that.  Every interleaving
 * above yields a live converter handle owning a freed cspair + freed
 * translation table.
 *
 * Sysctl interface (root):
 *
 *   hw.xlatrace_start = 1   spawn one racer thread per CPU
 *   hw.xlatrace_stop  = 1   stop racers, print stats
 *   hw.xlatrace_stats      print stats (attempts/opens/convs/closes/
 *                           dangling handoffs observed)
 *
 * Passive probe (harness-only, does NOT modify production code):
 * immediately after a successful production iconv_open(), read the
 * refcount of the pair the handle claims.  A correct open holds one
 * counted reference, so it must read >= 1.  A read of <= 0 proves the
 * pair was freed (and possibly reused) under the live handle.
 */

#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/malloc.h>
#include <sys/module.h>
#include <sys/proc.h>
#include <sys/systm.h>
#include <sys/sysctl.h>
#include <sys/thread.h>
#include <sys/thread2.h>
#include <sys/types.h>
#include <sys/iconv.h>

#define	XR_TO	"zz9to"
#define	XR_FROM	"zz9from"
#define	XR_INPUT	"filename-bytes-0123456789"

/*
 * Mirror of the production struct iconv_xlat (iconv_xlat.c:48-52).
 * Identical layout: same compilation unit flags, same KOBJ_FIELDS
 * expansion from <sys/kobj.h>.
 */
struct iconv_xlat_mirror {
	KOBJ_FIELDS;
	u_char		*d_table;
	struct iconv_cspair *d_csp;
};

static volatile int	g_run;
static int		g_nthreads;
static struct thread	*g_td[16];
static volatile long	g_attempts, g_opens, g_convs, g_closes;
static volatile long	g_enoent, g_dangling, g_stopped;

static void
xr_racer(void *arg)
{
	int cpu = (int)(intptr_t)arg;
	void *h;
	char out[64];
	int error, rc, pacing = 0;

	kprintf("[xlatrace] racer on cpu%d up (%s:%s)\n", cpu, XR_TO, XR_FROM);

	while (g_run) {
		error = iconv_open(XR_TO, XR_FROM, &h);		/* production */
		atomic_add_long(&g_attempts, 1);
		if (error == 0) {
			struct iconv_xlat_mirror *xm = h;
			struct iconv_cspair *csp = xm->d_csp;

			atomic_add_long(&g_opens, 1);
			/*
			 * PROBE (read-only): we hold a counted reference
			 * from a correct open, so refcount must be >= 1.
			 */
			rc = *(volatile int *)&csp->cp_refcount;
			if (rc <= 0) {
				long n = atomic_fetchadd_long(&g_dangling, 1);

				if (n < 40)
					kprintf("[xlatrace] PROOF: live handle "
					    "d_csp=%p refcount=%d -- pair "
					    "freed/reused under the open "
					    "(d_table=%p)\n",
					    csp, rc, xm->d_table);
			}
			iconv_convstr(h, out, XR_INPUT);	/* production */
			atomic_add_long(&g_convs, 1);
			iconv_close(h);				/* production */
			atomic_add_long(&g_closes, 1);
		} else {
			atomic_add_long(&g_enoent, 1);
		}
		/*
		 * Pace: block one tick every 1024 attempts so userland
		 * (the sysctl hammer, sshd) gets CPU.  A pure-spin kernel
		 * thread starves user LWPs on this scheduler.
		 */
		if ((++pacing & 1023) == 0)
			tsleep(&g_run, 0, "xryld", 1);
	}
	atomic_add_long(&g_stopped, 1);
	wakeup(&g_stopped);
	kprintf("[xlatrace] racer on cpu%d done\n", cpu);
	lwkt_exit();
}

static int
xr_start(void)
{
	int i, err;

	if (g_run)
		return (EBUSY);
	g_stopped = 0;
	g_run = 1;
	g_nthreads = 0;
	/* leave CPU 0 free for userland/ssh/softint */
	for (i = 1; i < ncpus && i < 16; i++) {
		err = lwkt_create(xr_racer, (void *)(intptr_t)i,
		    &g_td[i], NULL, TDF_NOSTART, i, "xlrace%d", i);
		if (err)
			kprintf("[xlatrace] lwkt_create cpu%d -> %d\n", i, err);
		else {
			lwkt_schedule(g_td[i]);
			g_nthreads++;
		}
	}
	kprintf("[xlatrace] %d racers started\n", g_nthreads);
	return (0);
}

static int
xr_stop(void)
{
	int i;

	if (!g_run)
		return (0);
	g_run = 0;
	i = 0;
	while (g_stopped < g_nthreads && i++ < 200)
		tsleep(&g_stopped, 0, "xrstop", hz / 20);
	kprintf("[xlatrace] stats: attempts=%ld opens=%ld convs=%ld "
	    "closes=%ld enoent=%ld DANGLING=%ld stopped=%ld/%d\n",
	    g_attempts, g_opens, g_convs, g_closes, g_enoent,
	    g_dangling, g_stopped, g_nthreads);
	return (0);
}

static int
xr_stats_sysctl(SYSCTL_HANDLER_ARGS)
{
	char buf[256];
	int error, len;

	len = ksnprintf(buf, sizeof(buf),
	    "attempts=%ld opens=%ld convs=%ld closes=%ld enoent=%ld "
	    "DANGLING=%ld running=%d\n",
	    g_attempts, g_opens, g_convs, g_closes, g_enoent, g_dangling,
	    g_run);
	error = sysctl_handle_string(oidp, buf, sizeof(buf), req);
	return (error);
}

static int
xr_ctl_sysctl(SYSCTL_HANDLER_ARGS)
{
	int error, v;

	v = 0;
	error = sysctl_handle_int(oidp, &v, 0, req);
	if (error || req->newptr == NULL)
		return (error);
	if (v == 1)
		error = xr_start();
	else if (v == 0)
		error = xr_stop();
	return (error);
}

SYSCTL_NODE(_hw, OID_AUTO, xlatrace, CTLFLAG_RW, 0, "xlat race harness");
SYSCTL_PROC(_hw_xlatrace, OID_AUTO, ctl, CTLTYPE_INT | CTLFLAG_RW,
	    0, 0, xr_ctl_sysctl, "I", "1=start racers, 0=stop");
SYSCTL_PROC(_hw_xlatrace, OID_AUTO, stats, CTLTYPE_STRING | CTLFLAG_RD,
	    0, 0, xr_stats_sysctl, "A", "racer statistics");

static int
xlatrace_modevent(module_t mod, int type, void *data)
{
	switch (type) {
	case MOD_LOAD:
		g_run = 0;
		kprintf("[xlatrace] harness loaded: production iconv.c + "
		    "iconv_xlat.c compiled in verbatim\n");
		break;
	case MOD_UNLOAD:
		if (g_run) {
			kprintf("[xlatrace] busy\n");
			return (EBUSY);
		}
		kprintf("[xlatrace] unloaded\n");
		break;
	default:
		return (EOPNOTSUPP);
	}
	return (0);
}

static moduledata_t xlatrace_mod = { "xlatrace_harness", xlatrace_modevent, NULL };
DECLARE_MODULE(xlatrace_harness, xlatrace_mod, SI_SUB_DRIVERS, SI_ORDER_ANY);
MODULE_VERSION(xlatrace_harness, 1);
