/*
 * dfh.c -- DF-2225 / DF-2235 / DF-2238 / DF-2239 / DF-2241 / DF-2247 /
 * DF-2248 / DF-2249 verification harness (guest-only test kernel file).
 *
 * Every test drives the REAL kernel code under audit:
 *   - DF-2225: real prop_dictionary_create()/prop_dictionary_set() first-use
 *     path against the real _PROP_ONCE_RUN macro state (hooks in
 *     prop_dictionary.c installed by harness.diff).
 *   - DF-2235: real iconv_open("UTF-8","UCS-2LE") + iconv_conv() through
 *     the kernel-compiled UCS converter; trigger uses a guard page so the
 *     1-byte OOB read of the source buffer faults visibly.
 *   - DF-2238/2239: real db_readline() with a fabricated console input
 *     stream (hook in db_input.c installed by harness.diff) and canary
 *     buffers.
 *   - DF-2241: real db_read_line()+db_expression() (lexer included), plus
 *     a real DDB session entered via Debugger() executing a registered
 *     DDB command, to demonstrate the db_active leak + wedge.
 *   - DF-2247/2248/2249: real iconv_open() on pairs armed through the real
 *     kern.iconv.add sysctl by the userland helper (dfarm), then the real
 *     iconv_conv()/iconv_convchr() KPI.
 *
 * All tests are invoked as root via sysctls:
 *   sysctl debug.df2225=<mode>   0=state 1=window-panic 2=race(N rounds)
 *   sysctl debug.df2235=<mode>   0=control 1=guard-page trigger
 *   sysctl debug.df2238=<mode>   0=126c 1=128c 2=127c  (chars + Enter)
 *   sysctl debug.df2239=<mode>   0=pre-X 1=pre-\n     (CTRL(n) first key)
 *   sysctl debug.df2241=<mode>   0=1+1 1=idiv 2=DDB wedge demo
 *   sysctl debug.df2247=<mode>   0=ctrl 1=NULL-table 2=undersized leak
 *   sysctl debug.df2248=<mode>   0=ctrl 1=0x80000000 2=0x100000000
 *   sysctl debug.df2249=<mode>   0=ctrl 1=OOBread 2=OOBwrite 3=both
 */

#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/systm.h>
#include <sys/sysctl.h>
#include <sys/malloc.h>
#include <sys/cons.h>
#include <sys/thread.h>
#include <opt_ddb.h>
#include <sys/kthread.h>
#include <sys/iconv.h>
#include <sys/time.h>
#include <sys/thread2.h>
#include <libprop/proplib.h>

#include <vm/vm_map.h>
#include <vm/vm_extern.h>
#include <vm/pmap.h>

#include <machine/cpufunc.h>
#include <machine/setjmp.h>

#include <ddb/ddb.h>

/* provided by harness.diff (db_input.c) */
extern int (*df_dbinput_getc)(void);
/* provided by harness.diff (prop_dictionary.c) */
extern int df2225_once_val(void);
extern void df2225_state_reset(void);
extern void df2225_arm_window(void);
extern void df2225_complete_init(void);
/* ddb globals not in ddb.h */
extern jmp_buf db_jmpbuf;
extern int db_read_line(void);

MALLOC_DEFINE(M_DFH, "dfh", "DF audit harness");

#define	DFH_PREFIX	"dfh: "

static void dfh_hex(const char *tag, const u_char *p, int n);

/* ------------------------------------------------------------------ */
/* fabricated console input stream                                      */
/* ------------------------------------------------------------------ */

#define	DF_FEEDQ_MAX	6
#define	DF_LINE_MAX	160

static char	df_feedq[DF_FEEDQ_MAX][DF_LINE_MAX];
static int	df_feedq_n, df_feedq_idx, df_feedq_pos;

static int
df_feed_getc(void)
{
	const char *s;

	while (df_feedq_idx < df_feedq_n) {
		s = df_feedq[df_feedq_idx];
		if (df_feedq_pos < (int)strlen(s))
			return ((u_char)s[df_feedq_pos++]);
		/* line exhausted, advance to the next queued line */
		df_feedq_idx++;
		df_feedq_pos = 0;
	}
	/* exhausted: behave like the real console (block for input) */
	return (cngetc());
}

