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();
}