Module Name:    src
Committed By:   riastradh
Date:           Sun Mar  9 18:11:55 UTC 2025

Modified Files:
        src/lib/libc/gen: arc4random.c
        src/lib/libc/include: arc4random.h
        src/tests/lib/libc/gen: t_arc4random.c

Log Message:
arc4random(3): Provide a fallback in case pthread_atfork fails.

This is considerably more work and burden on testing than simply
statically preallocating a bit of storage for pthread_atfork to
eliminate the failure mode altogether, but it is less work than
arguing further over the atfork interface:
https://mail-index.NetBSD.org/source-changes-d/2025/03/02/msg014387.html

PR lib/59117: arc4random has some failure modes it shouldn't


To generate a diff of this commit:
cvs rdiff -u -r1.45 -r1.46 src/lib/libc/gen/arc4random.c
cvs rdiff -u -r1.3 -r1.4 src/lib/libc/include/arc4random.h
cvs rdiff -u -r1.4 -r1.5 src/tests/lib/libc/gen/t_arc4random.c

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

Modified files:

Index: src/lib/libc/gen/arc4random.c
diff -u src/lib/libc/gen/arc4random.c:1.45 src/lib/libc/gen/arc4random.c:1.46
--- src/lib/libc/gen/arc4random.c:1.45	Thu Mar  6 00:54:27 2025
+++ src/lib/libc/gen/arc4random.c	Sun Mar  9 18:11:55 2025
@@ -1,4 +1,4 @@
-/*	$NetBSD: arc4random.c,v 1.45 2025/03/06 00:54:27 riastradh Exp $	*/
+/*	$NetBSD: arc4random.c,v 1.46 2025/03/09 18:11:55 riastradh Exp $	*/
 
 /*-
  * Copyright (c) 2014 The NetBSD Foundation, Inc.
@@ -42,17 +42,16 @@
  *
  * The arc4random(3) API may abort the process if:
  *
- * (a) the crypto self-test fails,
- * (b) pthread_atfork fails, or
- * (c) sysctl(KERN_ARND) fails when reseeding the PRNG.
+ * (a) the crypto self-test fails, or
+ * (b) sysctl(KERN_ARND) fails when reseeding the PRNG.
  *
- * The crypto self-test and pthread_atfork occur only once, on the
- * first use of any of the arc4random(3) API.  KERN_ARND is unlikely to
- * fail later unless the kernel is seriously broken.
+ * The crypto self-test occurs only once, on the first use of any of
+ * the arc4random(3) API.  KERN_ARND is unlikely to fail later unless
+ * the kernel is seriously broken.
  */
 
 #include <sys/cdefs.h>
-__RCSID("$NetBSD: arc4random.c,v 1.45 2025/03/06 00:54:27 riastradh Exp $");
+__RCSID("$NetBSD: arc4random.c,v 1.46 2025/03/09 18:11:55 riastradh Exp $");
 
 #include "namespace.h"
 #include "reentrant.h"
@@ -569,12 +568,14 @@ arc4random_initialize(void)
 	 * Set up a pthread_atfork handler to lock the global state
 	 * around fork so that if forked children can't use the
 	 * per-thread state, they can take the lock and use the global
-	 * state without deadlock.
+	 * state without deadlock.  If this fails, we will fall back to
+	 * PRNG state on the stack reinitialized from the kernel
+	 * entropy pool at every call.
 	 */
 	if (pthread_atfork(&arc4random_atfork_prepare,
 		&arc4random_atfork_parent, &arc4random_atfork_child)
-	    != 0)
-		abort();
+	    == 0)
+		arc4random_global.forksafe = true;
 
 	/*
 	 * For multithreaded builds, try to allocate a per-thread PRNG
@@ -594,7 +595,7 @@ arc4random_initialize(void)
 }
 
 static struct arc4random_prng *
-arc4random_prng_get(void)
+arc4random_prng_get(struct arc4random_prng *fallback)
 {
 	struct arc4random_prng *prng = NULL;
 
@@ -612,10 +613,20 @@ arc4random_prng_get(void)
 	}
 #endif
 
-	/* If we can't create it, fall back to the global PRNG.  */
+	/*
+	 * If we can't create it, fall back to the global PRNG -- or an
+	 * on-stack PRNG, in the unlikely event that pthread_atfork
+	 * failed, which we have to seed from scratch each time
+	 * (suboptimal, but unlikely, so not worth optimizing).
+	 */
 	if (__predict_false(prng == NULL)) {
-		mutex_lock(&arc4random_global.lock);
-		prng = &arc4random_global.prng;
+		if (__predict_true(arc4random_global.forksafe)) {
+			mutex_lock(&arc4random_global.lock);
+			prng = &arc4random_global.prng;
+		} else {
+			prng = fallback;
+			memset(prng, 0, sizeof(*prng));
+		}
 	}
 
 	/* Guarantee the PRNG is seeded.  */