static void
df_feed_clear(void)
{

	df_feedq_n = df_feedq_idx = df_feedq_pos = 0;
}

static void
df_feed_add(const char *s)
{

	KKASSERT(df_feedq_n < DF_FEEDQ_MAX);
	strlcpy(df_feedq[df_feedq_n++], s, DF_LINE_MAX);
}

/* ------------------------------------------------------------------ */
/* DF-2225 -- _PROP_ONCE_RUN races a concurrent first user               */
/* ------------------------------------------------------------------ */

static void
df2225_completer(void *arg __unused)
{

	/*
	 * Emulates the (fixed) winner finishing f() a little later than
	 * the cmpset: on the stock macro the loser never waits and hits
	 * the uninitialized tree long before this runs.
	 */
	tsleep(df2225_completer, 0, "df2225c", hz / 4);
	df2225_complete_init();
	kprintf(DFH_PREFIX "DF2225: delayed init completed (once=%d)\n",
	    df2225_once_val());
	kthread_exit();
}

static void
df2225_mode1(void)
{
	prop_dictionary_t pd;
	prop_object_t b;
	struct thread *td;

	kprintf(DFH_PREFIX "DF2225: arming once-window (flag=1, init NOT run)\n");
	df2225_arm_window();
	kprintf(DFH_PREFIX "DF2225: once=%d; spawning delayed completer\n",
	    df2225_once_val());
	kthread_create(df2225_completer, NULL, &td, "df2225c");

	/*
	 * This is what any concurrent first user does: real public API,
	 * real _PROP_ONCE_RUN loser path.
	 */
	pd = prop_dictionary_create();
	b = prop_bool_create(1);
	prop_dictionary_set(pd, "df2225-window-key", b);
	prop_object_release(b);
	prop_object_release(pd);
	kprintf(DFH_PREFIX "DF2225: mode1 SURVIVED (loser waited for init)\n");
}

static volatile int df2225_go;		/* 0=idle 1=gating 2=released */
static volatile int df2225_ready;
static volatile int df2225_done;
static int df2225_racers;

static void
df2225_racer(void *arg __unused)
{
	prop_dictionary_t pd;
	prop_object_t b;
	char key[32];

	for (;;) {
		while (df2225_go == 0)
			tsleep(&df2225_go, 0, "df2225w", 1);
		/* gate: converge all racers, then launch simultaneously */
		atomic_add_int(&df2225_ready, 1);
		while (df2225_go == 1)
			cpu_pause();
		pd = prop_dictionary_create();
		b = prop_bool_create(1);
		ksnprintf(key, sizeof(key), "df2225-race-key-%ld",
		    (long)(intptr_t)curthread);
		prop_dictionary_set(pd, key, b);
		prop_object_release(b);
		prop_object_release(pd);
		atomic_add_int(&df2225_done, 1);
		while (df2225_go != 0)
			tsleep(&df2225_go, 0, "df2225w2", 1);
	}
}

