DragonFlyBSD Kernel Audit
DF-2918 / fix.diff
← back to finding ↓ download raw
--- a/sys/kern/vfs_init.c	2026-09-03 10:26:07.445442997 +0000
+++ b/sys/kern/vfs_init.c	2026-09-03 10:52:21.885651979 +0000
@@ -78,6 +78,7 @@
 #include <sys/vnode.h>
 #include <sys/malloc.h>
 #include <sys/objcache.h>
+#include <sys/lock.h>
 
 static MALLOC_DEFINE(M_VNODEOP, "vnodeops", "vnode operations vectors");
 static MALLOC_DEFINE(M_NAMEI, "nameibufs", "namei path buffers");
@@ -90,6 +91,41 @@
 static TAILQ_HEAD(, vnodeopv_node) vnodeopv_list;
 static void vfs_calc_vnodeops(struct vop_ops *ops);
 
+/*
+ * Registry lock.  Held shared by lookups, iteration and the atomic
+ * lookup+refcount acquisition in vfsconf_acquire(); held exclusive by
+ * registration and by vfs_unregister's refcount-check/remove critical
+ * section.  This closes the TOCTOU where a kldunload could observe a
+ * zero vfc_refcount while a concurrent sys_mount() was between its
+ * vfsconf_find_by_name() and vfc_refcount++, and unregisters/frees the
+ * vfsconf (module .data) out from under the mounting thread.
+ */
+static struct lock vfsconf_lk;
+
+/*
+ * Number of mount(2) syscalls currently in flight.  vfs_unregister()
+ * refuses to tear a filesystem module down while any mount syscall is
+ * running: sys_mount() touches module memory (vfsconf/vfsops) from
+ * before its vfsconf_acquire() until after VFS_MOUNT(), and its
+ * auto-load path calls into the linker while holding the mountpoint
+ * vnode lock — which can deadlock against kldunload holding the linker
+ * lock through module teardown.  This veto removes the whole class of
+ * mount-vs-module-unload interleavings.
+ */
+static volatile int vfsconf_mounts_inflight;
+
+void
+vfsconf_mount_begin(void)
+{
+	atomic_add_int(&vfsconf_mounts_inflight, 1);
+}
+
+void
+vfsconf_mount_end(void)
+{
+	atomic_add_int(&vfsconf_mounts_inflight, -1);
+}
+
 
 /*
  * Add a vnode operations (vnops) vector to the global list.
@@ -204,6 +240,7 @@
 vfsinit(void *dummy)
 {
 	TAILQ_INIT(&vnodeopv_list);
+	lockinit(&vfsconf_lk, "vfscflk", 0, 0);
 	namei_oc = objcache_create_simple(M_NAMEI, MAXPATHLEN);
 
 	/*
@@ -237,8 +274,9 @@
 static STAILQ_HEAD(, vfsconf) vfsconf_list = 
 	STAILQ_HEAD_INITIALIZER(vfsconf_list);
 
-struct vfsconf *
-vfsconf_find_by_name(const char *name) 
+/* caller must hold vfsconf_lk (shared or exclusive) */
+static struct vfsconf *
+vfsconf_find_by_name_locked(const char *name)
 {
 	struct vfsconf *vfsp;
 
@@ -250,7 +288,19 @@
 }
 
 struct vfsconf *
-vfsconf_find_by_typenum(int typenum) 
+vfsconf_find_by_name(const char *name) 
+{
+	struct vfsconf *vfsp;
+
+	lockmgr(&vfsconf_lk, LK_SHARED);
+	vfsp = vfsconf_find_by_name_locked(name);
+	lockmgr(&vfsconf_lk, LK_RELEASE);
+	return vfsp;
+}
+
+/* caller must hold vfsconf_lk (shared or exclusive) */
+static struct vfsconf *
+vfsconf_find_by_typenum_locked(int typenum)
 {
 	struct vfsconf *vfsp;
 
@@ -261,6 +311,44 @@
 	return vfsp;
 }
 
