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