⬢ DragonFlyBSD Kernel Audit
DF-3547 / fix.diff
← back to finding ↓ download raw
--- a/sys/netgraph7/ng_device.c
+++ b/sys/netgraph7/ng_device.c
@@ -88,6 +88,10 @@ struct ngd_private {
 	struct	cdev	*ngddev;
 	struct	mtx	ngd_mtx;
 	int 		unit;
+	int		refs;		/* 1 base (node) + one per active cdevsw call */
+	int		dying;		/* teardown began; refuse new callers */
 	uint16_t	flags;
 #define	NGDF_OPEN	0x0001
 #define	NGDF_RWAIT	0x0002
@@ -190,6 +194,8 @@ ng_device_constructor(node_p node)
 	mtx_init(&priv->ngd_mtx, "ng_device", NULL, MTX_DEF);
 	mtx_init(&priv->readq.ifq_mtx, "ng_device queue", NULL, MTX_DEF);
 	IFQ_SET_MAXLEN(&priv->readq, ifqmaxlen);
+	priv->refs = 1;		/* node's base reference */
+	priv->dying = 0;
 
 	/* Link everything together */
 	NG_NODE_SET_PRIVATE(node, priv);
@@ -292,10 +298,54 @@ ng_device_disconnect(hook_p hook)
 	priv_p priv = NG_NODE_PRIVATE(NG_HOOK_NODE(hook));
+	int last;
 
 	DBG;
 
+	/*
+	 * DragonFly's destroy_dev() is asynchronous: it only queues work to
+	 * the devfs core thread and does NOT drain in-flight (or blocked)
+	 * cdevsw method invocations, and an open fd keeps dispatching through
+	 * si_drv1 afterwards.  Therefore priv may only be freed when the last
+	 * device method has returned.
+	 */
+	mtx_lock(&priv->ngd_mtx);
+	priv->dying = 1;
+	if (priv->ngddev != NULL)
+		priv->ngddev->si_drv1 = NULL;	/* no new callers can acquire */
+	last = (--priv->refs == 0);
+	mtx_unlock(&priv->ngd_mtx);
+
 	destroy_dev(priv->ngddev);
-	mtx_destroy(&priv->ngd_mtx);
 
-	IF_DRAIN(&priv->readq);
-	mtx_destroy(&(priv)->readq.ifq_mtx);
+	if (last) {
+		mtx_destroy(&priv->ngd_mtx);
+		IF_DRAIN(&priv->readq);
+		mtx_destroy(&(priv)->readq.ifq_mtx);
 
-	free_unr(ngd_unit, priv->unit);
+		free_unr(ngd_unit, priv->unit);
 
-	kfree(priv, M_NETGRAPH);
+		kfree(priv, M_NETGRAPH);
+	}
 
 	ng_rmnode_self(NG_HOOK_NODE(hook));
 
@@ -299,6 +349,34 @@ ng_device_disconnect(hook_p hook)
 	return(0);
 }
+
+/*
+ * Acquire/release the private structure from a cdevsw method.  Acquire
+ * serializes with ng_device_disconnect() on ngd_mtx so that priv cannot be
+ * freed while a method is running, and refuses to hand out dying privs.
+ */
+static priv_p
+ngd_acquire(struct cdev *dev)
+{
+	priv_p priv = dev->si_drv1;
+
+	if (priv == NULL)
+		return (NULL);
+	mtx_lock(&priv->ngd_mtx);
+	if (priv->dying) {
+		mtx_unlock(&priv->ngd_mtx);
+		return (NULL);
+	}
+	priv->refs++;
+	mtx_unlock(&priv->ngd_mtx);
+	return (priv);
+}
+
+static void
+ngd_release(priv_p priv)
+{
+	int last;
+
+	mtx_lock(&priv->ngd_mtx);
+	last = (--priv->refs == 0);
+	mtx_unlock(&priv->ngd_mtx);
+	if (last) {
+		IF_DRAIN(&priv->readq);
+		mtx_destroy(&(priv)->readq.ifq_mtx);
+		mtx_destroy(&priv->ngd_mtx);
+		free_unr(ngd_unit, priv->unit);
+		kfree(priv, M_NETGRAPH);
+	}
+}
 
 /******************************************************************************
  *  Device methods
@@ -332,6 +410,9 @@ ngdopen(struct cdev *dev, int flag, int mode, struct thread *td)
 	priv_p	priv = (priv_p )dev->si_drv1;
 
 	DBG;
+
+	if ((priv = ngd_acquire(dev)) == NULL)
+		return (ENXIO);
 
 	mtx_lock(&priv->ngd_mtx);
 	priv->flags |= NGDF_OPEN;
@@ -340,6 +421,8 @@ ngdopen(struct cdev *dev, int flag, int mode, struct thread *td)
 	return(0);
 }
@@ -349,6 +432,9 @@ ngdclose(struct cdev *dev, int flag, int mode, struct thread *td)
 	priv_p	priv = (priv_p )dev->si_drv1;
 
 	DBG;
+
+	if ((priv = ngd_acquire(dev)) == NULL)
+		return (ENXIO);
+
 	mtx_lock(&priv->ngd_mtx);
 	priv->flags &= ~NGDF_OPEN;
 	mtx_unlock(&priv->ngd_mtx);
@@ -409,6 +495,10 @@ ngdread(struct cdev *dev, struct uio *uio, int flag)
 	int len, error = 0;
 
 	DBG;
+
+	if ((priv = ngd_acquire(dev)) == NULL)
+		return (ENXIO);
 
 	/* get an mbuf */
 	do {
@@ -431,6 +521,7 @@ ngdread(struct cdev *dev, struct uio *uio, int flag)
 	if (m)
 		m_freem(m);
 
+	ngd_release(priv);
 	return (error);
 }
@@ -452,6 +543,10 @@ ngdwrite(struct cdev *dev, struct uio *uio, int flag)
 	int error = 0;
 
 	DBG;
+
+	if ((priv = ngd_acquire(dev)) == NULL)
+		return (ENXIO);
 
 	if (uio->uio_resid == 0)
 		return (0);
@@ -467,6 +562,7 @@ ngdwrite(struct cdev *dev, struct uio *uio, int flag)
 
 	NG_SEND_DATA_ONLY(error, priv->hook, m);
 
+	ngd_release(priv);
 	return (error);
 }
@@ -479,6 +575,10 @@ ngdpoll(struct cdev *dev, int events, struct thread *td)
 	int revents = 0;
 
+	if ((priv = ngd_acquire(dev)) == NULL)
+		return (0);
+
 	if (events & (POLLIN | POLLRDNORM) &&
 	    !IFQ_IS_EMPTY(&priv->readq))
 		revents |= events & (POLLIN | POLLRDNORM);
 
+	ngd_release(priv);
 	return (revents);
 }