+struct vfsconf *
+vfsconf_find_by_typenum(int typenum) 
+{
+	struct vfsconf *vfsp;
+
+	lockmgr(&vfsconf_lk, LK_SHARED);
+	vfsp = vfsconf_find_by_typenum_locked(typenum);
+	lockmgr(&vfsconf_lk, LK_RELEASE);
+	return vfsp;
+}
+
+/*
+ * Acquire a counted reference on a filesystem type by name.  The
+ * refcount increment is atomic with the list lookup (the shared
+ * registry lock excludes vfs_unregister()'s exclusive check-and-remove
+ * section), so a vfsconf returned here cannot be unregistered/freed
+ * while the reference is held.  Pair with vfsconf_release().
+ */
+struct vfsconf *
+vfsconf_acquire(const char *name)
+{
+	struct vfsconf *vfsp;
+
+	lockmgr(&vfsconf_lk, LK_SHARED);
+	vfsp = vfsconf_find_by_name_locked(name);
+	if (vfsp != NULL)
+		atomic_add_int(&vfsp->vfc_refcount, 1);
+	lockmgr(&vfsconf_lk, LK_RELEASE);
+	return vfsp;
+}
+
+void
+vfsconf_release(struct vfsconf *vfc)
+{
+	atomic_add_int(&vfc->vfc_refcount, -1);
+}
+
+/* caller must hold vfsconf_lk exclusively */
 static void
 vfsconf_add(struct vfsconf *vfc)
 {
@@ -268,6 +356,7 @@
 	STAILQ_INSERT_TAIL(&vfsconf_list, vfc, vfc_next);
 }
 
+/* caller must hold vfsconf_lk exclusively */
 static void
 vfsconf_remove(struct vfsconf *vfc)
 {
@@ -292,6 +381,8 @@
 /*
  * Iterate over all vfsconf entries. Break out of the iterator
  * by returning != 0.
+ *
+ * The registry lock is held shared across the iteration.
  */
 int
 vfsconf_each(int (*iter)(struct vfsconf *element, void *data), void *data)
@@ -299,12 +390,15 @@
 	int error;
 	struct vfsconf *vfsp;
 
+	error = 0;
+	lockmgr(&vfsconf_lk, LK_SHARED);
 	STAILQ_FOREACH(vfsp, &vfsconf_list, vfc_next) {
 		error = iter(vfsp, data);
 		if (error)
-			return (error);
+			break;
 	}
-	return (0);
+	lockmgr(&vfsconf_lk, LK_RELEASE);
+	return (error);
 }
 
 /*
@@ -320,10 +414,14 @@
 	struct sysctl_oid *oidp;
 	struct vfsops *vfsops = NULL;
 
-	if (vfsconf_find_by_name(vfc->vfc_name) != NULL)
+	lockmgr(&vfsconf_lk, LK_EXCLUSIVE);
+	if (vfsconf_find_by_name_locked(vfc->vfc_name) != NULL) {
+		lockmgr(&vfsconf_lk, LK_RELEASE);
 		return EEXIST;
+	}
 
 	vfsconf_add(vfc);
+	lockmgr(&vfsconf_lk, LK_RELEASE);
 
 	/*
 	 * If this filesystem has a sysctl node under vfs
@@ -461,21 +559,44 @@
 	struct vfsconf *vfsp;
 	int error;
 
-	vfsp = vfsconf_find_by_name(vfc->vfc_name);
+	/*
+	 * The refcount check and the removal are one exclusive critical
+	 * section, excluding vfsconf_acquire() (shared), so a mount can
+	 * never race a reference onto a vfsconf that is being torn down.
+	 */
+	lockmgr(&vfsconf_lk, LK_EXCLUSIVE);
+	vfsp = vfsconf_find_by_name_locked(vfc->vfc_name);
 
-	if (vfsp == NULL)
+	if (vfsp == NULL) {
+		lockmgr(&vfsconf_lk, LK_RELEASE);
 		return EINVAL;
+	}
+
+	if (vfsp->vfc_refcount != 0) {
+		lockmgr(&vfsconf_lk, LK_RELEASE);
+		return EBUSY;
+	}
 
-	if (vfsp->vfc_refcount != 0)
+	/*
+	 * Refuse to race any in-flight mount(2): mounts touch the module's
+	 * vfsconf/vfsops (and the linker, for auto-loaded types) from
+	 * before the refcount acquisition until after VFS_MOUNT().
+	 */
+	if (vfsconf_mounts_inflight != 0) {
+		lockmgr(&vfsconf_lk, LK_RELEASE);
 		return EBUSY;
+	}
 
 	if (vfc->vfc_vfsops->vfs_uninit != NULL) {
 		error = vfs_uninit(vfc, vfsp);
-		if (error)
+		if (error) {
+			lockmgr(&vfsconf_lk, LK_RELEASE);
 			return (error);
+		}
 	}
 
 	vfsconf_remove(vfsp);
+	lockmgr(&vfsconf_lk, LK_RELEASE);
 	return 0;
 }
 