static int
df2225_race(int rounds)
{
	int i, r, ncpus_avail;

	if (df2225_racers == 0) {
		ncpus_avail = ncpus ? ncpus : 1;
		if (ncpus_avail > 6)
			ncpus_avail = 6;
		for (i = 0; i < ncpus_avail; i++) {
			struct thread *td;
			int err = kthread_create_cpu(df2225_racer, NULL, &td,
			    i, "df2225r%d", i);
			if (err)
				kprintf(DFH_PREFIX "DF2225: kthread cpu%d "
				    "FAILED err=%d\n", i, err);
			else
				df2225_racers++;
		}
		kprintf(DFH_PREFIX "DF2225: %d racer threads on %d cpus\n",
		    df2225_racers, ncpus_avail);
		tsleep(df2225_race, 0, "df2225s", hz / 2);
	}

	kprintf(DFH_PREFIX "DF2225: racing %d rounds x %d threads "
	    "(panic = window hit)\n", rounds, df2225_racers);
	for (r = 1; r <= rounds; r++) {
		int spins;

		/* fresh boot-like state: once=0, tree/mutex zeroed */
		df2225_state_reset();
		atomic_store_int(&df2225_done, 0);
		atomic_store_int(&df2225_ready, 0);
		atomic_store_int(&df2225_go, 1);
		spins = 0;
		while (df2225_ready != df2225_racers) {
			cpu_pause();
			if ((++spins & 0xfffff) == 0)
				lwkt_yield();
			if (spins > 800000000) {
				kprintf(DFH_PREFIX "DF2225: round %d STUCK "
				    "at gate (ready=%d/%d)\n", r,
				    df2225_ready, df2225_racers);
				return (0);
			}
		}
		atomic_store_int(&df2225_go, 2);	/* launch */
		spins = 0;
		while (df2225_done != df2225_racers) {
			cpu_pause();
			if ((++spins & 0xfffff) == 0)
				lwkt_yield();
			if (spins > 400000000) {
				kprintf(DFH_PREFIX "DF2225: round %d STUCK "
				    "(done=%d/%d) -- a racer blocked inside "
				    "the window (lock state torn by "
				    "lockinit)\n", r, df2225_done,
				    df2225_racers);
				return (0);
			}
		}
		atomic_store_int(&df2225_go, 0);
		if (r == 1 || r == 2 || r == 5 || r == 10 || r == 25 ||
		    r == 50 || (r % 100) == 0)
			kprintf(DFH_PREFIX "DF2225: round %d done "
			    "(once=%d)\n", r, df2225_once_val());
		tsleep(&df2225_done, 0, "df2225m", 1);
	}
	kprintf(DFH_PREFIX "DF2225: race finished %d rounds WITHOUT hitting "
	    "the window\n", rounds);
	return (0);
}

static int
df2225_sysctl(SYSCTL_HANDLER_ARGS)
{
	int mode = 0, error;

	error = sysctl_handle_int(oidp, &mode, 0, req);
	if (error != 0 || req->newptr == NULL)
		return (error);
	switch (mode) {
	case 0:
		kprintf(DFH_PREFIX "DF2225: _prop_dict_init_once=%d "
		    "(0=unused, 1=stock-done, 2=fixed-done)\n",
		    df2225_once_val());
		if (df2225_once_val() == 0) {
			/* force first use single-threaded, then re-read */
			prop_dictionary_t pd = prop_dictionary_create();
			prop_object_t b = prop_bool_create(1);
			prop_dictionary_set(pd, "df2225-probe", b);
			prop_object_release(b);
			prop_object_release(pd);
			kprintf(DFH_PREFIX "DF2225: after first use once=%d\n",
			    df2225_once_val());
		}
		break;
	case 1:
		df2225_mode1();
		break;
	default:
		df2225_race(mode);
		break;
	}
	return (0);
}
SYSCTL_PROC(_debug, OID_AUTO, df2225, CTLTYPE_INT | CTLFLAG_RW,
	    0, 0, df2225_sysctl, "I", "DF-2225 once-race tests");

/* ------------------------------------------------------------------ */
/* DF-2235 -- iconv_ucs_conv reads 2 bytes before the ir<inlen check    */
/* ------------------------------------------------------------------ */

