DragonFlyBSD Kernel Audit
DF-2774 / fix.diff
← back to finding ↓ download raw
--- a/sys/kern/vfs_mount.c	2026-09-01 02:05:26.425046951 +0000
+++ b/sys/kern/vfs_mount.c	2026-09-01 02:07:28.231508072 +0000
@@ -138,6 +138,7 @@
 
 static struct lwkt_token mntid_token;
 static struct mount dummymount;
+static volatile uint64_t mnt_cookie_gen;
 
 /* note: mountlist exported to pstat */
 struct mntlist mountlist = TAILQ_HEAD_INITIALIZER(mountlist);
@@ -380,6 +381,7 @@
 	TAILQ_INIT(&mp->mnt_nvnodelist);
 	TAILQ_INIT(&mp->mnt_reservedvnlist);
 	TAILQ_INIT(&mp->mnt_jlist);
+	mp->mnt_cookie = atomic_fetchadd_64(&mnt_cookie_gen, 1) + 1;
 	mp->mnt_nvnodelistsize = 0;
 	mp->mnt_flag = 0;
 	mp->mnt_hold = 1;		/* hold for umount last drop */
@@ -708,6 +710,39 @@
 }
 
 /*
+ * mountlist_hold (MP SAFE)
+ *
+ * Like mountlist_exists(), but if the mount is still on the mountlist
+ * a hold is acquired so the caller can safely use the returned mount.
+ * mountlist_exists() only compares pointers and gives the caller no
+ * lifetime guarantee whatsoever (a mount that is concurrently being
+ * unmounted can be ripped off the list and freed right after the
+ * check, and a mount reusing the freed structure's address makes the
+ * check succeed against the wrong mount).
+ *
+ * Returns the held mount (caller must mount_drop()), or NULL if the
+ * mount is no longer on the mountlist.
+ */
+struct mount *
+mountlist_hold(struct mount *mp)
+{
+	struct mount *found = NULL;
+	struct mount *lmp;
+
+	lwkt_gettoken(&mountlist_token);
+	TAILQ_FOREACH(lmp, &mountlist, mnt_list) {
+		if (lmp == mp) {
+			mount_hold(lmp);
+			found = lmp;
+			break;
+		}
+	}
+	lwkt_reltoken(&mountlist_token);
+
+	return (found);
+}
+
+/*
  * mountlist_scan
  *
  * Safely scan the mount points on the mount list.  Each mountpoint
--- a/sys/kern/vfs_quota.c	2026-09-01 02:05:26.425046951 +0000
+++ b/sys/kern/vfs_quota.c	2026-09-01 02:08:11.098966457 +0000
@@ -420,16 +420,29 @@
 struct mount*
 vq_vptomp(struct vnode *vp)
 {
-	/* XXX: vp->v_pfsmp may point to a freed structure
-	* we use mountlist_exists() to check if it is valid
-	* before using it */
-	if ((vp->v_pfsmp != NULL) && (mountlist_exists(vp->v_pfsmp))) {
-		/* This is a PFS, use a copy of the real mp */
-		return vp->v_pfsmp;
-	} else {
-		/* Not a PFS or a PFS beeing unmounted */
-		return vp->v_mount;
+	struct mount *mp;
+
+	/*
+	 * XXX: vp->v_pfsmp may point to a mount that has already been
+	 * unmounted and freed, or to a *different* mount that reused
+	 * the freed struct mount's address.  Take a real hold and
+	 * validate the generation cookie before using it.
+	 *
+	 * Returns a held mount; the caller must mount_drop() it.
+	 */
+	if (vp->v_pfsmp != NULL) {
+		mp = mountlist_hold(vp->v_pfsmp);
+		if (mp != NULL) {
+			if (mp->mnt_cookie == vp->v_pfsmp_cookie)
+				return mp;	/* held */
+			mount_drop(mp);	/* different mount reused the addr */
+		}
 	}
+	/* Not a PFS or the PFS is beeing unmounted */
+	mp = vp->v_mount;
+	mount_hold(mp);
+
+	return mp;
 }
 
 int