--- a/sys/kern/vfs_mount.c	2026-09-03 10:26:07.445442997 +0000
+++ b/sys/kern/vfs_mount.c	2026-09-03 10:27:06.928684162 +0000
@@ -337,7 +337,7 @@
 	if (fstypename == NULL)
 		return (ENODEV);
 
-	vfsp = vfsconf_find_by_name(fstypename);
+	vfsp = vfsconf_acquire(fstypename);
 	if (vfsp == NULL)
 		return (ENODEV);
 	mp = kmalloc(sizeof(struct mount), M_MOUNT, M_WAITOK | M_ZERO);
@@ -348,7 +348,6 @@
 	vfs_busy(mp, 0);
 	mp->mnt_vfc = vfsp;
 	mp->mnt_pbuf_count = nswbuf_kva / NSWBUF_SPLIT;
-	vfsp->vfc_refcount++;
 	mp->mnt_stat.f_type = vfsp->vfc_typenum;
 	mp->mnt_flag |= MNT_RDONLY;
 	mp->mnt_flag |= vfsp->vfc_flags & MNT_VISFLAGMASK;
--- a/sys/kern/vfs_syscalls.c	2026-09-03 10:26:07.445442997 +0000
+++ b/sys/kern/vfs_syscalls.c	2026-09-03 10:52:54.205243728 +0000
@@ -133,6 +133,14 @@
 
 	cred = td->td_ucred;
 
+	/*
+	 * Mark the mount syscall in-flight so vfs module teardown
+	 * (vfs_unregister via kldunload) vetoes with EBUSY instead of
+	 * racing us for the vfsconf/vfsops lifetime or deadlocking
+	 * against the auto-load path below.
+	 */
+	vfsconf_mount_begin();
+
 	/* We do not allow user mounts inside a jail for now */
 	if (usermount && jailed(cred)) {
 		error = EPERM;
@@ -310,7 +318,7 @@
 		error = EPERM;
 		goto done;
 	}
-	vfsp = vfsconf_find_by_name(fstypename);
+	vfsp = vfsconf_acquire(fstypename);
 	if (vfsp == NULL) {
 		linker_file_t lf;
 
@@ -331,7 +339,7 @@
 		}
 		lf->userrefs++;
 		/* lookup again, see if the VFS was loaded */
-		vfsp = vfsconf_find_by_name(fstypename);
+		vfsp = vfsconf_acquire(fstypename);
 		if (vfsp == NULL) {
 			lf->userrefs--;
 			linker_file_unload(lf);
@@ -356,7 +364,6 @@
 	vfs_busy(mp, LK_NOWAIT);
 	mp->mnt_vfc = vfsp;
 	mp->mnt_pbuf_count = nswbuf_kva / NSWBUF_SPLIT;
-	vfsp->vfc_refcount++;
 	mp->mnt_stat.f_type = vfsp->vfc_typenum;
 	mp->mnt_flag |= vfsp->vfc_flags & MNT_VISFLAGMASK;
 	strncpy(mp->mnt_stat.f_fstypename, vfsp->vfc_name, MFSNAMELEN);
@@ -465,7 +472,7 @@
 			crfree(mp->mnt_cred);
 			mp->mnt_cred = NULL;
 		}
-		mp->mnt_vfc->vfc_refcount--;
+		vfsconf_release(mp->mnt_vfc);
 		lwkt_reltoken(&mp->mnt_token);
 		vfs_unbusy(mp);
 		kfree(mp, M_MOUNT);
@@ -473,6 +480,7 @@
 		vput(vp);
 	}
 done:
+	vfsconf_mount_end();
 	return (error);
 }
 
@@ -1076,7 +1084,7 @@
 		mp->mnt_cred = NULL;
 	}
 
-	mp->mnt_vfc->vfc_refcount--;
+	vfsconf_release(mp->mnt_vfc);
 
 	/*
 	 * If not quickhalting the mount, we expect there to be no
--- a/sys/sys/mount.h	2026-09-03 10:26:07.449442946 +0000
+++ b/sys/sys/mount.h	2026-09-03 10:52:43.601377687 +0000
@@ -532,6 +532,12 @@
 struct vfsconf *vfsconf_find_by_name(const char *);
 struct vfsconf *vfsconf_find_by_typenum(int);
 int vfsconf_get_maxtypenum(void);
+
+struct vfsconf *vfsconf_acquire(const char *);
+void vfsconf_release(struct vfsconf *);
+void vfsconf_mount_begin(void);
+void vfsconf_mount_end(void);
+
 int vfsconf_each(int (*)(struct vfsconf *, void *), void *);
 
 #endif