/*
 * DF-4229 harness: uninitialized `error` in cyapawrite() (sys/dev/smbus/cyapa/cyapa.c)
 *
 * Reproduces, in userspace, the EXACT control flow of cyapawrite()
 * (cyapa.c:785-1056) for the trigger reachable from userland:
 *
 *      write(fd, buf, 0)   -- zero-length write on an open /dev/cyapa*
 *
 * Trace on DragonFly (all sources cited):
 *   sys/kern/sys_generic.c:506  dofilewrite() -> fo_write() with no
 *                               uio_resid==0 short-circuit
 *   sys/kern/vfs_vnops.c vn_write() -> VOP_WRITE_FP()
 *   sys/vfs/devfs/devfs_vnops.c:1366 devfs_write() -> dev_dwrite()
 *   sys/kern/kern_device.c dev_dwrite() -> cyapawrite()
 *
 * In cyapawrite():
 *   cyapa.c:791   int error;                    <-- never initialized
 *   cyapa.c:802   while ((n = fifo_space(&sc->wfifo)) > 0 && uio->uio_resid)
 *                    SKIPPED: resid == 0         <-- error never assigned
 *   cyapa.c:815   while (... && error == 0)      <-- uninit read (wfifo non-empty)
 *   cyapa.c:1054  if (error == 0 && (...))       <-- uninit read (always)
 *   cyapa.c:1056  return error;                  <-- stale stack slot returned
 *                                                     to userland as write()'s
 *                                                     result / errno
 *
 * The kernel function's stack frame occupies memory previously used by
 * arbitrary kernel code (previous syscall on the thread stack), so the
 * returned 32-bit value is stale kernel stack data (can contain kernel
 * pointers -> KASLR-relevant info leak).
 *
 * Demonstration method: poison the stack below the model function with a
 * known pattern, then call the model. A return of the pattern proves the
 * value flows from an uninitialized stack slot. Build with -O0 so `error`
 * gets a real stack slot (as it does in the kernel at -O2 where it is
 * likewise not initialized before the skip path).
 */
#include <stdio.h>
#include <string.h>
#include <stdint.h>

#define CYAPA_BUFSIZE 128
#define CYAPA_BUFMASK (CYAPA_BUFSIZE - 1)

struct cyapa_fifo {
	int rindex;
	int windex;
	char buf[CYAPA_BUFSIZE];
};

/* ---- stubs mirroring the invariants at the trigger point ---- */
static struct cyapa_fifo wfifo = { 5, 5, { 0 } };	/* empty wfifo */
static long uio_resid = 0;				/* write(fd, buf, 0) */

static size_t fifo_space(struct cyapa_fifo *fifo)
{
	size_t n;
	n = CYAPA_BUFSIZE - (fifo->windex & CYAPA_BUFMASK);
	if (n > (size_t)(CYAPA_BUFSIZE - (fifo->windex - fifo->rindex)))
		n = (size_t)(CYAPA_BUFSIZE - (fifo->windex - fifo->rindex));
	return n;
}

static int fifo_ready(struct cyapa_fifo *fifo)
{
	return ((fifo->windex - fifo->rindex) != 0);
}

static int uiomove_stub(void)
{
	return 0;					/* success */
}

/* exact control-flow model of cyapawrite() cyapa.c:785-1056 */
static int cyapawrite_model(void)
{
	int error;					/* cyapa.c:791 */
	int cmd_completed;
	size_t n;
	(void)(uintptr_t)&n;

again:
	/*
	 * Copy data from userland.
	 */
	while ((n = fifo_space(&wfifo)) > 0 && uio_resid) {	/* cyapa.c:802 */
		if (n > uio_resid)
			n = uio_resid;
		error = uiomove_stub();			/* cyapa.c:805 -- NOT reached */
		if (error)
			break;
		break;					/* resid==0: loop never runs */
	}

	/*
	 * Handle commands
	 */
	cmd_completed = (fifo_ready(&wfifo) != 0);		/* cyapa.c:814 */
	while (fifo_ready(&wfifo) && cmd_completed && error == 0)	/* cyapa.c:815 */
		break;					/* cmd loop stubbed */

	/* cyapa.c:1054 */
	if (error == 0 && (cmd_completed || uio_resid))
		goto again;
	return error;					/* cyapa.c:1056 */
}

/*
 * Smash a region of stack with the poison pattern; the callee's frame
 * (including `error`'s slot) is carved out of this dirty region.
 */
static void poison_stack(void)
{
	volatile unsigned char junk[8192];

	memset((void *)junk, 0xAB, sizeof(junk));
}

int main(void)
{
	int r;

	poison_stack();
	r = cyapawrite_model();
	printf("write(/dev/cyapa*, buf, 0) returned: 0x%08x (%d)\n",
	       (unsigned)r, r);
	if (r == (int)0xABABABAB) {
		printf("RESULT: UNINITIALIZED STACK VALUE LEAKED "
		       "(poison 0xABABABAB observed)\n");
		return 0;
	}
	printf("RESULT: stale stack value observed (not the poison pattern "
	       "this run; run again)\n");
	return 1;
}