static void
df2235_run(int mode)
{
	void *h = NULL;
	const char *in;
	char *out;
	char outbuf[64];
	size_t ilen, olen;
	vm_offset_t kva __unused;
	int ret;

	ret = iconv_open("UTF-8", "UCS-2LE", &h);
	if (ret != 0 || h == NULL) {
		kprintf(DFH_PREFIX "DF2235: iconv_open FAILED ret=%d "
		    "(arm the pair first: dfarm ucs)\n", ret);
		return;
	}

	if (mode == 0) {
		in = "AB";
		ilen = 2;
		out = outbuf;
		olen = sizeof(outbuf);
		ret = iconv_conv(h, &in, &ilen, &out, &olen);
		kprintf(DFH_PREFIX "DF2235: control ret=%d consumed=%zu "
		    "produced=%zu\n", ret, (size_t)(2 - ilen),
		    sizeof(outbuf) - olen);
		dfh_hex("DF2235 control out", (u_char *)outbuf,
		    sizeof(outbuf) - olen);
	} else {
		/*
		 * The OOB read must land on kernel virtual memory that is
		 * NOT backed by any vm_map entry, otherwise the kernel-mode
		 * page fault would be silently re-faulted by vm_fault().
		 * Allocate two contiguous pages, then free ONLY the second
		 * one (vm_map splits the entry, pmap mapping removed): the
		 * 1-byte UCS-2 'string' sits at the last byte of page 1 and
		 * a read of src[1] (= first byte of page 2) now faults with
		 * no map entry -> Fatal trap 12.
		 */
		vm_offset_t kva2;
		int tries;

		kva2 = 0;
		for (tries = 0; tries < 8; tries++) {
			vm_offset_t k = kmem_alloc(kernel_map,
			    2 * PAGE_SIZE, VM_SUBSYS_KMALLOC);
			if (k == 0)
				break;
			memset((char *)k, 0x41, PAGE_SIZE);	/* fault in A */
			kmem_free(kernel_map, k + PAGE_SIZE, PAGE_SIZE);
			if (pmap_kextract(k + PAGE_SIZE) == 0) {
				kva2 = k;
				break;
			}
			/* second page still mapped somehow; retry elsewhere */
			kmem_free(kernel_map, k, PAGE_SIZE);
		}
		if (kva2 == 0) {
			kprintf(DFH_PREFIX "DF2235: could not build guard\n");
			return;
		}
		((char *)kva2)[PAGE_SIZE - 1] = 0x41;	/* 'A' */
		cpu_invltlb();

		kprintf(DFH_PREFIX "DF2235: TRIGGER odd UCS-2LE input "
		    "(ilen=1, unmapped page at %jx)\n",
		    (uintmax_t)(kva2 + PAGE_SIZE));
		in = (const char *)(kva2 + PAGE_SIZE - 1);
		ilen = 1;
		out = outbuf;
		olen = sizeof(outbuf);
		ret = iconv_conv(h, &in, &ilen, &out, &olen);
		/* stock: Fatal trap 12 above; fixed: clean -1 */
		kprintf(DFH_PREFIX "DF2235: TRIGGER SURVIVED ret=%d "
		    "consumed=%zu produced=%zu\n", ret, (size_t)(1 - ilen),
		    sizeof(outbuf) - olen);
		kmem_free(kernel_map, kva2, PAGE_SIZE);
	}
	iconv_close(h);
}

static int
df2235_sysctl(SYSCTL_HANDLER_ARGS)
{
	int mode = 0, error;

	error = sysctl_handle_int(oidp, &mode, 0, req);
	if (error != 0 || req->newptr == NULL)
		return (error);
	df2235_run(mode);
	return (0);
}
SYSCTL_PROC(_debug, OID_AUTO, df2235, CTLTYPE_INT | CTLFLAG_RW,
	    0, 0, df2235_sysctl, "I", "DF-2235 ucs OOB read tests");

/* ------------------------------------------------------------------ */
/* DF-2238 / DF-2239 -- db_readline() buffer handling                   */
/* ------------------------------------------------------------------ */

static void
df2238_run(int mode)
{
	char *buf;
	char line[DF_LINE_MAX];
	int lsize = 128, n, rv, i, corrupt;

	/*
	 * 160-byte buffer, line area [0..127], canary 'C' fill at
	 * [128..159].  A line of n printable chars + Enter writes '\r'
	 * at [n] and the NUL terminator at [n+1] -- both OOB when
	 * n >= lsize-1.
	 */
	buf = kmalloc(160, M_DFH, M_WAITOK | M_ZERO);
	memset(buf, 'C', 160);

	n = (mode == 0) ? 126 : (mode == 2 ? 127 : 128);
	memset(line, 'A', n);
	line[n] = '\r';
	line[n + 1] = '\0';

	df_feed_clear();
	df_feed_add(line);
	df_dbinput_getc = df_feed_getc;
	rv = db_readline(buf, lsize);
	df_dbinput_getc = NULL;

	kprintf(DFH_PREFIX "DF2238: mode %d (%d chars + Enter) rv=%d\n",
	    mode, n, rv);
	kprintf(DFH_PREFIX "DF2238: [124..133]:");
	for (i = 124; i <= 133; i++)
		kprintf(" %02x", (u_char)buf[i]);
	kprintf("\n");
	corrupt = 0;
	for (i = 128; i < 160; i++) {
		if (buf[i] != 'C') {
			corrupt++;
			kprintf(DFH_PREFIX "DF2238: OOB WRITE at [%d] = %02x "
			    "(expected canary 43)\n", i, (u_char)buf[i]);
		}
	}
	kprintf(DFH_PREFIX "DF2238: canary [128..159] corrupted bytes = %d"
	    "  <<<< OOB-write proof\n", corrupt);
	kfree(buf, M_DFH);
}

