DragonFlyBSD Kernel Audit
DF-0044 / fix.diff
← back to finding ↓ download raw
diff --git a/sys/kern/vfs_mount.c b/sys/kern/vfs_mount.c
index 0000000..1111111 100644
@@ -1242,6 +1242,15 @@
 		if (ncp == mp->mnt_ncmountpt.ncp)
 			break;
 	}
+	/*
+	 * Match the hold contract of vfs_getvfs() and mountlist_scan():
+	 * the returned mp (if any) is held and the caller must drop it via
+	 * mount_drop().  Without this, the caller (cache_fullpath) would
+	 * dereference mp->mnt_ncmounton after we release mountlist_token,
+	 * racing dounmount()->mount_drop()->kfree() and causing a UAF.
+	 */
+	if (mp)
+		mount_hold(mp);
 	lwkt_reltoken(&mountlist_token);
 
 	return (mp);

diff --git a/sys/kern/vfs_cache.c b/sys/kern/vfs_cache.c
index 0000000..1111111 100644
@@ -5202,6 +5202,7 @@
 	mp = nch.mount;
 
 	while (ncp && (ncp != fd_nrdir.ncp || mp != fd_nrdir.mount)) {
+		struct mount *held_mp = NULL;
 		new_mp = NULL;
 
 		/*
@@ -5211,13 +5212,24 @@
 		 * ncp.
 		 */
 		if (guess && (ncp->nc_flag & NCF_ISMOUNTPT)) {
-			new_mp = mount_get_by_nc(ncp);
+			/*
+			 * Post-fix in vfs_mount.c: mount_get_by_nc() now
+			 * returns mp held; we must mount_drop() it once we
+			 * are done dereferencing new_mp->mnt_ncmounton.
+			 */
+			held_mp = mount_get_by_nc(ncp);
+			new_mp = held_mp;
 		}
 		/*
 		 * While traversing upwards if we encounter the root
 		 * of the current mount we have to skip to the mount point.
 		 */
 		if (ncp == mp->mnt_ncmountpt.ncp) {
+			/* Override: drop the held mp from mount_get_by_nc, if any. */
+			if (held_mp) {
+				mount_drop(held_mp);
+				held_mp = NULL;
+			}
 			new_mp = mp;
 		}
 		if (new_mp) {
@@ -5227,6 +5239,8 @@
 			if (ncp)
 				_cache_hold(ncp);
 			mp = nch.mount;
+			if (held_mp)
+				mount_drop(held_mp);
 			continue;
 		}