@@ -626,9 +637,21 @@ arc4random_prng_get(void)
 }
 
 static void
-arc4random_prng_put(struct arc4random_prng *prng)
+arc4random_prng_put(struct arc4random_prng *prng,
+    struct arc4random_prng *fallback)
 {
 
+	/*
+	 * If we had to use a stack fallback, zero it before we return
+	 * so that after we return we avoid leaving secrets on the
+	 * stack that could recover the parent's future outputs in an
+	 * unprivileged forked child (of course, we can't guarantee
+	 * that the compiler hasn't spilled anything; this is
+	 * best-effort, not a guarantee).
+	 */
+	if (__predict_false(prng == fallback))
+		explicit_memset(fallback, 0, sizeof(*fallback));
+
 	/* If we had fallen back to the global PRNG, unlock it.  */
 	if (__predict_false(prng == &arc4random_global.prng))
 		mutex_unlock(&arc4random_global.lock);
@@ -639,12 +662,12 @@ arc4random_prng_put(struct arc4random_pr
 uint32_t
 arc4random(void)
 {
-	struct arc4random_prng *prng;
+	struct arc4random_prng *prng, fallback;
 	uint32_t v;
 
-	prng = arc4random_prng_get();
+	prng = arc4random_prng_get(&fallback);
 	crypto_prng_buf(&prng->arc4_prng, &v, sizeof v);
-	arc4random_prng_put(prng);
+	arc4random_prng_put(prng, &fallback);
 
 	return v;
 }
@@ -652,18 +675,18 @@ arc4random(void)
 void
 arc4random_buf(void *buf, size_t len)
 {
-	struct arc4random_prng *prng;
+	struct arc4random_prng *prng, fallback;
 
 	if (len <= crypto_prng_MAXOUTPUTBYTES) {
-		prng = arc4random_prng_get();
+		prng = arc4random_prng_get(&fallback);
 		crypto_prng_buf(&prng->arc4_prng, buf, len);
-		arc4random_prng_put(prng);
+		arc4random_prng_put(prng, &fallback);
 	} else {
 		uint8_t seed[crypto_onetimestream_SEEDBYTES];
 
-		prng = arc4random_prng_get();
+		prng = arc4random_prng_get(&fallback);
 		crypto_prng_buf(&prng->arc4_prng, seed, sizeof seed);
-		arc4random_prng_put(prng);
+		arc4random_prng_put(prng, &fallback);
 
 		crypto_onetimestream(seed, buf, len);
 		(void)explicit_memset(seed, 0, sizeof seed);
@@ -673,7 +696,7 @@ arc4random_buf(void *buf, size_t len)
 uint32_t
 arc4random_uniform(uint32_t bound)
 {
-	struct arc4random_prng *prng;
+	struct arc4random_prng *prng, fallback;
 	uint32_t minimum, r;
 
 	/*
@@ -693,10 +716,10 @@ arc4random_uniform(uint32_t bound)
 	 */
 	minimum = (-bound % bound);
 
-	prng = arc4random_prng_get();
+	prng = arc4random_prng_get(&fallback);
 	do crypto_prng_buf(&prng->arc4_prng, &r, sizeof r);
 	while (__predict_false(r < minimum));
-	arc4random_prng_put(prng);
+	arc4random_prng_put(prng, &fallback);
 
 	return (r % bound);
 }
@@ -704,11 +727,11 @@ arc4random_uniform(uint32_t bound)
 void
 arc4random_stir(void)
 {
-	struct arc4random_prng *prng;
+	struct arc4random_prng *prng, fallback;
 
-	prng = arc4random_prng_get();
+	prng = arc4random_prng_get(&fallback);
 	arc4random_prng_addrandom(prng, NULL, 0);
-	arc4random_prng_put(prng);
+	arc4random_prng_put(prng, &fallback);
 }
 
 /*
@@ -718,13 +741,13 @@ arc4random_stir(void)
 void
 arc4random_addrandom(u_char *data, int datalen)
 {
-	struct arc4random_prng *prng;
+	struct arc4random_prng *prng, fallback;
 
 	_DIAGASSERT(0 <= datalen);
 
-	prng = arc4random_prng_get();
+	prng = arc4random_prng_get(&fallback);
 	arc4random_prng_addrandom(prng, data, datalen);
-	arc4random_prng_put(prng);
+	arc4random_prng_put(prng, &fallback);
 }
 
 #ifdef _ARC4RANDOM_TEST

Index: src/lib/libc/include/arc4random.h
diff -u src/lib/libc/include/arc4random.h:1.3 src/lib/libc/include/arc4random.h:1.4
--- src/lib/libc/include/arc4random.h:1.3	Thu Mar  6 00:53:26 2025
+++ src/lib/libc/include/arc4random.h	Sun Mar  9 18:11:55 2025
@@ -1,4 +1,4 @@
-/*	$NetBSD: arc4random.h,v 1.3 2025/03/06 00:53:26 riastradh Exp $	*/
+/*	$NetBSD: arc4random.h,v 1.4 2025/03/09 18:11:55 riastradh Exp $	*/
 
 /*-
  * Copyright (c) 2014 The NetBSD Foundation, Inc.
@@ -52,6 +52,7 @@ struct arc4random_global_state {
 	struct arc4random_prng	prng;
 	once_t			once;
 	bool			initialized;
+	bool			forksafe;
 	bool			per_thread;
 };
 

Index: src/tests/lib/libc/gen/t_arc4random.c
diff -u src/tests/lib/libc/gen/t_arc4random.c:1.4 src/tests/lib/libc/gen/t_arc4random.c:1.5
--- src/tests/lib/libc/gen/t_arc4random.c:1.4	Thu Mar  6 00:54:27 2025
+++ src/tests/lib/libc/gen/t_arc4random.c	Sun Mar  9 18:11:55 2025
@@ -1,4 +1,4 @@
-/*	$NetBSD: t_arc4random.c,v 1.4 2025/03/06 00:54:27 riastradh Exp $	*/
+/*	$NetBSD: t_arc4random.c,v 1.5 2025/03/09 18:11:55 riastradh Exp $	*/
 
 /*-
  * Copyright (c) 2024 The NetBSD Foundation, Inc.
@@ -29,7 +29,7 @@
 #define	_REENTRANT
 
 #include <sys/cdefs.h>
-__RCSID("$NetBSD: t_arc4random.c,v 1.4 2025/03/06 00:54:27 riastradh Exp $");
+__RCSID("$NetBSD: t_arc4random.c,v 1.5 2025/03/09 18:11:55 riastradh Exp $");
 
 #include <sys/resource.h>
 #include <sys/stat.h>
@@ -606,6 +606,53 @@ ATF_TC_BODY(local, tc)
 	ATF_CHECK(memcmp(buf, buf1, sizeof(buf)) != 0);
 }
 
+ATF_TC(stackfallback);
+ATF_TC_HEAD(stackfallback, tc)
+{
+	atf_tc_set_md_var(tc, "descr",
+	    "Test arc4random with pthread_atfork and thr_keycreate failure");
+}
+ATF_TC_BODY(stackfallback, tc)
+{
+	unsigned char buf[32], buf1[32];
+	struct arc4random_prng *local;
+
+	/*
+	 * Get a sample to start things off.  This makes the library
+	 * gets initialized.
+	 */
+	arc4random_buf(buf, sizeof(buf));
+	ATF_CHECK(!iszero(buf, sizeof(buf)));	/* Pr[fail] = 1/2^256 */
+
+	/*
+	 * Clear the arc4random global state, and the local state if it
+	 * exists, and pretend pthread_atfork and thr_keycreate had
+	 * both failed.
+	 */
+	memset(&arc4random_global.prng, 0, sizeof(arc4random_global.prng));
+	if ((local = arc4random_prng()) != NULL)
+		memset(local, 0, sizeof(*local));
+	arc4random_global.forksafe = false;
+	arc4random_global.per_thread = false;
+
+	/*
+	 * Make sure it still works to get a sample.
+	 */
+	arc4random_buf(buf1, sizeof(buf1));
+	ATF_CHECK(!iszero(buf, sizeof(buf)));	/* Pr[fail] = 1/2^256 */
+	ATF_CHECK(memcmp(buf, buf1, sizeof(buf)) != 0);
+
+	/*
+	 * Make sure the global and local epochs did not change.
+	 */
+	ATF_CHECK_EQ_MSG(arc4random_global.prng.arc4_epoch, 0,
+	    "global epoch: %d", arc4random_global.prng.arc4_epoch);
+	if (local != NULL) {
+		ATF_CHECK_EQ_MSG(local->arc4_epoch, 0,
+		    "local epoch: %d", local->arc4_epoch);
+	}
+}
+
 ATF_TP_ADD_TCS(tp)
 {
 
@@ -617,6 +664,7 @@ ATF_TP_ADD_TCS(tp)
 	ATF_TP_ADD_TC(tp, global_aslimit);
 	ATF_TP_ADD_TC(tp, global_threadkeylimit);
 	ATF_TP_ADD_TC(tp, local);
+	ATF_TP_ADD_TC(tp, stackfallback);
 
 	return atf_no_error();
 }

Reply via email to