Module Name: src
Committed By: riastradh
Date: Tue Aug 27 13:43:02 UTC 2024
Modified Files:
src/distrib/sets/lists/debug: mi
src/distrib/sets/lists/tests: mi
src/lib/libc/gen: arc4random.c
src/lib/libc/include: reentrant.h
src/tests/lib/libc/gen: Makefile
Added Files:
src/lib/libc/include: arc4random.h
src/tests/lib/libc/gen: t_arc4random.c
Log Message:
arc4random(3): Add automatic tests.
This verifies that:
- arc4random zeroes its state and reseeds itself on fork
- arc4random reseeds itself on entropy consolidation (e.g., VM clone)
- arc4random falls back to global state if it can't allocate local
state because address space limits cause mmap to fail
NOTE: This adds a new libc symbol __arc4random_global, but it's in
the reserved namespace and only used by t_arc4random, so no libc
minor bump.
PR kern/58632: getentropy(2) and arc4random(3) do not reseed on VM
fork
To generate a diff of this commit:
cvs rdiff -u -r1.445 -r1.446 src/distrib/sets/lists/debug/mi
cvs rdiff -u -r1.1333 -r1.1334 src/distrib/sets/lists/tests/mi
cvs rdiff -u -r1.36 -r1.37 src/lib/libc/gen/arc4random.c
cvs rdiff -u -r0 -r1.1 src/lib/libc/include/arc4random.h
cvs rdiff -u -r1.21 -r1.22 src/lib/libc/include/reentrant.h
cvs rdiff -u -r1.55 -r1.56 src/tests/lib/libc/gen/Makefile
cvs rdiff -u -r0 -r1.1 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/distrib/sets/lists/debug/mi
diff -u src/distrib/sets/lists/debug/mi:1.445 src/distrib/sets/lists/debug/mi:1.446
--- src/distrib/sets/lists/debug/mi:1.445 Sun Aug 18 04:02:58 2024
+++ src/distrib/sets/lists/debug/mi Tue Aug 27 13:43:01 2024
@@ -1,4 +1,4 @@
-# $NetBSD: mi,v 1.445 2024/08/18 04:02:58 rin Exp $
+# $NetBSD: mi,v 1.446 2024/08/27 13:43:01 riastradh Exp $
#
./etc/mtree/set.debug comp-sys-root
./usr/lib comp-sys-usr compatdir
@@ -2009,6 +2009,7 @@
./usr/libdata/debug/usr/tests/lib/libc/gen/posix_spawn/t_spawn.debug tests-kernel-tests debug,atf,compattestfile
./usr/libdata/debug/usr/tests/lib/libc/gen/posix_spawn/t_spawnattr.debug tests-kernel-tests debug,atf,compattestfile
./usr/libdata/debug/usr/tests/lib/libc/gen/t_alarm.debug tests-lib-debug debug,atf,compattestfile
+./usr/libdata/debug/usr/tests/lib/libc/gen/t_arc4random.debug tests-lib-debug debug,atf,compattestfile
./usr/libdata/debug/usr/tests/lib/libc/gen/t_assert.debug tests-lib-debug debug,atf,compattestfile
./usr/libdata/debug/usr/tests/lib/libc/gen/t_basedirname.debug tests-lib-debug debug,atf,compattestfile
./usr/libdata/debug/usr/tests/lib/libc/gen/t_closefrom.debug tests-lib-debug debug,atf,compattestfile
Index: src/distrib/sets/lists/tests/mi
diff -u src/distrib/sets/lists/tests/mi:1.1333 src/distrib/sets/lists/tests/mi:1.1334
--- src/distrib/sets/lists/tests/mi:1.1333 Tue Aug 20 08:20:19 2024
+++ src/distrib/sets/lists/tests/mi Tue Aug 27 13:43:02 2024
@@ -1,4 +1,4 @@
-# $NetBSD: mi,v 1.1333 2024/08/20 08:20:19 ozaki-r Exp $
+# $NetBSD: mi,v 1.1334 2024/08/27 13:43:02 riastradh Exp $
#
# Note: don't delete entries from here - mark them as "obsolete" instead.
#
@@ -2984,6 +2984,7 @@
./usr/tests/lib/libc/gen/posix_spawn/t_spawn tests-kernel-tests compattestfile,atf
./usr/tests/lib/libc/gen/posix_spawn/t_spawnattr tests-kernel-tests compattestfile,atf
./usr/tests/lib/libc/gen/t_alarm tests-lib-tests compattestfile,atf
+./usr/tests/lib/libc/gen/t_arc4random tests-lib-tests compattestfile,atf
./usr/tests/lib/libc/gen/t_assert tests-lib-tests compattestfile,atf
./usr/tests/lib/libc/gen/t_basedirname tests-lib-tests compattestfile,atf
./usr/tests/lib/libc/gen/t_closefrom tests-lib-tests compattestfile,atf
Index: src/lib/libc/gen/arc4random.c
diff -u src/lib/libc/gen/arc4random.c:1.36 src/lib/libc/gen/arc4random.c:1.37
--- src/lib/libc/gen/arc4random.c:1.36 Mon Aug 26 15:50:26 2024
+++ src/lib/libc/gen/arc4random.c Tue Aug 27 13:43:02 2024
@@ -1,4 +1,4 @@
-/* $NetBSD: arc4random.c,v 1.36 2024/08/26 15:50:26 riastradh Exp $ */
+/* $NetBSD: arc4random.c,v 1.37 2024/08/27 13:43:02 riastradh Exp $ */
/*-
* Copyright (c) 2014 The NetBSD Foundation, Inc.
@@ -52,7 +52,7 @@
*/
#include <sys/cdefs.h>
-__RCSID("$NetBSD: arc4random.c,v 1.36 2024/08/26 15:50:26 riastradh Exp $");
+__RCSID("$NetBSD: arc4random.c,v 1.37 2024/08/27 13:43:02 riastradh Exp $");
#include "namespace.h"
#include "reentrant.h"
@@ -72,6 +72,9 @@ __RCSID("$NetBSD: arc4random.c,v 1.36 20
#include <string.h>
#include <unistd.h>
+#include "arc4random.h"
+#include "reentrant.h"
+
#ifdef __weak_alias
__weak_alias(arc4random,_arc4random)
__weak_alias(arc4random_addrandom,_arc4random_addrandom)
@@ -305,9 +308,7 @@ crypto_core_selftest(void)
#define crypto_prng_MAXOUTPUTBYTES \
(crypto_core_OUTPUTBYTES - crypto_prng_SEEDBYTES)
-struct crypto_prng {
- uint8_t state[crypto_prng_SEEDBYTES];
-};
+__CTASSERT(sizeof(struct crypto_prng) == crypto_prng_SEEDBYTES);
static void
crypto_prng_seed(struct crypto_prng *prng, const void *seed)
@@ -457,11 +458,6 @@ entropy_epoch(void)
/* arc4random state: per-thread, per-process (zeroed in child on fork) */
-struct arc4random_prng {
- struct crypto_prng arc4_prng;
- unsigned arc4_epoch;
-};
-
static void
arc4random_prng_addrandom(struct arc4random_prng *prng, const void *data,
size_t datalen)
@@ -531,14 +527,7 @@ arc4random_prng_destroy(struct arc4rando
/* Library state */
-static struct arc4random_global {
-#ifdef _REENTRANT
- mutex_t lock;
- thread_key_t thread_key;
-#endif
- struct arc4random_prng prng;
- bool initialized;
-} arc4random_global = {
+struct arc4random_global_state arc4random_global = {
#ifdef _REENTRANT
.lock = MUTEX_INITIALIZER,
#endif
Index: src/lib/libc/include/reentrant.h
diff -u src/lib/libc/include/reentrant.h:1.21 src/lib/libc/include/reentrant.h:1.22
--- src/lib/libc/include/reentrant.h:1.21 Wed Dec 8 20:11:54 2021
+++ src/lib/libc/include/reentrant.h Tue Aug 27 13:43:02 2024
@@ -1,4 +1,4 @@
-/* $NetBSD: reentrant.h,v 1.21 2021/12/08 20:11:54 andvar Exp $ */
+/* $NetBSD: reentrant.h,v 1.22 2024/08/27 13:43:02 riastradh Exp $ */
/*-
* Copyright (c) 1997, 1998, 2003 The NetBSD Foundation, Inc.
@@ -91,6 +91,9 @@
* is.
*/
+#ifndef _LIBC_REENTRANT_H_
+#define _LIBC_REENTRANT_H_
+
#include <pthread.h>
#include <signal.h>
@@ -326,3 +329,5 @@ thr_once(once_t *once_control, void (*ro
#define FUNLOCKFILE(fp) __nothing
#endif /* _REENTRANT */
+
+#endif /* _LIBC_REENTRANT_H_ */
Index: src/tests/lib/libc/gen/Makefile
diff -u src/tests/lib/libc/gen/Makefile:1.55 src/tests/lib/libc/gen/Makefile:1.56
--- src/tests/lib/libc/gen/Makefile:1.55 Tue May 31 13:42:59 2022
+++ src/tests/lib/libc/gen/Makefile Tue Aug 27 13:43:02 2024
@@ -1,4 +1,4 @@
-# $NetBSD: Makefile,v 1.55 2022/05/31 13:42:59 riastradh Exp $
+# $NetBSD: Makefile,v 1.56 2024/08/27 13:43:02 riastradh Exp $
.include <bsd.own.mk>
@@ -8,6 +8,7 @@ TESTS_SUBDIRS+= execve
TESTS_SUBDIRS+= posix_spawn
TESTS_C+= t_alarm
+TESTS_C+= t_arc4random
TESTS_C+= t_assert
TESTS_C+= t_basedirname
TESTS_C+= t_closefrom
@@ -44,6 +45,7 @@ TESTS_C+= t_vis
COPTS.t_siginfo.c+= -DENABLE_TESTS
.endif
+CPPFLAGS.t_arc4random.c+=-I${NETBSDSRCDIR}/lib/libc/include
CPPFLAGS.t_siginfo.c+=-D__TEST_FENV
COPTS.t_fpsetround.c+=${${ACTIVE_CC} == "gcc":? -frounding-math :}
@@ -54,6 +56,8 @@ DPADD.t_fpclassify+= ${LIBM}
LDADD.t_fpsetround+= -lm
DPADD.t_fpsetround+= ${LIBM}
+LDADD.t_arc4random+= -lpthread
+DPADD.t_arc4random+= ${LIBPTHREAD}
LDADD.t_nice+= -lpthread
DPADD.t_nice+= ${LIBPTHREAD}
LDADD.t_syslog+= -lpthread
Added files:
Index: src/lib/libc/include/arc4random.h
diff -u /dev/null src/lib/libc/include/arc4random.h:1.1
--- /dev/null Tue Aug 27 13:43:02 2024
+++ src/lib/libc/include/arc4random.h Tue Aug 27 13:43:02 2024
@@ -0,0 +1,60 @@
+/* $NetBSD: arc4random.h,v 1.1 2024/08/27 13:43:02 riastradh Exp $ */
+
+/*-
+ * Copyright (c) 2014 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Taylor R. Campbell.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _LIBC_GEN_ARC4RANDOM_H_
+#define _LIBC_GEN_ARC4RANDOM_H_
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#include "reentrant.h"
+
+struct crypto_prng {
+ uint8_t state[32];
+};
+
+struct arc4random_prng {
+ struct crypto_prng arc4_prng;
+ unsigned arc4_epoch;
+};
+
+struct arc4random_global_state {
+ mutex_t lock;
+ thread_key_t thread_key;
+ struct arc4random_prng prng;
+ bool initialized;
+};
+
+#define arc4random_global __arc4random_global /* libc private symbol */
+
+extern struct arc4random_global_state arc4random_global;
+
+#endif /* _LIBC_GEN_ARC4RANDOM_H_ */
Index: src/tests/lib/libc/gen/t_arc4random.c
diff -u /dev/null src/tests/lib/libc/gen/t_arc4random.c:1.1
--- /dev/null Tue Aug 27 13:43:02 2024
+++ src/tests/lib/libc/gen/t_arc4random.c Tue Aug 27 13:43:02 2024
@@ -0,0 +1,467 @@
+/* $NetBSD: t_arc4random.c,v 1.1 2024/08/27 13:43:02 riastradh Exp $ */
+
+/*-
+ * Copyright (c) 2024 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#define _REENTRANT
+
+#include <sys/cdefs.h>
+__RCSID("$NetBSD: t_arc4random.c,v 1.1 2024/08/27 13:43:02 riastradh Exp $");
+
+#include <sys/resource.h>
+#include <sys/sysctl.h>
+#include <sys/wait.h>
+
+#include <atf-c.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "arc4random.h"
+#include "reentrant.h"
+#include "h_macros.h"
+
+/*
+ * iszero(buf, len)
+ *
+ * True if len bytes at buf are all zero, false if any one of them
+ * is nonzero.
+ */
+static bool
+iszero(const void *buf, size_t len)
+{
+ const unsigned char *p = buf;
+ size_t i;
+
+ for (i = 0; i < len; i++) {
+ if (p[i] != 0)
+ return false;
+ }
+ return true;
+}
+
+/*
+ * arc4random_prng()
+ *
+ * Get a pointer to the current arc4random state, without updating
+ * any of the state, not even lazy initialization.
+ */
+static struct arc4random_prng *
+arc4random_prng(void)
+{
+ struct arc4random_prng *prng = NULL;
+
+ /*
+ * If arc4random has been initialized and there is a thread key
+ * (i.e., libc was built with _REENTRANT), get the thread-local
+ * arc4random state if there is one.
+ */
+ if (arc4random_global.initialized)
+ prng = thr_getspecific(arc4random_global.thread_key);
+
+ /*
+ * If we couldn't get the thread-local state, get the global
+ * state instead.
+ */
+ if (prng == NULL)
+ prng = &arc4random_global.prng;
+
+ return prng;
+}
+
+/*
+ * arc4random_global_buf(buf, len)
+ *
+ * Same as arc4random_buf, but force use of the global state.
+ * Must happen before any other use of arc4random.
+ */
+static void
+arc4random_global_buf(void *buf, size_t len)
+{
+ struct rlimit rlim, orlim;
+ struct arc4random_prng *prng;
+
+ /*
+ * Save the address space limit.
+ */
+ RL(getrlimit(RLIMIT_AS, &orlim));
+ memcpy(&rlim, &orlim, sizeof(rlim));
+
+ /*
+ * Get a sample while the address space limit is zero. This
+ * should try, and fail, to allocate a thread-local arc4random
+ * state with mmap(2).
+ */
+ rlim.rlim_cur = 0;
+ RL(setrlimit(RLIMIT_AS, &rlim));
+ arc4random_buf(buf, len);
+ RL(setrlimit(RLIMIT_AS, &orlim));
+
+ /*
+ * Restore the address space limit.
+ */
+ RL(setrlimit(RLIMIT_AS, &orlim));
+
+ /*
+ * Verify the PRNG is the global one, not the thread-local one,
+ * and that it was initialized.
+ */
+ prng = arc4random_prng();
+ ATF_CHECK_EQ(prng, &arc4random_global.prng);
+ ATF_CHECK(!iszero(&prng->arc4_prng, sizeof(prng->arc4_prng)));
+ ATF_CHECK(prng->arc4_epoch != 0);
+}
+
+/*
+ * arc4random_global_thread(cookie)
+ *
+ * Start routine for a thread that just grabs an output from the
+ * global state.
+ */
+static void *
+arc4random_global_thread(void *cookie)
+{
+ unsigned char buf[32];
+
+ arc4random_global_buf(buf, sizeof(buf));
+
+ return NULL;
+}
+
+ATF_TC(addrandom);
+ATF_TC_HEAD(addrandom, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "Test arc4random_addrandom updates the state");
+}
+ATF_TC_BODY(addrandom, tc)
+{
+ unsigned char buf[32], zero32[32] = {0};
+ struct arc4random_prng *prng, copy;
+
+ /*
+ * Get a sample to start things off.
+ */
+ arc4random_buf(buf, sizeof(buf));
+ ATF_CHECK(!iszero(buf, sizeof(buf))); /* Pr[fail] = 1/2^256 */
+
+ /*
+ * By this point, the global state must be initialized -- if
+ * not, the process should have aborted.
+ */
+ ATF_CHECK(arc4random_global.initialized);
+
+ /*
+ * Get the PRNG, global or local. By this point, the PRNG
+ * state should be nonzero (with overwhelmingly high
+ * probability) and the epoch should also be nonzero.
+ */
+ prng = arc4random_prng();
+ ATF_CHECK(!iszero(&prng->arc4_prng, sizeof(prng->arc4_prng)));
+ ATF_CHECK(prng->arc4_epoch != 0);
+
+ /*
+ * Save a copy and update the state with arc4random_addrandom.
+ */
+ copy = *prng;
+ arc4random_addrandom(zero32, sizeof(zero32));
+
+ /*
+ * The state should have changed. (The epoch may or may not.)
+ */
+ ATF_CHECK(memcmp(&prng->arc4_prng, ©.arc4_prng,
+ sizeof(copy.arc4_prng)) != 0);
+
+ /*
+ * Save a copy and update the state with arc4random_stir.
+ */
+ copy = *prng;
+ arc4random_stir();
+
+ /*
+ * The state should have changed. (The epoch may or may not.)
+ */
+ ATF_CHECK(memcmp(&prng->arc4_prng, ©.arc4_prng,
+ sizeof(copy.arc4_prng)) != 0);
+}
+
+ATF_TC(consolidate);
+ATF_TC_HEAD(consolidate, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "Test consolidating entropy resets the epoch");
+}
+ATF_TC_BODY(consolidate, tc)
+{
+ unsigned char buf[32];
+ struct arc4random_prng *local, *global = &arc4random_global.prng;
+ unsigned localepoch, globalepoch;
+ const int consolidate = 1;
+ pthread_t thread;
+
+ /*
+ * Get a sample from the global state to make sure the global
+ * state is initialized. Remember the epoch.
+ */
+ arc4random_global_buf(buf, sizeof(buf));
+ ATF_CHECK(!iszero(buf, sizeof(buf))); /* Pr[fail] = 1/2^256 */
+ ATF_CHECK(!iszero(&global->arc4_prng, sizeof(global->arc4_prng)));
+ ATF_CHECK((globalepoch = global->arc4_epoch) != 0);
+
+ /*
+ * Get a sample from the local state too to make sure the local
+ * state is initialized. Remember the epoch.
+ */
+ arc4random_buf(buf, sizeof(buf));
+ ATF_CHECK(!iszero(buf, sizeof(buf))); /* Pr[fail] = 1/2^256 */
+ local = arc4random_prng();
+ ATF_CHECK(!iszero(&local->arc4_prng, sizeof(local->arc4_prng)));
+ ATF_CHECK((localepoch = local->arc4_epoch) != 0);
+
+ /*
+ * Trigger entropy consolidation.
+ */
+ RL(sysctlbyname("kern.entropy.consolidate", /*oldp*/NULL, /*oldlen*/0,
+ &consolidate, sizeof(consolidate)));
+
+ /*
+ * Verify the epoch cache isn't changed yet until we ask for
+ * more data.
+ */
+ ATF_CHECK_EQ_MSG(globalepoch, global->arc4_epoch,
+ "global epoch was %u, now %u", globalepoch, global->arc4_epoch);
+ ATF_CHECK_EQ_MSG(localepoch, local->arc4_epoch,
+ "local epoch was %u, now %u", localepoch, local->arc4_epoch);
+
+ /*
+ * Request new output and verify the local epoch cache has
+ * changed.
+ */
+ arc4random_buf(buf, sizeof(buf));
+ ATF_CHECK(!iszero(buf, sizeof(buf))); /* Pr[fail] = 1/2^256 */
+ ATF_CHECK_MSG(localepoch != local->arc4_epoch,
+ "local epoch unchanged from %u", localepoch);
+
+ /*
+ * Create a new thread to grab output from the global state,
+ * wait for it to complete, and verify the global epoch cache
+ * has changed. (Now that we have already used the local state
+ * in this thread, we can't use the global state any more.)
+ */
+ RZ(pthread_create(&thread, NULL, &arc4random_global_thread, NULL));
+ RZ(pthread_join(thread, NULL));
+ ATF_CHECK_MSG(globalepoch != global->arc4_epoch,
+ "global epoch unchanged from %u", globalepoch);
+}
+
+ATF_TC(fork);
+ATF_TC_HEAD(fork, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "Test fork zeros the state and gets independent state");
+}
+ATF_TC_BODY(fork, tc)
+{
+ unsigned char buf[32];
+ struct arc4random_prng *local, *global = &arc4random_global.prng;
+ struct arc4random_prng childstate;
+ int fd[2];
+ pid_t child, pid;
+ ssize_t nread;
+ int status;
+
+ /*
+ * Get a sample from the global state to make sure the global
+ * state is initialized.
+ */
+ arc4random_global_buf(buf, sizeof(buf));
+ ATF_CHECK(!iszero(buf, sizeof(buf))); /* Pr[fail] = 1/2^256 */
+ ATF_CHECK(!iszero(&global->arc4_prng, sizeof(global->arc4_prng)));
+ ATF_CHECK(global->arc4_epoch != 0);
+
+ /*
+ * Get a sample from the local state too to make sure the local
+ * state is initialized.
+ */
+ arc4random_buf(buf, sizeof(buf));
+ ATF_CHECK(!iszero(buf, sizeof(buf))); /* Pr[fail] = 1/2^256 */
+ local = arc4random_prng();
+ ATF_CHECK(!iszero(&local->arc4_prng, sizeof(local->arc4_prng)));
+ ATF_CHECK(local->arc4_epoch != 0);
+
+ /*
+ * Create a pipe to transfer the state from child to parent.
+ */
+ RL(pipe(fd));
+
+ /*
+ * Fork a child.
+ */
+ RL(child = fork());
+ if (child == 0) {
+ status = 0;
+
+ /*
+ * Verify the states have been zero'd on fork.
+ */
+ if (!iszero(local, sizeof(*local))) {
+ fprintf(stderr, "failed to zero local state\n");
+ status = 1;
+ }
+ if (!iszero(global, sizeof(*global))) {
+ fprintf(stderr, "failed to zero global state\n");
+ status = 1;
+ }
+
+ /*
+ * Verify we generate nonzero output.
+ */
+ arc4random_buf(buf, sizeof(buf));
+ if (iszero(buf, sizeof(buf))) {
+ fprintf(stderr, "failed to generate nonzero output\n");
+ status = 1;
+ }
+
+ /*
+ * Share the state to compare with parent.
+ */
+ if ((size_t)write(fd[1], local, sizeof(*local)) !=
+ sizeof(*local)) {
+ fprintf(stderr, "failed to share local state\n");
+ status = 1;
+ }
+ _exit(status);
+ }
+
+ /*
+ * Verify the global state has been zeroed as expected. (This
+ * way it is never available to the child, even shortly after
+ * the fork syscall returns before the atfork handler is
+ * called.)
+ */
+ ATF_CHECK(iszero(global, sizeof(*global)));
+
+ /*
+ * Read the state from the child.
+ */
+ RL(nread = read(fd[0], &childstate, sizeof(childstate)));
+ ATF_CHECK_EQ_MSG(nread, sizeof(childstate),
+ "nread=%zu sizeof(childstate)=%zu", nread, sizeof(childstate));
+
+ /*
+ * Verify the child state is distinct. (The global state has
+ * been zero'd so it's OK it if coincides.) Check again after
+ * we grab another output.
+ */
+ ATF_CHECK(memcmp(local, &childstate, sizeof(*local)) != 0);
+ arc4random_buf(buf, sizeof(buf));
+ ATF_CHECK(!iszero(buf, sizeof(buf))); /* Pr[fail] = 1/2^256 */
+ ATF_CHECK(memcmp(local, &childstate, sizeof(*local)) != 0);
+
+ /*
+ * Wait for the child to complete and verify it passed.
+ */
+ RL(pid = waitpid(child, &status, 0));
+ ATF_CHECK_EQ_MSG(status, 0, "child exited with nonzero status=%d",
+ status);
+}
+
+ATF_TC(global);
+ATF_TC_HEAD(global, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "Test the global state is used when address space limit is hit");
+}
+ATF_TC_BODY(global, tc)
+{
+ unsigned char buf[32], buf1[32];
+
+ /*
+ * Get a sample from the global state (and verify it was using
+ * the global state).
+ */
+ arc4random_global_buf(buf, sizeof(buf));
+
+ /*
+ * Verify we got a sample.
+ */
+ ATF_CHECK(!iszero(buf, sizeof(buf))); /* Pr[fail] = 1/2^256 */
+
+ /*
+ * Get a sample from whatever state and make sure it wasn't
+ * repeated, which happens only with probability 1/2^256.
+ */
+ arc4random_buf(buf1, sizeof(buf1));
+ ATF_CHECK(!iszero(buf1, sizeof(buf1))); /* Pr[fail] = 1/2^256 */
+ ATF_CHECK(memcmp(buf, buf1, sizeof(buf)) != 0);
+}
+
+ATF_TC(local);
+ATF_TC_HEAD(local, tc)
+{
+ atf_tc_set_md_var(tc, "descr",
+ "Test arc4random uses thread-local state");
+ /* XXX skip if libc was built without _REENTRANT */
+}
+ATF_TC_BODY(local, tc)
+{
+ unsigned char buf[32], buf1[32];
+ struct arc4random_prng *prng;
+
+ /*
+ * Get a sample to start things off.
+ */
+ arc4random_buf(buf, sizeof(buf));
+ ATF_CHECK(!iszero(buf, sizeof(buf))); /* Pr[fail] = 1/2^256 */
+
+ /*
+ * Verify the arc4random state is _not_ the global state.
+ */
+ prng = arc4random_prng();
+ ATF_CHECK(prng != &arc4random_global.prng);
+ ATF_CHECK(!iszero(&prng->arc4_prng, sizeof(prng->arc4_prng)));
+ ATF_CHECK(prng->arc4_epoch != 0);
+
+ /*
+ * Get another sample and make sure it wasn't repeated, which
+ * happens only with probability 1/2^256.
+ */
+ arc4random_buf(buf1, sizeof(buf1));
+ ATF_CHECK(!iszero(buf1, sizeof(buf1))); /* Pr[fail] = 1/2^256 */
+ ATF_CHECK(memcmp(buf, buf1, sizeof(buf)) != 0);
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+
+ ATF_TP_ADD_TC(tp, addrandom);
+ ATF_TP_ADD_TC(tp, consolidate);
+ ATF_TP_ADD_TC(tp, fork);
+ ATF_TP_ADD_TC(tp, global);
+ ATF_TP_ADD_TC(tp, local);
+
+ return atf_no_error();
+}