Module Name:    src
Committed By:   riastradh
Date:           Thu Apr 30 16:50:01 UTC 2020

Modified Files:
        src/sys/kern: kern_entropy.c

Log Message:
Lock the rndsource list without E->lock for ioctls too.

Use the same mechanism as entropy_request, with a little more
diagnostic information in case anything goes wrong.  No need for
LIST_FOREACH_SAFE; elements cannot be deleted while the list is
locked.

This is part II of avoiding percpu_foreach with spin lock held.


To generate a diff of this commit:
cvs rdiff -u -r1.3 -r1.4 src/sys/kern/kern_entropy.c

Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.

Modified files:

Index: src/sys/kern/kern_entropy.c
diff -u src/sys/kern/kern_entropy.c:1.3 src/sys/kern/kern_entropy.c:1.4
--- src/sys/kern/kern_entropy.c:1.3	Thu Apr 30 16:43:12 2020
+++ src/sys/kern/kern_entropy.c	Thu Apr 30 16:50:00 2020
@@ -1,4 +1,4 @@
-/*	$NetBSD: kern_entropy.c,v 1.3 2020/04/30 16:43:12 riastradh Exp $	*/
+/*	$NetBSD: kern_entropy.c,v 1.4 2020/04/30 16:50:00 riastradh Exp $	*/
 
 /*-
  * Copyright (c) 2019 The NetBSD Foundation, Inc.
@@ -77,7 +77,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: kern_entropy.c,v 1.3 2020/04/30 16:43:12 riastradh Exp $");
+__KERNEL_RCSID(0, "$NetBSD: kern_entropy.c,v 1.4 2020/04/30 16:50:00 riastradh Exp $");
 
 #include <sys/param.h>
 #include <sys/types.h>
@@ -161,13 +161,13 @@ struct {
 	unsigned	epoch;		/* (A) changes when needed -> 0 */
 	kcondvar_t	cv;		/* notifies state changes */
 	struct selinfo	selq;		/* notifies needed -> 0 */
+	struct lwp	*sourcelock;	/* lock on list of sources */
 	LIST_HEAD(,krndsource) sources;	/* list of entropy sources */
 	enum entropy_stage {
 		ENTROPY_COLD = 0, /* single-threaded */
 		ENTROPY_WARM,	  /* multi-threaded at boot before CPUs */
 		ENTROPY_HOT,	  /* multi-threaded multi-CPU */
 	}		stage;
-	bool		requesting;	/* busy requesting from sources */
 	bool		consolidate;	/* kick thread to consolidate */
 	bool		seed_rndsource;	/* true if seed source is attached */
 	bool		seeded;		/* true if seed file already loaded */
@@ -1514,11 +1514,11 @@ rnd_detach_source(struct krndsource *rs)
 	/* We may have to wait for entropy_request.  */
 	ASSERT_SLEEPABLE();
 
-	/* Remove it from the list and wait for entropy_request.  */
+	/* Wait until the source list is not in use, and remove it.  */
 	mutex_enter(&E->lock);
-	LIST_REMOVE(rs, list);
-	while (E->requesting)
+	while (E->sourcelock)
 		cv_wait(&E->cv, &E->lock);
+	LIST_REMOVE(rs, list);
 	mutex_exit(&E->lock);
 
 	/* Free the per-CPU data.  */
