/*
 * hexdiff.c -- pass-2 differential harness for sys/libkern/hexncpy.c
 * (DF-5709 evidence; also confirms the negative results of the pass-2 audit).
 *
 * Links against the REAL kernel source (hexncpy.c compiled unmodified via the
 * shim/sys/systm.h stub). Tests:
 *
 *  T1  Guard-byte theorem: for every inlen>0, outlen>=3, sep in
 *      {NULL, ":", "-", " ", ""}: hexncpy touches no byte at or after
 *      outb[outlen] and none before outb[0].  Expected: 0 violations.
 *  T2  Reference differential: expected = full hex string (pairs joined by
 *      sep, or plain pairs for sep==NULL) truncated to outlen-1 chars +
 *      NUL.  Deviations classified:
 *        dev_sepnull  -- sep==NULL mismatch (known DF-2253 class)
 *        dev_trunc    -- sep!=NULL, actual is strict prefix: whole-group
 *                        truncation granularity vs char-granularity
 *                        (cosmetic; occurs only when outlen%3 != 0 and the
 *                        buffer ran out mid-group)
 *        dev_other    -- anything else.  Expected: 0.
 *  T3  HEX_NCPYLEN macro mis-expansion (DF-5709): the macro from
 *      sys/sys/libkern.h:67 is copied VERBATIM; compound arguments
 *      mis-size the buffer by C precedence.  Demonstrates a latent
 *      hexncpy overflow for the "future caller" pattern.
 *  T4  Known-behavior confirmation: DF-2252 (inlen<=0 -> outb[-1] NUL)
 *      and DF-2253 (sep==NULL eats the final nibble).
 */
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>

char *hexncpy(const u_char *, int, char *, int, const char *);

/* ---- verbatim from sys/sys/libkern.h:67 ---- */
#define HEX_NCPYLEN(s)		(s * 3)
/* ---- the fix under test (DF-5709) ---- */
#define HEX_NCPYLEN_FIXED(s)	((s) * 3)

#define GUARD		0xA5
#define MAXIN		64

static unsigned long t1_cases, t1_viol;
static unsigned long t2_cases, t2_match, t2_dev_sepnull, t2_dev_trunc, t2_dev_other;
static unsigned long t4_df2252, t4_df2253;

static void *
xmalloc(size_t n)
{
	void *p = malloc(n);
	if (p == NULL) { perror("malloc"); exit(1); }
	return p;
}

/* Full reference string: hex pairs of inlen bytes, sep between pairs
 * (no trailing sep), or plain pairs when sep==NULL. */
static void
ref_full(const u_char *inb, int inlen, const char *sep, char *out)
{
	const char hx[] = "0123456789abcdef";
	int o = 0, i;

	for (i = 0; i < inlen; i++) {
		out[o++] = hx[inb[i] >> 4];
		out[o++] = hx[inb[i] & 0xf];
		if (sep && i != inlen - 1)
			out[o++] = sep[0];
	}
	out[o] = '\0';
}

static void
run_case(const u_char *inb, int inlen, int outlen, const char *sep)
{	size_t bufsz = 32 + (size_t)outlen + 32;
	unsigned char *buf = xmalloc(bufsz);
	char *base;
	char *ret;
	char actual[3 * MAXIN + 4], full[3 * MAXIN + 4], expected[3 * MAXIN + 4];
	size_t keep;
	size_t i;
	int bad = 0;

	/* ---- T1: guarded sandbox ---- */
	memset(buf, GUARD, bufsz);
	base = (char *)buf + 32;
	memset(base, 0x00, (size_t)outlen);
	ret = hexncpy(inb, inlen, base, outlen, sep);
	t1_cases++;
	for (i = 0; i < 32; i++)
		if (buf[i] != GUARD) { bad++; break; }
	for (i = 32 + (size_t)outlen; i < bufsz; i++)
		if (buf[i] != GUARD) { bad++; break; }
	if (ret != base)
		bad++;
	if (bad) {
		t1_viol++;
		printf("T1 VIOLATION inlen=%d outlen=%d sep=%s\n",
		    inlen, outlen, sep ? sep : "(null)");
	}

	/* ---- T2: differential (skip the "" separator: embedded NULs) ---- */
	if (sep == NULL || sep[0] != '\0') {
	snprintf(actual, sizeof(actual), "%s", base);
	ref_full(inb, inlen, sep, full);
	keep = strlen(full);
	if (keep > (size_t)outlen - 1)
		keep = (size_t)outlen - 1;
	memcpy(expected, full, keep);
	expected[keep] = '\0';

	t2_cases++;
	if (strcmp(actual, expected) == 0)
		t2_match++;
	else if (sep == NULL)
		t2_dev_sepnull++;		/* known DF-2253 class */
	else if (strlen(actual) < strlen(expected) &&
		 strncmp(actual, expected, strlen(actual)) == 0)
		t2_dev_trunc++;		/* whole-group truncation granularity */
	else {
		t2_dev_other++;
		printf("T2 OTHER inlen=%d outlen=%d sep=%s actual='%s' expected='%s'\n",
		    inlen, outlen, sep ? sep : "(null)", actual, expected);
	}
	}
	free(buf);
}

/* ---------------- T3: macro mis-expansion (DF-5709) ---------------- */
enum { NBYTES = 8 };
static char macro_sized[HEX_NCPYLEN(NBYTES + 1)];	/* expands (8 + 1*3)  = 11 */
static char fixed_sized[HEX_NCPYLEN_FIXED(NBYTES + 1)];	/* expands ((8+1)*3)  = 27 */