--- a/sys/kern/vfs_vnops.c	2026-09-01 02:05:26.429046900 +0000
+++ b/sys/kern/vfs_vnops.c	2026-09-01 02:05:37.624905461 +0000
@@ -324,6 +324,7 @@
 			goto bad;
 		mp = vq_vptomp(vp);
 		VFS_ACCOUNT(mp, vap->va_uid, vap->va_gid, -osize);
+		mount_drop(mp);
 	}
 
 	/*
--- a/sys/kern/vfs_vopops.c	2026-09-01 02:05:26.429046900 +0000
+++ b/sys/kern/vfs_vopops.c	2026-09-01 02:05:42.544843305 +0000
@@ -456,7 +456,7 @@
 	int error, do_accounting = 0;
 	struct vattr va;
 	uint64_t size_before=0, size_after=0;
-	struct mount *mp;
+	struct mount *mp = NULL;
 	uint64_t offset, delta;
 
 	ap.a_head.a_desc = &vop_write_desc;
@@ -493,9 +493,12 @@
 	}
 	DO_OPS(ops, error, &ap, vop_write);
 	if ((error == 0) && do_accounting) {
+		KKASSERT(mp != NULL);
 		VFS_ACCOUNT(mp, va.va_uid, va.va_gid, size_after - size_before);
 	}
 done:
+	if (mp)
+		mount_drop(mp);
 	VFS_MPUNLOCK();
 
 	return(error);
--- a/sys/kern/vfs_cache.c	2026-09-01 02:07:20.219609296 +0000
+++ b/sys/kern/vfs_cache.c	2026-09-01 02:07:29.823487957 +0000
@@ -1384,8 +1384,10 @@
 		 * implementation
 		 */
 		if (mp) {
-			if (strncmp(mp->mnt_stat.f_fstypename, "null", 5) == 0)
+			if (strncmp(mp->mnt_stat.f_fstypename, "null", 5) == 0) {
 				vp->v_pfsmp = mp;
+				vp->v_pfsmp_cookie = mp->mnt_cookie;
+			}
 		}
 	} else {
 		/*
--- a/sys/sys/mount.h	2026-09-01 02:05:26.433046850 +0000
+++ b/sys/sys/mount.h	2026-09-01 02:07:25.379544103 +0000
@@ -219,6 +219,7 @@
 	struct vfsconf	*mnt_vfc;		/* configuration info */
 	u_int		mnt_namecache_gen;	/* ++ to clear negative hits */
 	u_int		mnt_pbuf_count;		/* pbuf usage limit */
+	uint64_t	mnt_cookie;		/* unique generation cookie */
 	struct vnode	*mnt_syncer;		/* syncer vnode */
 	struct syncer_ctx *mnt_syncer_ctx;	/* syncer process context */
 	struct vnodelst	mnt_nvnodelist;		/* list of vnodes this mount */
@@ -806,6 +807,7 @@
 struct mount *mountlist_boot_getfirst(void);
 void	mountlist_remove(struct mount *mp);
 int	mountlist_exists(struct mount *mp);
+struct mount *mountlist_hold(struct mount *mp);
 int	mountlist_scan(int (*callback)(struct mount *, void *), void *, int);
 struct mount *mount_get_by_nc(struct namecache *ncp);
 #else /* !_KERNEL */
--- a/sys/sys/vnode.h	2026-09-01 02:07:20.219609296 +0000
+++ b/sys/sys/vnode.h	2026-09-01 02:07:23.955562095 +0000
@@ -191,6 +191,7 @@
 	} v_pollinfo;
 	struct vmresident *v_resident;		/* optional vmresident */
 	struct mount *v_pfsmp;			/* real mp for pfs/nullfs mt */
+	uint64_t v_pfsmp_cookie;		/* generation of v_pfsmp */
 	struct timespec v_lastwrite_ts;		/* async mmap flush ts */
 };
 #define	v_socket	v_un.vu_socket