⬢ DragonFlyBSD Kernel Audit
DF-3194 / fix.diff
← back to finding ↓ download raw
--- a/sys/vfs/nfs/nfs_node.c
+++ b/sys/vfs/nfs/nfs_node.c
@@ -191,13 +191,23 @@
 		    bcmp((caddr_t)fhp, (caddr_t)np2->n_fhp, fhsize)) {
 			continue;
 		}
+		/*
+		 * Connect the fresh (still VNON / v_data == NULL) vnode to
+		 * np before dropping it: vx_put() forces VREF_FINALIZE for
+		 * VNON vnodes (sys/kern/vfs_lock.c), running
+		 * VOP_INACTIVE -> nfs_inactive() and later
+		 * VOP_RECLAIM -> nfs_reclaim(), both of which dereference
+		 * vp->v_data.  A bare vx_put() of the unconnected vnode
+		 * NULL-derefs in nfs_inactive (np = VTONFS(vp) == NULL at
+		 * nfs_node.c:393) and panics.  Leaving (np, vp) connected
+		 * lets reclaim tear the pair down correctly: nfs_reclaim()
+		 * sees n_hash.le_prev == NULL, skips the LIST_REMOVE, and
+		 * frees np (and a big n_fhp) for us.
+		 */
+		np->n_vnode = vp;
+		vp->v_data = np;
 		vx_put(vp);
 		lockmgr(&nfsnhash_lock, LK_RELEASE);
-
-		if (np->n_fhsize > NFS_SMALLFH)
-			kfree((caddr_t)np->n_fhp, M_NFSBIGFH);
-		np->n_fhp = NULL;
-		kfree_obj(np, nmp->nm_mnode);
 		goto retry;
 	}
 
@@ -270,6 +280,11 @@
 		}
 		if (NFSTOV(np) != vp) {
 			vput(vp);
+			/*
+			 * vp is stale after vput(): NULL it or the restart
+			 * double-vputs the same vnode (refcount underflow).
+			 */
+			vp = NULL;
 			goto loop;
 		}
 		*npp = np;
@@ -333,14 +348,15 @@
 		    bcmp((caddr_t)fhp, (caddr_t)np2->n_fhp, fhsize)) {
 			continue;
 		}
+		/*
+		 * Same discipline as nfs_nget(): never vx_put() a freshly
+		 * created VNON vnode with v_data still NULL (see above).
+		 */
+		np->n_vnode = vp;
+		vp->v_data = np;
 		vx_put(vp);
 		lockmgr(&nfsnhash_lock, LK_RELEASE);
 
-		if (np->n_fhsize > NFS_SMALLFH)
-			kfree((caddr_t)np->n_fhp, M_NFSBIGFH);
-		np->n_fhp = NULL;
-		kfree_obj(np, nmp->nm_mnode);
-
 		/*
 		 * vp state is retained on retry/loop so we must NULL it
 		 * out here or fireworks may ensue.