--- a/sys/bus/firewire/fwdev.c +++ b/sys/bus/firewire/fwdev.c @@ -97,6 +97,18 @@ { int i; + /* + * FW_SSTBUF hands us fully user-controlled values; without + * validation the 32-bit nchunk*npacket product in + * fwdma_malloc_multiseg() can wrap to 0 while bnchunk/bnpacket + * stay huge, leaving a 0-segment buffer that fw_read/fw_write + * index out of bounds via fwdma_v_addr(). (DF-5413) + */ + if (b->nchunk == 0 || b->npacket == 0 || + b->nchunk > 4096 || b->npacket > 512 || + b->psize == 0 || b->psize > FWPMAX_S400) + return(EINVAL); + if (q->flag & (FWXFERQ_RUNNING | FWXFERQ_EXTBUF)) return(EBUSY); @@ -223,8 +235,15 @@ /* remove binding */ for (fwb = STAILQ_FIRST(&ir->binds); fwb != NULL; fwb = STAILQ_FIRST(&ir->binds)) { + crit_enter(); STAILQ_REMOVE(&fc->binds, fwb, fw_bind, fclist); STAILQ_REMOVE_HEAD(&ir->binds, chlist); + crit_exit(); + /* free the bind's private xfer (leak, DF-5417) */ + while ((xfer = STAILQ_FIRST(&fwb->xferlist)) != NULL) { + STAILQ_REMOVE_HEAD(&fwb->xferlist, link); + fw_xfer_free(xfer); + } kfree(fwb, M_FW); } ir->flag &= ~(FWXFERQ_OPEN | @@ -302,20 +321,15 @@ err = EIO; return err; } else if(xfer != NULL) { -#if 0 /* XXX broken */ /* per packet mode or FWACT_CH bind?*/ crit_enter(); ir->queued --; STAILQ_REMOVE_HEAD(&ir->q, link); crit_exit(); - fp = &xfer->recv.hdr; - if (sc->fc->irx_post != NULL) - sc->fc->irx_post(sc->fc, fp->mode.ld); - err = uiomove((void *)fp, 1 /* XXX header size */, uio); - /* XXX copy payload too */ - /* XXX we should recycle this xfer */ -#endif - fw_xfer_free( xfer); + /* Dequeue before freeing: leaving the freed xfer linked + * in ir->q makes every later read free it again. + * (DF-5414) */ + fw_xfer_free(xfer); } else if(ir->stproc != NULL) { /* iso bulkxfer */ fp = (struct fw_pkt *)fwdma_v_addr(ir->buf, @@ -592,12 +606,37 @@ case FW_CBINDADDR: fwb = fw_bindlookup(sc->fc, bindreq->start.hi, bindreq->start.lo); - if(fwb == NULL){ + if (fwb == NULL || ir == NULL){ err = EINVAL; break; } + { + struct fw_bind *tfwb; + int found = 0; + + /* fw_bindlookup() is global: only unbind binds + * that were registered through this xferq, + * otherwise STAILQ_REMOVE() below walks a list + * that never contained fwb. (DF-5416) */ + STAILQ_FOREACH(tfwb, &ir->binds, chlist) + if (tfwb == fwb) { + found = 1; + break; + } + if (!found) { + err = EINVAL; + break; + } + } + crit_enter(); STAILQ_REMOVE(&sc->fc->binds, fwb, fw_bind, fclist); STAILQ_REMOVE(&ir->binds, fwb, fw_bind, chlist); + crit_exit(); + /* the bind's private xfer must not leak (DF-5417) */ + while ((xfer = STAILQ_FIRST(&fwb->xferlist)) != NULL) { + STAILQ_REMOVE_HEAD(&fwb->xferlist, link); + fw_xfer_free(xfer); + } kfree(fwb, M_FW); break; case FW_SBINDADDR: @@ -631,6 +670,10 @@ STAILQ_INSERT_TAIL(&fwb->xferlist, xfer, link); crit_exit(); err = fw_bindadd(sc->fc, fwb); + if (err) { + fw_xfer_free(xfer); + kfree(fwb, M_FW); + } break; case FW_GDEVLST: i = len = 1; @@ -720,6 +763,13 @@ ir = ((struct fw_drv1 *)dev->si_drv1)->ir; + /* no receive stream bound yet: rkq would be dereferenced + * through a NULL ir. (DF-5415) */ + if (ir == NULL) { + ap->a_result = ENODEV; + return (0); + } + ap->a_result = 0; switch (kn->kn_filter) {