⬢ DragonFlyBSD Kernel Audit
DF-2998 / fix.diff
← back to finding ↓ download raw
--- a/sys/vfs/nfs/nfs_subs.c
+++ b/sys/vfs/nfs/nfs_subs.c
@@ -718,19 +718,39 @@
 	 * device, and if so, check for a possible alias. Once the
 	 * correct vnode has been obtained, fill in the rest of the
 	 * information.
+	 *
+	 * DF-2998: retyping a LIVE (already-typed) vnode from server
+	 * attributes is not legitimate -- a file handle identifies an
+	 * object whose type does not change over its lifetime.  A
+	 * malicious server used this to flip v_type/v_ops mid-life
+	 * (VREG->VDIR, VREG->VCHR with server rdev, VDIR->VREG),
+	 * corrupting type-dependent state: nfs_inactive()'s sillyrename
+	 * branch reads n_un3.nf_silly which aliases the VDIR cookie
+	 * list head (kernel panic in nfs_removerpc), and the
+	 * VDIR->VREG case feeds directory buffer state to nvtruncbuf
+	 * (bgetvp "overlapping buffer" panic).  Only assign the type
+	 * at creation (VNON); if a live vnode's type differs, refuse
+	 * the attributes, invalidate the cache, and fail the load so
+	 * callers treat the reply as bogus.
 	 */
 	np = VTONFS(vp);
 	if (vp->v_type != vtyp) {
-		nfs_setvtype(vp, vtyp);
-		if (vp->v_type == VFIFO) {
-			vp->v_ops = &vp->v_mount->mnt_vn_fifo_ops;
-		} else if (vp->v_type == VCHR || vp->v_type == VBLK) {
-			vp->v_ops = &vp->v_mount->mnt_vn_spec_ops;
-			addaliasu(vp, rmajor, rminor);
+		if (vp->v_type == VNON) {
+			nfs_setvtype(vp, vtyp);
+			if (vp->v_type == VFIFO) {
+				vp->v_ops = &vp->v_mount->mnt_vn_fifo_ops;
+			} else if (vp->v_type == VCHR ||
+				  vp->v_type == VBLK) {
+				vp->v_ops = &vp->v_mount->mnt_vn_spec_ops;
+				addaliasu(vp, rmajor, rminor);
+			} else {
+				vp->v_ops = &vp->v_mount->mnt_vn_use_ops;
+			}
+			np->n_mtime = mtime.tv_sec;
 		} else {
-			vp->v_ops = &vp->v_mount->mnt_vn_use_ops;
+			np->n_attrstamp = 0;
+			return (EBADRPC);
 		}
-		np->n_mtime = mtime.tv_sec;
 	} else if (np->n_mtime != mtime.tv_sec) {
 		/*
 		 * If we haven't modified the file locally and the server