⬢ DragonFlyBSD Kernel Audit
DF-3191 / fix.diff
← back to finding ↓ download raw
--- a/sys/vfs/fuse/fuse_vfsops.c	2026-09-08 06:30:24.086056927 +0000
+++ b/sys/vfs/fuse/fuse_vfsops.c	2026-09-08 06:30:24.110056618 +0000
@@ -165,6 +165,19 @@
 		vrele(devvp);
 		return EBADF;
 	}
+
+	/*
+	 * DF-3192: args.fd must be the /dev/fuse descriptor that was just
+	 * looked up (devvp).  devfs_get_cdevpriv() returns whatever private
+	 * data ANY driver attached to the fd; passing e.g. a /dev/cuse fd
+	 * made the kernel type-confuse that driver's private object with
+	 * struct fuse_mount and scribble a fuse_mount layout into it.
+	 */
+	if (file->f_type != DTYPE_VNODE || file->f_data != devvp) {
+		dropfp(curthread, args.fd, file);
+		vrele(devvp);
+		return EINVAL;
+	}
 	error = devfs_get_cdevpriv(file, (void**)&fmp);
 	dropfp(curthread, args.fd, file);
 	if (error) {
@@ -173,7 +186,18 @@
 	}
 	KKASSERT(fmp);
 
-	fmp->mp = mp;
+	/*
+	 * DF-3191: one /dev/fuse descriptor serves exactly one mount.
+	 * Without this guard a second mount(2) on the same fd re-initializes
+	 * the live fmp (locks, IPC lists, node tree, rfnp) and the first
+	 * unmount tears down the second mount's state (use-after-free /
+	 * panic).  Atomically claim the association.
+	 */
+	if (atomic_cmpset_ptr(&fmp->mp, (uintptr_t)NULL, (uintptr_t)mp) == 0) {
+		vrele(devvp);
+		return EBUSY;
+	}
+
 	fmp->dead = false;
 	mtx_init(&fmp->mnt_lock, "fuse_mnt_lock");
 	mtx_init(&fmp->ipc_lock, "fuse_ipc_lock");
@@ -209,6 +233,7 @@
 
 	error = fuse_ipc_tx(fip);
 	if (error) {
+		fmp->mp = NULL;		/* release claim: fd may be retried */
 		vrele(devvp);
 		return error;
 	}
@@ -220,6 +245,7 @@
 
 	if (fuse_cmp_version(fmp, 7, 0) < 0) {
 		fuse_ipc_put(fip);
+		fmp->mp = NULL;		/* release claim: fd may be retried */
 		vrele(devvp);
 		return EPROTONOSUPPORT;
 	}
@@ -272,6 +298,20 @@
 		fuse_mount_kill(fmp);
 	}
 
+	/*
+	 * DF-3193: FUSE is MPSAFE, so VFS_STATFS/VFS_STATVFS are not
+	 * serialized against unmount.  Wait for in-flight IPCs to leave
+	 * the queues before tearing down fmp; otherwise their wait/timeout
+	 * paths wake up on freed memory.  Owners remove themselves on
+	 * timeout (bounded by fuse_ipc_wait retries), so this terminates.
+	 */
+	mtx_lock(&fmp->ipc_lock);
+	while (!TAILQ_EMPTY(&fmp->reply_head) ||
+	       !TAILQ_EMPTY(&fmp->request_head)) {
+		mtxsleep(&fmp->reply_head, &fmp->ipc_lock, 0, "fdrain", hz);
+	}
+	mtx_unlock(&fmp->ipc_lock);
+
 	/* Wait for helper thread to exit */
 	while (fmp->helper_td) {
 		wakeup(&fmp->helper_td);
--- a/sys/vfs/fuse/fuse_ipc.c	2026-09-08 06:30:24.086056927 +0000
+++ b/sys/vfs/fuse/fuse_ipc.c	2026-09-08 06:30:24.126056414 +0000
@@ -162,6 +162,7 @@
 
 	if (fuse_test_dead(fip->fmp)) {
 		KKASSERT(!fuse_ipc_test_replied(fip));
+		fuse_ipc_remove(fip);	/* unqueue before caller put()s us */
 		fuse_ipc_set_replied(fip);
 		return ENOTCONN;
 	}
@@ -263,6 +264,17 @@
 	wakeup(fmp);
 	KNOTE(&fmp->kq.ki_note, 0);
 
+	/*
+	 * DF-3193: fuse_mount_kill() may have set dead between our entry
+	 * check and the enqueue.  Re-check and unqueue ourselves so the
+	 * unmount drain cannot wait on a request nobody will answer.
+	 */
+	if (__predict_false(fuse_test_dead(fmp))) {
+		fuse_ipc_remove(fip);
+		fuse_ipc_put(fip);
+		return ENOTCONN;
+	}
+
 	error = fuse_ipc_wait(fip);
 	KKASSERT(fuse_ipc_test_replied(fip));
 	if (error) {
@@ -304,6 +316,12 @@
 	wakeup(fmp);
 	KNOTE(&fmp->kq.ki_note, 0);
 
+	if (__predict_false(fuse_test_dead(fmp))) {
+		fuse_ipc_remove(fip);
+		fuse_ipc_put(fip);
+		return ENOTCONN;
+	}
+
 	error = fuse_ipc_wait_sent(fip);
 	if (error)
 		fuse_ipc_put(fip);