static void
t3(void)
{
	u_char in[NBYTES + 1] = {0xde,0xad,0xbe,0xef,0x13,0x37,0xc0,0x0d,0x42};
	int n = NBYTES;
	int inlen = NBYTES + 1;		/* 9 */
	int outlen;			/* what a caller would compute */
	size_t bufsz = 32 + sizeof(macro_sized) + 32;
	unsigned char *buf;
	char *base;
	size_t i;
	int over = 0;

	printf("T3 HEX_NCPYLEN argument is unparenthesized (sys/sys/libkern.h:67):\n");
	printf("   #define HEX_NCPYLEN(s)  (s * 3)\n");
	printf("   HEX_NCPYLEN(NBYTES+1), NBYTES=%d -> sizeof array = %zu (need %d)\n",
	    NBYTES, sizeof(macro_sized), (NBYTES + 1) * 3);
	printf("   HEX_NCPYLEN_FIXED(NBYTES+1)      -> sizeof array = %zu\n",
	    sizeof(fixed_sized));
	printf("   runtime: n=%d  HEX_NCPYLEN(n+1)=%d  HEX_NCPYLEN_FIXED(n+1)=%d\n",
	    n, HEX_NCPYLEN(n + 1), HEX_NCPYLEN_FIXED(n + 1));

	/* latent-overflow demo: the future-caller pattern --
	 *   char hexstr[HEX_NCPYLEN(cnt + 1)];
	 *   hexncpy(p, cnt + 1, hexstr, HEX_NCPYLEN(cnt + 1), ":");
	 * The array is 11 bytes; hexncpy is told outlen=27 and writes 27 bytes.
	 */
	outlen = HEX_NCPYLEN(inlen);
	buf = xmalloc(bufsz);
	memset(buf, GUARD, bufsz);
	base = (char *)buf + 32;
	memset(base, 0x00, sizeof(macro_sized));
	hexncpy(in, inlen, base, outlen, ":");
	for (i = 32 + sizeof(macro_sized); i < bufsz; i++)
		if (buf[i] != GUARD)
			over++;
	printf("   hexncpy(9 bytes, outlen=%d, \":\") into the %zu-byte macro-sized buffer:\n",
	    outlen, sizeof(macro_sized));
	printf("      string produced   : '%s'\n", base);
	printf("      guard bytes smashed past the %zu-byte buffer: %d (true footprint = %d bytes)\n",
	    sizeof(macro_sized), over, 3 * inlen);
	if (sizeof(macro_sized) == (size_t)(NBYTES + 3) && outlen == 3 * inlen && over > 0)
		printf("T3 RESULT: LATENT OVERFLOW DEMONSTRATED "
		    "(macro sizes %d->%zu, hexncpy needs %d bytes at sep!=NULL)\n",
		    3 * inlen, sizeof(macro_sized), 3 * inlen);
	else
		printf("T3 RESULT: NOT DEMONSTRATED (unexpected)\n");
	free(buf);
}

/* ---------------- T4: known behaviors ---------------- */
static void
t4(void)
{
	u_char in[4] = {0x01, 0x02, 0x03, 0x04};
	unsigned char *buf;
	char *base;

	/* DF-2252: inlen == 0 -> NUL written at outb[-1] */
	buf = xmalloc(64);
	memset(buf, GUARD, 64);
	base = (char *)buf + 32;
	memset(base, 0x00, 32);
	hexncpy(in, 0, base, 32, ":");
	if (base[-1] == 0x00) {		/* sandbox was pre-filled with 0xA5 */
		t4_df2252++;
		printf("T4 DF-2252 confirmed: inlen=0 wrote NUL at outb[-1] (byte before buffer)\n");
	}
	free(buf);

	/* DF-2253: sep==NULL, exact HEX_NCPYLEN buffer -> final nibble eaten */
	buf = xmalloc(64);
	memset(buf, GUARD, 64);
	base = (char *)buf + 16;
	memset(base, 0x00, 32);
	hexncpy(in, 4, base, HEX_NCPYLEN(4), NULL);
	if (strcmp(base, "0102030") == 0) {	/* should be "01020304" */
		t4_df2253++;
		printf("T4 DF-2253 confirmed: sep=NULL, outlen=HEX_NCPYLEN(4)=12 -> '%s' "
		    "(final nibble of 04 destroyed)\n", base);
	}
	free(buf);
}

int
main(void)
{
	static const char *seps_t1[] = { NULL, ":", "-", " ", "" };
	u_char in[MAXIN];
	int s, il, ol;

	for (il = 0; il < MAXIN; il++)
		in[il] = (u_char)(il * 37 + 5);

	for (s = 0; s < 5; s++)
		for (il = 1; il <= MAXIN; il++)
			for (ol = 3; ol <= 3 * MAXIN; ol++)
				run_case(in, il, ol, seps_t1[s]);
	printf("T1 guard theorem : cases=%lu violations=%lu (expected 0)\n",
	    t1_cases, t1_viol);
	printf("T2 differential  : cases=%lu match=%lu dev_sepnull(DF-2253 class)=%lu "
	    "dev_trunc_granularity=%lu dev_other=%lu (expected 0)\n",
	    t2_cases, t2_match, t2_dev_sepnull, t2_dev_trunc, t2_dev_other);

	t3();
	t4();

	return (t1_viol == 0 && t2_dev_other == 0 && t4_df2252 == 1 && t4_df2253 == 1)
	    ? 0 : 1;
}