@@ -1526,6 +1526,83 @@ rnd_detach_source(struct krndsource *rs)
 }
 
 /*
+ * rnd_lock_sources()
+ *
+ *	Prevent changes to the list of rndsources while we iterate it.
+ *	Interruptible.  Caller must hold the global entropy lock.  If
+ *	successful, no rndsource will go away until rnd_unlock_sources
+ *	even while the caller releases the global entropy lock.
+ */
+static int
+rnd_lock_sources(void)
+{
+	int error;
+
+	KASSERT(mutex_owned(&E->lock));
+
+	while (E->sourcelock) {
+		error = cv_wait_sig(&E->cv, &E->lock);
+		if (error)
+			return error;
+	}
+
+	E->sourcelock = curlwp;
+	return 0;
+}
+
+/*
+ * rnd_trylock_sources()
+ *
+ *	Try to lock the list of sources, but if it's already locked,
+ *	fail.  Caller must hold the global entropy lock.  If
+ *	successful, no rndsource will go away until rnd_unlock_sources
+ *	even while the caller releases the global entropy lock.
+ */
+static bool
+rnd_trylock_sources(void)
+{
+
+	KASSERT(E->stage == ENTROPY_COLD || mutex_owned(&E->lock));
+
+	if (E->sourcelock)
+		return false;
+	E->sourcelock = curlwp;
+	return true;
+}
+
+/*
+ * rnd_unlock_sources()
+ *
+ *	Unlock the list of sources after rnd_lock_sources or
+ *	rnd_trylock_sources.  Caller must hold the global entropy lock.
+ */
+static void
+rnd_unlock_sources(void)
+{
+
+	KASSERT(E->stage == ENTROPY_COLD || mutex_owned(&E->lock));
+
+	KASSERTMSG(E->sourcelock == curlwp, "lwp %p releasing lock held by %p",
+	    curlwp, E->sourcelock);
+	E->sourcelock = NULL;
+	if (E->stage >= ENTROPY_WARM)
+		cv_broadcast(&E->cv);
+}
+
+/*
+ * rnd_sources_locked()
+ *
+ *	True if we hold the list of rndsources locked, for diagnostic
+ *	assertions.
+ */
+static bool
+rnd_sources_locked(void)
+{
+
+	return E->sourcelock == curlwp;
+}
+
+/*
  * entropy_request(nbytes)
  *
  *	Request nbytes bytes of entropy from all sources in the system.
@@ -1535,7 +1612,7 @@ rnd_detach_source(struct krndsource *rs)
 static void
 entropy_request(size_t nbytes)
 {
-	struct krndsource *rs, *next;
+	struct krndsource *rs;
 
 	KASSERT(E->stage == ENTROPY_COLD || mutex_owned(&E->lock));
 
@@ -1544,16 +1621,15 @@ entropy_request(size_t nbytes)
 	 * Otherwise, note that a request is in progress to avoid
 	 * reentry and to block rnd_detach_source until we're done.
 	 */
-	if (E->requesting)
+	if (!rnd_trylock_sources())
 		return;
-	E->requesting = true;
 	entropy_request_evcnt.ev_count++;
 
 	/* Clamp to the maximum reasonable request.  */
 	nbytes = MIN(nbytes, ENTROPY_CAPACITY);
 
 	/* Walk the list of sources.  */
-	LIST_FOREACH_SAFE(rs, &E->sources, list, next) {
+	LIST_FOREACH(rs, &E->sources, list) {
 		/* Skip sources without callbacks.  */
 		if (!ISSET(rs->flags, RND_FLAG_HASCB))
 			continue;
@@ -1567,9 +1643,7 @@ entropy_request(size_t nbytes)
 	}
 
 	/* Notify rnd_detach_source that the request is done.  */
-	E->requesting = false;
-	if (E->stage >= ENTROPY_WARM)
-		cv_broadcast(&E->cv);
+	rnd_unlock_sources();
 }
 
 /*
@@ -1740,7 +1814,7 @@ rndsource_entropybits(struct krndsource 
 	unsigned nbits = rs->total;
 
 	KASSERT(E->stage >= ENTROPY_WARM);
-	KASSERT(mutex_owned(&E->lock));
+	KASSERT(rnd_sources_locked());
 	percpu_foreach(rs->state, rndsource_entropybits_cpu, &nbits);
 	return nbits;
 }
@@ -1766,7 +1840,7 @@ rndsource_to_user(struct krndsource *rs,
 {
 
 	KASSERT(E->stage >= ENTROPY_WARM);
-	KASSERT(mutex_owned(&E->lock));
+	KASSERT(rnd_sources_locked());
 
 	/* Avoid kernel memory disclosure.  */
 	memset(urs, 0, sizeof(*urs));
