DragonFlyBSD Kernel Audit
DF-3017 / fix.diff
← back to finding ↓ download raw
--- a/sys/sys/devfs.h	2026-09-05 10:07:15.510812707 +0000
+++ b/sys/sys/devfs.h	2026-09-05 10:08:18.410014538 +0000
@@ -324,6 +324,14 @@
 #define DEVFS_NLINKSWAIT		0x400	/* NLinks final */
 
 /*
+ * devfs_allocv() is active on the node and may be using it across a
+ * devfs_lock drop (getnewvnode()/vget()).  devfs_freep() must defer
+ * the physical free until allocv() revalidates the node.
+ */
+#define DEVFS_ALLOCVINPROG		0x800	/* allocv() active on node */
+#define DEVFS_FREEWAIT			0x1000	/* freep deferred to allocv */
+
+/*
  * Clone helper stuff
  */
 #define DEVFS_BITMAP_INITIAL_SIZE	1
--- a/sys/vfs/devfs/devfs_core.c	2026-09-05 10:07:15.510812707 +0000
+++ b/sys/vfs/devfs/devfs_core.c	2026-09-05 10:08:18.422014386 +0000
@@ -308,6 +308,13 @@
 	KKASSERT(node);
 
 	/*
+	 * Prevent devfs_freep() from physically freeing the node while we
+	 * have to release devfs_lock below.  The node is only revalidated
+	 * under devfs_lock after each drop.
+	 */
+	node->flags |= DEVFS_ALLOCVINPROG;
+
+	/*
 	 * devfs master lock must not be held across a vget() call, we have
 	 * to hold our ad-hoc vp to avoid a free race from destroying the
 	 * contents of the structure.  The vget() will interlock recycles
@@ -321,6 +328,12 @@
 		vdrop(vp);
 		lockmgr(&devfs_lock, LK_EXCLUSIVE);
 		if (error == 0) {
+			if (node->flags & DEVFS_DESTROYED) {
+				vput(vp);
+				*vpp = NULL;
+				error = ENOENT;
+				goto out;
+			}
 			*vpp = vp;
 			goto out;
 		}
@@ -342,6 +355,19 @@
 
 	vp = *vpp;
 
+	/*
+	 * The node could have been unlinked and destroyed by a concurrent
+	 * devfs_freep() while devfs_lock was dropped (freep defers the
+	 * physical free while DEVFS_ALLOCVINPROG is set).  Revalidate.
+	 */
+	if (node->flags & DEVFS_DESTROYED) {
+		vp->v_type = VBAD;
+		vx_put(vp);
+		*vpp = NULL;
+		error = ENOENT;
+		goto out;
+	}
+
 	if (node->v_node != NULL) {
 		vp->v_type = VBAD;
 		vx_put(vp);
@@ -386,6 +412,17 @@
 	vx_downgrade(vp);	/* downgrade VX lock to VN lock */
 
 out:
+	/*
+	 * We are the last potential user of the node.  Clear the
+	 * in-progress flag and, if a devfs_freep() ran while we were
+	 * unlocked (deferring its physical destruction), complete it.
+	 */
+	KKASSERT(lockstatus(&devfs_lock, curthread) == LK_EXCLUSIVE);
+	if (node->flags & DEVFS_ALLOCVINPROG) {
+		node->flags &= ~DEVFS_ALLOCVINPROG;
+		if (node->flags & DEVFS_DESTROYED)
+			devfs_freep(node);	/* completes deferred free */
+	}
 	return error;
 }
 
@@ -440,7 +477,11 @@
 	 * issues.
 	 */
 	if (node->flags & DEVFS_DESTROYED) {
-		if ((node->flags & DEVFS_NLINKSWAIT) &&
+		if (node->flags & DEVFS_FREEWAIT) {
+			/* completion from devfs_allocv(): physically free */
+			kfree(node->d_dir.d_name, M_DEVFS);
+			objcache_put(devfs_node_cache, node);
+		} else if ((node->flags & DEVFS_NLINKSWAIT) &&
 		    node->nlinks == 0) {
 			kprintf("devfs: final node '%s' on nlinks\n",
 				node->d_dir.d_name);
@@ -525,6 +566,13 @@
 	 */
 	if (node->nlinks) {
 		node->flags |= DEVFS_NLINKSWAIT;
+	} else if (node->flags & DEVFS_ALLOCVINPROG) {
+		/*
+		 * A devfs_allocv() is active on this node with devfs_lock
+		 * dropped.  Defer the physical free; allocv() completes it
+		 * once it revalidates the node.
+		 */
+		node->flags |= DEVFS_FREEWAIT;
 	} else {
 		if (node->d_dir.d_name) {
 			kfree(node->d_dir.d_name, M_DEVFS);