static int
df2238_sysctl(SYSCTL_HANDLER_ARGS)
{
	int mode = 0, error;

	error = sysctl_handle_int(oidp, &mode, 0, req);
	if (error != 0 || req->newptr == NULL)
		return (error);
	df2238_run(mode);
	return (0);
}
SYSCTL_PROC(_debug, OID_AUTO, df2238, CTLTYPE_INT | CTLFLAG_RW,
	    0, 0, df2238_sysctl, "I", "DF-2238 db_readline overflow tests");

static void
df2239_run(int mode)
{
	char *region, *lstart;
	u_char pre;
	int lsize = 32, rv, i;

	/*
	 * 64-byte region, line buffer at [16..47], the byte immediately
	 * before the buffer is region[15].  First keypress CTRL(n) with
	 * no history takes the else branch, hist_redraw computes
	 * db_le = index(lstart, '\0') == lstart and then reads db_le[-1]
	 * == region[15] (OOB read); if that byte is \r or \n it is
	 * overwritten with NUL (OOB write).
	 */
	region = kmalloc(64, M_DFH, M_WAITOK | M_ZERO);
	memset(region, 'P', 64);
	lstart = region + 16;
	pre = (mode == 1) ? '\n' : 'X';
	region[15] = pre;

	df_feed_clear();
	df_feed_add("\x0e\r");		/* CTRL(n), then Enter */
	df_dbinput_getc = df_feed_getc;
	rv = db_readline(lstart, lsize);
	df_dbinput_getc = NULL;

	kprintf(DFH_PREFIX "DF2239: mode %d pre-set=%02x pre-now=%02x rv=%d\n",
	    mode, pre, (u_char)region[15], rv);
	kprintf(DFH_PREFIX "DF2239: [8..24]:");
	for (i = 8; i <= 24; i++)
		kprintf(" %02x", (u_char)region[i]);
	kprintf("\n");
	if (region[15] != pre)
		kprintf(DFH_PREFIX "DF2239: OOB WRITE before buffer: "
		    "region[15] %02x -> %02x  <<<< proof\n",
		    pre, (u_char)region[15]);
	else
		kprintf(DFH_PREFIX "DF2239: region[15] unchanged (%02x); "
		    "the byte WAS read (write fires only for \\r/\\n)\n",
		    (u_char)region[15]);
	kfree(region, M_DFH);
}

static int
df2239_sysctl(SYSCTL_HANDLER_ARGS)
{
	int mode = 0, error;

	error = sysctl_handle_int(oidp, &mode, 0, req);
	if (error != 0 || req->newptr == NULL)
		return (error);
	df2239_run(mode);
	return (0);
}
SYSCTL_PROC(_debug, OID_AUTO, df2239, CTLTYPE_INT | CTLFLAG_RW,
	    0, 0, df2239_sysctl, "I", "DF-2239 db_readline pre-buffer tests");

/* ------------------------------------------------------------------ */
/* DF-2241 -- LONG_MIN / -1 raises #DE inside db_mult_expr              */
/* ------------------------------------------------------------------ */

#define	DF2241_EXPR	"0x8000000000000000%(0-1)\r"

static int
df2241_expr_eval(void)
{
	db_expr_t val = 0;
	int ok;

	db_read_line();
	ok = db_expression(&val);
	if (ok)
		kprintf(DFH_PREFIX "DF2241: expression value = %ld\n",
		    (long)val);
	else
		kprintf(DFH_PREFIX "DF2241: expression parse failed\n");
	return (ok);
}

static void
df2241_mode0(void)
{

	df_feed_clear();
	df_feed_add("1+1\r");
	df_dbinput_getc = df_feed_getc;
	kprintf(DFH_PREFIX "DF2241: control 1+1 ->\n");
	df2241_expr_eval();
	df_dbinput_getc = NULL;
}

