⬢ DragonFlyBSD Kernel Audit
DF-5548 / fix.diff
← back to finding ↓ download raw
--- a/sys/bus/firewire/fwmem.c
+++ b/sys/bus/firewire/fwmem.c
--- a/sys/bus/firewire/fwmem.c
+++ b/sys/bus/firewire/fwmem.c
@@ -48,6 +48,8 @@
 #include <sys/signal.h>
 #include <sys/mman.h>
 #include <sys/fcntl.h>
+#include <sys/spinlock.h>
+#include <sys/spinlock2.h>
 #include <sys/thread2.h>
 
 #include <bus/firewire/firewire.h>
@@ -70,6 +72,10 @@
 
 MALLOC_DEFINE(M_FWMEM, "fwmem", "fwmem/FireWire");
 
+/* DF-5548: serialize the si_drv1/refcount lifecycle.  devfs does not
+ * serialize open/close/ioctl/strategy of one cdev across threads. */
+static struct spinlock fwmem_lk = SPINLOCK_INITIALIZER(fwmem_lk, "fwmem_lk");
+
 #define MAXLEN (512 << fwmem_speed)
 
 struct fwmem_softc {
@@ -263,18 +272,36 @@
 	cdev_t dev = ap->a_head.a_dev;
 	struct fwmem_softc *fms;
 
+	spin_lock(&fwmem_lk);
 	if (dev->si_drv1 != NULL) {
-		if ((ap->a_oflags & FWRITE) != 0)
+		if ((ap->a_oflags & FWRITE) != 0) {
+			spin_unlock(&fwmem_lk);
 			return (EBUSY);
+		}
 		fms = (struct fwmem_softc *)dev->si_drv1;
 		fms->refcount ++;
+		spin_unlock(&fwmem_lk);
 	} else {
+		spin_unlock(&fwmem_lk);
 		fms = (struct fwmem_softc *)kmalloc(sizeof(struct fwmem_softc),
 							M_FWMEM, M_WAITOK);
 		bcopy(&fwmem_eui64, &fms->eui, sizeof(struct fw_eui64));
-		dev->si_drv1 = (void *)fms;
-		dev->si_iosize_max = min(MAXPHYS,64*1024);
 		fms->refcount = 1;
+		spin_lock(&fwmem_lk);
+		if (dev->si_drv1 != NULL) {
+			/* lost a concurrent first-open race: keep the winner */
+			kfree(fms, M_FWMEM);
+			fms = (struct fwmem_softc *)dev->si_drv1;
+			if ((ap->a_oflags & FWRITE) != 0) {
+				spin_unlock(&fwmem_lk);
+				return (EBUSY);
+			}
+			fms->refcount ++;
+		} else {
+			dev->si_drv1 = (void *)fms;
+			dev->si_iosize_max = min(MAXPHYS,64*1024);
+		}
+		spin_unlock(&fwmem_lk);
 	}
 	if (fwmem_debug)
 		kprintf("%s: refcount=%d\n", __func__, fms->refcount);
@@ -288,14 +315,20 @@
 	cdev_t dev = ap->a_head.a_dev;
 	struct fwmem_softc *fms;
 
+	spin_lock(&fwmem_lk);
 	fms = (struct fwmem_softc *)dev->si_drv1;
+	if (fms == NULL) {
+		spin_unlock(&fwmem_lk);
+		return (0);
+	}
 	fms->refcount --;
 	if (fwmem_debug)
 		kprintf("%s: refcount=%d\n", __func__, fms->refcount);
 	if (fms->refcount < 1) {
-		kfree(dev->si_drv1, M_FW);
+		kfree(fms, M_FWMEM);	/* DF-5549: was M_FW */
 		dev->si_drv1 = NULL;
 	}
+	spin_unlock(&fwmem_lk);
 
 	return (0);
 }
@@ -331,6 +364,7 @@
 	struct fwmem_softc *fms;
 	struct fw_device *fwdev;
 	struct fw_xfer *xfer;
+	struct fw_eui64 euilocal;
 	int unit, err=0, iolen;
 
 	/* XXX check request length */
@@ -339,8 +373,16 @@
 	sc = devclass_get_softc(firewire_devclass, unit);
 
 	crit_enter();
+	spin_lock(&fwmem_lk);
 	fms = (struct fwmem_softc *)dev->si_drv1;
-	fwdev = fw_noderesolve_eui64(sc->fc, &fms->eui);
+	if (fms == NULL) {
+		spin_unlock(&fwmem_lk);
+		err = ENXIO;
+		goto error;
+	}
+	euilocal = fms->eui;
+	spin_unlock(&fwmem_lk);
+	fwdev = fw_noderesolve_eui64(sc->fc, &euilocal);
 	if (fwdev == NULL) {
 		if (fwmem_debug)
 			kprintf("fwmem: no such device ID:%08x%08x\n",
@@ -404,7 +446,12 @@
 	struct fwmem_softc *fms;
 	int err = 0;
 
+	spin_lock(&fwmem_lk);
 	fms = (struct fwmem_softc *)dev->si_drv1;
+	if (fms == NULL) {
+		spin_unlock(&fwmem_lk);
+		return (ENXIO);
+	}
 	switch (ap->a_cmd) {
 	case FW_SDEUI64:
 		bcopy(ap->a_data, &fms->eui, sizeof(struct fw_eui64));
@@ -415,6 +462,7 @@
 	default:
 		err = EINVAL;
 	}
+	spin_unlock(&fwmem_lk);
 	return(err);
 }