⬢ DragonFlyBSD Kernel Audit
DF-3195 / fix.diff
← back to finding ↓ download raw
--- a/sys/vfs/nfs/nfsnode.h
+++ b/sys/vfs/nfs/nfsnode.h
@@ -152,7 +152,7 @@
 #define	NLMODIFIED	0x0004	/* Client has pending modifications */
 #define	NWRITEERR	0x0008	/* Flag write errors so close will know */
 #define	NREMOVED	0x0020	/* Flag possible removal of file */
-#define	NUNUSED040	0x0040
+#define	NSILLY		0x0040	/* n_un3 currently holds a sillyrename */
 #define	NQNFSEVICTED	0x0080	/* Has been evicted */
 #define	NACC		0x0100	/* Special file accessed */
 #define	NUPD		0x0200	/* Special file updated */
--- a/sys/vfs/nfs/nfs_node.c
+++ b/sys/vfs/nfs/nfs_node.c
@@ -389,9 +389,19 @@
 	np = VTONFS(ap->a_vp);
 	if (prtactive && VREFCNT(ap->a_vp) > 1)
 		vprint("nfs_inactive: pushing active", ap->a_vp);
-	if (ap->a_vp->v_type != VDIR) {
+	/*
+	 * Discriminate the n_un3 union (nf_silly vs nd_cook) with the
+	 * NSILLY flag, NOT with vp->v_type: the type of a live node is
+	 * mutable at runtime (nfs_loadattrcache -> nfs_setvtype applies
+	 * server-claimed ftype to existing vnodes), so a pending
+	 * sillyrename on a node retyped VREG->VDIR must still be flushed
+	 * here, and a VDIR node with cookies must never be walked as a
+	 * sillyrename.
+	 */
+	if (np->n_flag & NSILLY) {
 		sp = np->n_sillyrename;
 		np->n_sillyrename = NULL;
+		np->n_flag &= ~NSILLY;
 	} else {
 		sp = NULL;
 	}
@@ -463,13 +473,32 @@
 	 * large file handle structures that might be associated with
 	 * this nfs node.
 	 */
-	if (vp->v_type == VDIR) {
+	/*
+	 * Same NSILLY discipline as nfs_inactive(): only walk the cookie
+	 * list if the union actually holds one.  A server-side retype of
+	 * a sillyrenamed file to VDIR used to make this loop interpret
+	 * the struct sillyrename (M_NFSREQ) as an nfsdmap list and
+	 * kfree() it (and the s_cred ucred it points at) as M_NFSDIROFF,
+	 * chasing wild pointers from the freed objects.
+	 */
+	if (vp->v_type == VDIR && (np->n_flag & NSILLY) == 0) {
 		dp = np->n_cookies.lh_first;
 		while (dp) {
 			dp2 = dp;
 			dp = dp->ndm_list.le_next;
 			kfree((caddr_t)dp2, M_NFSDIROFF);
 		}
+	} else if (np->n_flag & NSILLY) {
+		/* unreachable safety net: inactive normally flushes it */
+		struct sillyrename *sp = np->n_sillyrename;
+
+		np->n_sillyrename = NULL;
+		np->n_flag &= ~NSILLY;
+		if (sp) {
+			crfree(sp->s_cred);
+			vrele(sp->s_dvp);
+			kfree(sp, M_NFSREQ);
+		}
 	}
 	if (np->n_fhsize > NFS_SMALLFH) {
 		kfree((caddr_t)np->n_fhp, M_NFSBIGFH);
--- a/sys/vfs/nfs/nfs_vnops.c
+++ b/sys/vfs/nfs/nfs_vnops.c
@@ -3036,6 +3036,7 @@
 	error = nfs_lookitup(dvp, sp->s_name, sp->s_namlen, sp->s_cred,
 		cnp->cn_td, &np);
 	np->n_sillyrename = sp;
+	np->n_flag |= NSILLY;
 	return (0);
 bad:
 	vrele(sp->s_dvp);