static void
df2241_mode1(void)
{

	kprintf(DFH_PREFIX "DF2241: evaluating \"%s\" OUTSIDE DDB "
	    "(expect #DE trap -> panic on stock)\n", DF2241_EXPR);
	if (setjmp(db_jmpbuf) != 0) {
		/* db_error() longjmp target: reached only when the divide
		 * is rejected instead of trapping */
		kprintf(DFH_PREFIX "DF2241: db_error caught via longjmp "
		    "(divide guarded) -- FIXED behavior\n");
		return;
	}
	df_feed_clear();
	df_feed_add(DF2241_EXPR);
	df_dbinput_getc = df_feed_getc;
	df2241_expr_eval();
	df_dbinput_getc = NULL;
	kprintf(DFH_PREFIX "DF2241: no trap, no guard (unexpected)\n");
}

#ifdef DDB
/*
 * Real DDB command: evaluated INSIDE a Debugger() session, so the #DE
 * hits the db_global_jmpbuf longjmp path (kdb_trap re-entry) instead of
 * a plain panic -- this is the exact operator scenario.
 */
DB_COMMAND(df2241wedge, df2241_cmd)
{
	db_expr_t val = 0;
	int ok;

	/* the poisoned expression is next in the feed queue */
	db_read_line();
	ok = db_expression(&val);
	if (ok)
		db_printf(DFH_PREFIX "DF2241: in-DDB value = %ld\n", (long)val);
	else
		db_printf(DFH_PREFIX "DF2241: in-DDB parse failed\n");
}
#endif

static void
df2241_mode2(void)
{
	volatile int leaked;

	kprintf(DFH_PREFIX "DF2241: scripted DDB session: "
	    "\"df2241wedge\" then \"continue\"\n");
	df_feed_clear();
	df_feed_add("df2241wedge\r");
	df_feed_add(DF2241_EXPR);
	df_feed_add("continue\r");
	df_dbinput_getc = df_feed_getc;
	Debugger("df2241");
	df_dbinput_getc = NULL;

	leaked = db_active;
	kprintf(DFH_PREFIX "DF2241: db_active=%d after DDB exit "
	    "(0 = correct)\n", leaked);
	if (leaked != 0) {
		kprintf(DFH_PREFIX "DF2241: LEAK CONFIRMED; forcing a kernel "
		    "trap to demonstrate permanent DDB re-entry (wedge)\n");
		*(volatile int *)0x20 = 1;
		kprintf(DFH_PREFIX "DF2241: unreachable\n");
	}
	kprintf(DFH_PREFIX "DF2241: no leak, guest healthy\n");
}

static int
df2241_sysctl(SYSCTL_HANDLER_ARGS)
{
	int mode = 0, error;

	error = sysctl_handle_int(oidp, &mode, 0, req);
	if (error != 0 || req->newptr == NULL)
		return (error);
	switch (mode) {
	case 0:
		df2241_mode0();
		break;
	case 1:
		df2241_mode1();
		break;
#ifdef DDB
	case 2:
		df2241_mode2();
		break;
#endif
	default:
		kprintf(DFH_PREFIX "DF2241: bad mode %d\n", mode);
	}
	return (0);
}
SYSCTL_PROC(_debug, OID_AUTO, df2241, CTLTYPE_INT | CTLFLAG_RW,
	    0, 0, df2241_sysctl, "I", "DF-2241 idiv trap tests");

/* ------------------------------------------------------------------ */
/* DF-2247 / DF-2248 / DF-2249 -- iconv_xlat_conv issues               */
/* ------------------------------------------------------------------ */