@@ -1789,7 +1863,7 @@ rndsource_to_user_est(struct krndsource 
 {
 
 	KASSERT(E->stage >= ENTROPY_WARM);
-	KASSERT(mutex_owned(&E->lock));
+	KASSERT(rnd_sources_locked());
 
 	/* Avoid kernel memory disclosure.  */
 	memset(urse, 0, sizeof(*urse));
@@ -1916,6 +1990,11 @@ entropy_ioctl(unsigned long cmd, void *d
 		 * as requested, and report how many we copied out.
 		 */
 		mutex_enter(&E->lock);
+		error = rnd_lock_sources();
+		if (error) {
+			mutex_exit(&E->lock);
+			return error;
+		}
 		LIST_FOREACH(rs, &E->sources, list) {
 			if (start++ == stat->start)
 				break;
@@ -1926,6 +2005,7 @@ entropy_ioctl(unsigned long cmd, void *d
 		}
 		KASSERT(i <= stat->count);
 		stat->count = i;
+		rnd_unlock_sources();
 		mutex_exit(&E->lock);
 		break;
 	}
@@ -1944,16 +2024,24 @@ entropy_ioctl(unsigned long cmd, void *d
 		 * as requested, and report how many we copied out.
 		 */
 		mutex_enter(&E->lock);
+		error = rnd_lock_sources();
+		if (error) {
+			mutex_exit(&E->lock);
+			return error;
+		}
 		LIST_FOREACH(rs, &E->sources, list) {
 			if (start++ == estat->start)
 				break;
 		}
 		while (i < estat->count && rs != NULL) {
+			mutex_exit(&E->lock);
 			rndsource_to_user_est(rs, &estat->source[i++]);
+			mutex_enter(&E->lock);
 			rs = LIST_NEXT(rs, list);
 		}
 		KASSERT(i <= estat->count);
 		estat->count = i;
+		rnd_unlock_sources();
 		mutex_exit(&E->lock);
 		break;
 	}
@@ -1968,14 +2056,23 @@ entropy_ioctl(unsigned long cmd, void *d
 		 * out; if not found, fail with ENOENT.
 		 */
 		mutex_enter(&E->lock);
+		error = rnd_lock_sources();
+		if (error) {
+			mutex_exit(&E->lock);
+			return error;
+		}
 		LIST_FOREACH(rs, &E->sources, list) {
 			if (strncmp(rs->name, nstat->name, n) == 0)
 				break;
 		}
-		if (rs != NULL)
+		if (rs != NULL) {
+			mutex_exit(&E->lock);
 			rndsource_to_user(rs, &nstat->source);
-		else
+			mutex_enter(&E->lock);
+		} else {
 			error = ENOENT;
+		}
+		rnd_unlock_sources();
 		mutex_exit(&E->lock);
 		break;
 	}
@@ -1990,14 +2087,23 @@ entropy_ioctl(unsigned long cmd, void *d
 		 * out; if not found, fail with ENOENT.
 		 */
 		mutex_enter(&E->lock);
+		error = rnd_lock_sources();
+		if (error) {
+			mutex_exit(&E->lock);
+			return error;
+		}
 		LIST_FOREACH(rs, &E->sources, list) {
 			if (strncmp(rs->name, enstat->name, n) == 0)
 				break;
 		}
-		if (rs != NULL)
+		if (rs != NULL) {
+			mutex_exit(&E->lock);
 			rndsource_to_user_est(rs, &enstat->source);
-		else
+			mutex_enter(&E->lock);
+		} else {
 			error = ENOENT;
+		}
+		rnd_unlock_sources();
 		mutex_exit(&E->lock);
 		break;
 	}

Reply via email to