⬢ DragonFlyBSD Kernel Audit
DF-4148 / fix.diff
← back to finding ↓ download raw
--- a/sys/dev/misc/cuse/cuse.c
+++ b/sys/dev/misc/cuse/cuse.c
@@ struct cuse_server {
 	struct kqinfo selinfo;
 	pid_t	pid;
 	int	is_closing;
+	int	dying;			/* guarded by cuse_global_mtx */
 	int	refs;
 };
@@ static void
 cuse_server_free_dev(struct cuse_server_dev *pcsd)
 {
@@
 	/* prevent creation of more devices */
 	cuse_server_lock(pcs);
 	if (pcsd->kern_dev != NULL)
+		cuse_global_lock();	/* pairs with open-side si_drv1 read */
 		pcsd->kern_dev->si_drv1 = NULL;
+		cuse_global_unlock();
@@ static void
 cuse_server_unref(struct cuse_server *pcs)
 {
@@
-	cuse_server_lock(pcs);
-	if (--(pcs->refs) != 0) {
-		cuse_server_unlock(pcs);
+	cuse_global_lock();
+	if (--(pcs->refs) != 0) {
+		cuse_global_unlock();
 		return;
 	}
+	pcs->dying = 1;
+	cuse_global_unlock();
+
+	cuse_server_lock(pcs);
 	cuse_server_is_closing(pcs);
@@ static int
 cuse_client_open(struct dev_open_args *ap)
 {
 	struct cdev *dev = ap->a_head.a_dev;
-	struct cuse_client_command *pccmd;
-	int fflags = ap->a_oflags;
-	struct cuse_server_dev *pcsd;
-	struct cuse_client *pcc;
-	struct cuse_server *pcs;
-	struct cuse_dev *pcd;
+	struct cuse_client_command *pccmd;
+	int fflags = ap->a_oflags;
+	struct cuse_server_dev *pcsd;
+	struct cuse_client *pcc;
+	struct cuse_server *pcs;
+	struct cuse_dev *pcd;
 	int error;
 	int n;
 
+	/*
+	 * Serialize against the final cuse_server_unref(): the si_drv1
+	 * read, the dying check and the refs increment must happen
+	 * atomically with respect to the teardown path, otherwise an
+	 * in-flight open can resurrect a server whose memory is about
+	 * to be freed (refs 0 -> 1 after the final drop).
+	 */
+	cuse_global_lock();
 	pcsd = dev->si_drv1;
 	if (pcsd != NULL) {
 		pcs = pcsd->server;
 		pcd = pcsd->user_dev;
+		if (pcs->dying) {
+			cuse_global_unlock();
+			return (EINVAL);
+		}
 
-		cuse_server_lock(pcs);
 		/*
 		 * Check that the refcount didn't wrap and that the
 		 * same process is not both client and server.  This
 		 * can easily lead to deadlocks when destroying the
 		 * CUSE character device nodes:
 		 */
 		pcs->refs++;
 		if (pcs->refs < 0 || pcs->pid == curproc->p_pid) {
 			/* overflow or wrong PID */
 			pcs->refs--;
-			cuse_server_unlock(pcs);
+			cuse_global_unlock();
 			return (EINVAL);
 		}
-		cuse_server_unlock(pcs);
+		cuse_global_unlock();
 	} else {
+		cuse_global_unlock();
 		return (EINVAL);
 	}