static void
df2247_run(int mode)
{
	void *h = NULL;
	const char *in;
	char *out;
	u_char srcbuf[256], outbuf[256];
	size_t ilen, olen;
	int ret, i, oob;

	if (mode == 0)
		ret = iconv_open("DF47OUT0", "DF47IN0", &h);
	else if (mode == 1)
		ret = iconv_open("DF47OUT1", "DF47IN1", &h);
	else
		ret = iconv_open("DF47OUT2", "DF47IN2", &h);
	if (ret != 0 || h == NULL) {
		kprintf(DFH_PREFIX "DF2247: iconv_open FAILED ret=%d "
		    "(arm pairs first: dfarm xlat ...)\n", ret);
		return;
	}

	for (i = 0; i < 256; i++)
		srcbuf[i] = i;
	in = (const char *)srcbuf;
	out = (char *)outbuf;
	ilen = 256;
	olen = 256;

	if (mode == 1) {
		kprintf(DFH_PREFIX "DF2247: TRIGGER NULL d_table "
		    "(datalen 0 pair); conv byte 'B' -> d_table[0x42] on NULL"
		    "\n");
		in = "B";
		ilen = 1;
		olen = 1;
	}
	ret = iconv_conv(h, &in, &ilen, &out, &olen);
	kprintf(DFH_PREFIX "DF2247: mode %d conv ret=%d consumed=%zu "
	    "produced=%zu\n", mode, ret, (size_t)(256 - ilen),
	    (size_t)(256 - olen));
	if (mode == 1) {
		kprintf(DFH_PREFIX "DF2247: TRIGGER SURVIVED (unexpected on "
		    "stock)\n");
	} else {
		oob = 0;
		for (i = 0; i < 256; i++) {
			if (mode == 0 && outbuf[i] != 0xAA) {
				kprintf(DFH_PREFIX "DF2247: CONTROL MISMATCH "
				    "[%d]=%02x\n", i, outbuf[i]);
			}
			if (mode == 2 && i >= 64 && outbuf[i] != 0xAA)
				oob++;
		}
		if (mode == 2) {
			kprintf(DFH_PREFIX "DF2247: undersized-table OUTPUT "
			    "(>=64: heap bytes read past 64-byte table):\n");
			dfh_hex("DF2247 leak sample", outbuf, 256);
			kprintf(DFH_PREFIX "DF2247: %d of 192 output bytes "
			    "beyond the 64-byte allocation differ from the "
			    "fill 0xAA  <<<< OOB-read proof\n", oob);
		} else {
			kprintf(DFH_PREFIX "DF2247: control output all 0xAA "
			    "as expected\n");
		}
	}
	iconv_close(h);
}

static int
df2247_sysctl(SYSCTL_HANDLER_ARGS)
{
	int mode = 0, error;

	error = sysctl_handle_int(oidp, &mode, 0, req);
	if (error != 0 || req->newptr == NULL)
		return (error);
	df2247_run(mode);
	return (0);
}
SYSCTL_PROC(_debug, OID_AUTO, df2247, CTLTYPE_INT | CTLFLAG_RW,
	    0, 0, df2247_sysctl, "I", "DF2247 xlat table tests");

static void
df2248_run(int mode)
{
	void *h = NULL;
	const char *in;
	char *out;
	u_char srcbuf[16], outbuf[16];
	size_t ilen, olen;
	int ret, i;

	ret = iconv_open("DF48OUT", "DF48IN", &h);
	if (ret != 0 || h == NULL) {
		kprintf(DFH_PREFIX "DF2248: iconv_open FAILED ret=%d "
		    "(arm pair first: dfarm xlat DF48OUT DF48IN 256 0xAA)\n",
		    ret);
		return;
	}

	for (i = 0; i < 16; i++)
		srcbuf[i] = i;
	in = (const char *)srcbuf;
	out = (char *)outbuf;
	ilen = 16;
	olen = 16;

	if (mode == 1) {
		kprintf(DFH_PREFIX "DF2248: TRIGGER in=out=0x80000000 "
		    "(bit 31 set -> int r negative -> while(r--) runaway)\n");
		ilen = 0x80000000UL;
		olen = 0x80000000UL;
	} else if (mode == 2) {
		kprintf(DFH_PREFIX "DF2248: TRUNCATION PROBE "
		    "in=out=0x100000000 (low 32 bits = 0)\n");
		ilen = 0x100000000UL;
		olen = 0x100000000UL;
	}
	ret = iconv_conv(h, &in, &ilen, &out, &olen);
	kprintf(DFH_PREFIX "DF2248: mode %d conv ret=%d ilen_left=%zu "
	    "olen_left=%zu\n", mode, ret, ilen, olen);
	if (mode == 0) {
		kprintf(DFH_PREFIX "DF2248: control output:\n");
		dfh_hex("DF2248 control out", outbuf, 16);
	} else {
		kprintf(DFH_PREFIX "DF2248: mode %d SURVIVED "
		    "(mode 1 unexpected on stock; mode 2 surviving with "
		    "consumed=0 IS the truncation proof)\n", mode);
	}
	iconv_close(h);
}

static int
df2248_sysctl(SYSCTL_HANDLER_ARGS)
{
	int mode = 0, error;

	error = sysctl_handle_int(oidp, &mode, 0, req);
	if (error != 0 || req->newptr == NULL)
		return (error);
	df2248_run(mode);
	return (0);
}
SYSCTL_PROC(_debug, OID_AUTO, df2248, CTLTYPE_INT | CTLFLAG_RW,
	    0, 0, df2248_sysctl, "I", "DF-2248 xlat min-truncation tests");

static void
df2249_run(int mode)
{
	void *h = NULL;
	const char *in;
	char *out;
	u_char srcbuf[2], outbuf[16];
	size_t ilen, olen;
	int ret;

	/*
	 * Table fill 0xAA with: 'A'(0x41)->0x41, 'Z'(0x5A)->0x5C.
	 */
	ret = iconv_open("DF49OUT", "DF49IN", &h);
	if (ret != 0 || h == NULL) {
		kprintf(DFH_PREFIX "DF2249: iconv_open FAILED ret=%d "
		    "(arm pair first: dfarm xlat DF49OUT DF49IN 256 0xAA "
		    "&& dfarm patch DF49OUT DF49IN 41:41 5A:5C)\n", ret);
		return;
	}

	srcbuf[0] = (mode == 0) ? 'A' : 'Z';
	srcbuf[1] = 'Z';
	memset(outbuf, 'Q', sizeof(outbuf));

	in = (const char *)srcbuf;
	out = (char *)outbuf;
	ilen = 1;
	olen = sizeof(outbuf);

	if (mode == 1 || mode == 3) {
		kprintf(DFH_PREFIX "DF2249: TRIGGER OOB READ: "
		    "iconv_convchr with *inbytesleft=0\n");
		ilen = 0;
	}
	if (mode == 2 || mode == 3) {
		kprintf(DFH_PREFIX "DF2249: TRIGGER OOB WRITE: "
		    "iconv_convchr with *outbytesleft=0\n");
		out = (char *)(outbuf + 8);
		olen = 0;
	}

	ret = iconv_convchr(h, &in, &ilen, &out, &olen);
	kprintf(DFH_PREFIX "DF2249: mode %d convchr ret=%d\n", mode, ret);
	kprintf(DFH_PREFIX "DF2249: outbuf[0..11]:");
	{
		int i;
		for (i = 0; i < 12; i++)
			kprintf(" %02x", outbuf[i]);
		kprintf("\n");
	}
	switch (mode) {
	case 0:
		kprintf(DFH_PREFIX "DF2249: control expect out[0]=41 "
		    "(table['A'])\n");
		break;
	case 1:
	case 3:
		kprintf(DFH_PREFIX "DF2249: expect out[0]=5c (table['Z']) -- "
		    "the byte read past the EMPTY input buffer was 'Z'\n");
		break;
	}
	if (mode == 2 || mode == 3) {
		if (outbuf[8] != 'Q')
			kprintf(DFH_PREFIX "DF2249: OOB WRITE proof: "
			    "outbuf[8] %02x -> %02x (byte written with "
			    "*outbytesleft==0)\n", 'Q', outbuf[8]);
		else
			kprintf(DFH_PREFIX "DF2249: outbuf[8] still Q "
			    "(no OOB write)\n");
	}
	iconv_close(h);
}

static int
df2249_sysctl(SYSCTL_HANDLER_ARGS)
{
	int mode = 0, error;

	error = sysctl_handle_int(oidp, &mode, 0, req);
	if (error != 0 || req->newptr == NULL)
		return (error);
	df2249_run(mode);
	return (0);
}
SYSCTL_PROC(_debug, OID_AUTO, df2249, CTLTYPE_INT | CTLFLAG_RW,
	    0, 0, df2249_sysctl, "I", "DF-2249 xlat convchr tests");

/* ------------------------------------------------------------------ */

static void
dfh_hex(const char *tag, const u_char *p, int n)
{

	kprintf(DFH_PREFIX "%s [%d bytes]:", tag, n);
	while (n-- > 0)
		kprintf(" %02x", *p++);
	kprintf("\n");
}
