Home | History | Annotate | Line # | Download | only in gen
t_arc4random.c revision 1.1
      1 /*	$NetBSD: t_arc4random.c,v 1.1 2024/08/27 13:43:02 riastradh Exp $	*/
      2 
      3 /*-
      4  * Copyright (c) 2024 The NetBSD Foundation, Inc.
      5  * All rights reserved.
      6  *
      7  * Redistribution and use in source and binary forms, with or without
      8  * modification, are permitted provided that the following conditions
      9  * are met:
     10  * 1. Redistributions of source code must retain the above copyright
     11  *    notice, this list of conditions and the following disclaimer.
     12  * 2. Redistributions in binary form must reproduce the above copyright
     13  *    notice, this list of conditions and the following disclaimer in the
     14  *    documentation and/or other materials provided with the distribution.
     15  *
     16  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
     17  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
     18  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     19  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
     20  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
     21  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
     22  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
     23  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
     24  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
     25  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
     26  * POSSIBILITY OF SUCH DAMAGE.
     27  */
     28 
     29 #define	_REENTRANT
     30 
     31 #include <sys/cdefs.h>
     32 __RCSID("$NetBSD: t_arc4random.c,v 1.1 2024/08/27 13:43:02 riastradh Exp $");
     33 
     34 #include <sys/resource.h>
     35 #include <sys/sysctl.h>
     36 #include <sys/wait.h>
     37 
     38 #include <atf-c.h>
     39 #include <stdio.h>
     40 #include <string.h>
     41 #include <unistd.h>
     42 
     43 #include "arc4random.h"
     44 #include "reentrant.h"
     45 #include "h_macros.h"
     46 
     47 /*
     48  * iszero(buf, len)
     49  *
     50  *	True if len bytes at buf are all zero, false if any one of them
     51  *	is nonzero.
     52  */
     53 static bool
     54 iszero(const void *buf, size_t len)
     55 {
     56 	const unsigned char *p = buf;
     57 	size_t i;
     58 
     59 	for (i = 0; i < len; i++) {
     60 		if (p[i] != 0)
     61 			return false;
     62 	}
     63 	return true;
     64 }
     65 
     66 /*
     67  * arc4random_prng()
     68  *
     69  *	Get a pointer to the current arc4random state, without updating
     70  *	any of the state, not even lazy initialization.
     71  */
     72 static struct arc4random_prng *
     73 arc4random_prng(void)
     74 {
     75 	struct arc4random_prng *prng = NULL;
     76 
     77 	/*
     78 	 * If arc4random has been initialized and there is a thread key
     79 	 * (i.e., libc was built with _REENTRANT), get the thread-local
     80 	 * arc4random state if there is one.
     81 	 */
     82 	if (arc4random_global.initialized)
     83 		prng = thr_getspecific(arc4random_global.thread_key);
     84 
     85 	/*
     86 	 * If we couldn't get the thread-local state, get the global
     87 	 * state instead.
     88 	 */
     89 	if (prng == NULL)
     90 		prng = &arc4random_global.prng;
     91 
     92 	return prng;
     93 }
     94 
     95 /*
     96  * arc4random_global_buf(buf, len)
     97  *
     98  *	Same as arc4random_buf, but force use of the global state.
     99  *	Must happen before any other use of arc4random.
    100  */
    101 static void
    102 arc4random_global_buf(void *buf, size_t len)
    103 {
    104 	struct rlimit rlim, orlim;
    105 	struct arc4random_prng *prng;
    106 
    107 	/*
    108 	 * Save the address space limit.
    109 	 */
    110 	RL(getrlimit(RLIMIT_AS, &orlim));
    111 	memcpy(&rlim, &orlim, sizeof(rlim));
    112 
    113 	/*
    114 	 * Get a sample while the address space limit is zero.  This
    115 	 * should try, and fail, to allocate a thread-local arc4random
    116 	 * state with mmap(2).
    117 	 */
    118 	rlim.rlim_cur = 0;
    119 	RL(setrlimit(RLIMIT_AS, &rlim));
    120 	arc4random_buf(buf, len);
    121 	RL(setrlimit(RLIMIT_AS, &orlim));
    122 
    123 	/*
    124 	 * Restore the address space limit.
    125 	 */
    126 	RL(setrlimit(RLIMIT_AS, &orlim));
    127 
    128 	/*
    129 	 * Verify the PRNG is the global one, not the thread-local one,
    130 	 * and that it was initialized.
    131 	 */
    132 	prng = arc4random_prng();
    133 	ATF_CHECK_EQ(prng, &arc4random_global.prng);
    134 	ATF_CHECK(!iszero(&prng->arc4_prng, sizeof(prng->arc4_prng)));
    135 	ATF_CHECK(prng->arc4_epoch != 0);
    136 }
    137 
    138 /*
    139  * arc4random_global_thread(cookie)
    140  *
    141  *	Start routine for a thread that just grabs an output from the
    142  *	global state.
    143  */
    144 static void *
    145 arc4random_global_thread(void *cookie)
    146 {
    147 	unsigned char buf[32];
    148 
    149 	arc4random_global_buf(buf, sizeof(buf));
    150 
    151 	return NULL;
    152 }
    153 
    154 ATF_TC(addrandom);
    155 ATF_TC_HEAD(addrandom, tc)
    156 {
    157 	atf_tc_set_md_var(tc, "descr",
    158 	    "Test arc4random_addrandom updates the state");
    159 }
    160 ATF_TC_BODY(addrandom, tc)
    161 {
    162 	unsigned char buf[32], zero32[32] = {0};
    163 	struct arc4random_prng *prng, copy;
    164 
    165 	/*
    166 	 * Get a sample to start things off.
    167 	 */
    168 	arc4random_buf(buf, sizeof(buf));
    169 	ATF_CHECK(!iszero(buf, sizeof(buf)));	/* Pr[fail] = 1/2^256 */
    170 
    171 	/*
    172 	 * By this point, the global state must be initialized -- if
    173 	 * not, the process should have aborted.
    174 	 */
    175 	ATF_CHECK(arc4random_global.initialized);
    176 
    177 	/*
    178 	 * Get the PRNG, global or local.  By this point, the PRNG
    179 	 * state should be nonzero (with overwhelmingly high
    180 	 * probability) and the epoch should also be nonzero.
    181 	 */
    182 	prng = arc4random_prng();
    183 	ATF_CHECK(!iszero(&prng->arc4_prng, sizeof(prng->arc4_prng)));
    184 	ATF_CHECK(prng->arc4_epoch != 0);
    185 
    186 	/*
    187 	 * Save a copy and update the state with arc4random_addrandom.
    188 	 */
    189 	copy = *prng;
    190 	arc4random_addrandom(zero32, sizeof(zero32));
    191 
    192 	/*
    193 	 * The state should have changed.  (The epoch may or may not.)
    194 	 */
    195 	ATF_CHECK(memcmp(&prng->arc4_prng, &copy.arc4_prng,
    196 		sizeof(copy.arc4_prng)) != 0);
    197 
    198 	/*
    199 	 * Save a copy and update the state with arc4random_stir.
    200 	 */
    201 	copy = *prng;
    202 	arc4random_stir();
    203 
    204 	/*
    205 	 * The state should have changed.  (The epoch may or may not.)
    206 	 */
    207 	ATF_CHECK(memcmp(&prng->arc4_prng, &copy.arc4_prng,
    208 		sizeof(copy.arc4_prng)) != 0);
    209 }
    210 
    211 ATF_TC(consolidate);
    212 ATF_TC_HEAD(consolidate, tc)
    213 {
    214 	atf_tc_set_md_var(tc, "descr",
    215 	    "Test consolidating entropy resets the epoch");
    216 }
    217 ATF_TC_BODY(consolidate, tc)
    218 {
    219 	unsigned char buf[32];
    220 	struct arc4random_prng *local, *global = &arc4random_global.prng;
    221 	unsigned localepoch, globalepoch;
    222 	const int consolidate = 1;
    223 	pthread_t thread;
    224 
    225 	/*
    226 	 * Get a sample from the global state to make sure the global
    227 	 * state is initialized.  Remember the epoch.
    228 	 */
    229 	arc4random_global_buf(buf, sizeof(buf));
    230 	ATF_CHECK(!iszero(buf, sizeof(buf)));	/* Pr[fail] = 1/2^256 */
    231 	ATF_CHECK(!iszero(&global->arc4_prng, sizeof(global->arc4_prng)));
    232 	ATF_CHECK((globalepoch = global->arc4_epoch) != 0);
    233 
    234 	/*
    235 	 * Get a sample from the local state too to make sure the local
    236 	 * state is initialized.  Remember the epoch.
    237 	 */
    238 	arc4random_buf(buf, sizeof(buf));
    239 	ATF_CHECK(!iszero(buf, sizeof(buf)));	/* Pr[fail] = 1/2^256 */
    240 	local = arc4random_prng();
    241 	ATF_CHECK(!iszero(&local->arc4_prng, sizeof(local->arc4_prng)));
    242 	ATF_CHECK((localepoch = local->arc4_epoch) != 0);
    243 
    244 	/*
    245 	 * Trigger entropy consolidation.
    246 	 */
    247 	RL(sysctlbyname("kern.entropy.consolidate", /*oldp*/NULL, /*oldlen*/0,
    248 		&consolidate, sizeof(consolidate)));
    249 
    250 	/*
    251 	 * Verify the epoch cache isn't changed yet until we ask for
    252 	 * more data.
    253 	 */
    254 	ATF_CHECK_EQ_MSG(globalepoch, global->arc4_epoch,
    255 	    "global epoch was %u, now %u", globalepoch, global->arc4_epoch);
    256 	ATF_CHECK_EQ_MSG(localepoch, local->arc4_epoch,
    257 	    "local epoch was %u, now %u", localepoch, local->arc4_epoch);
    258 
    259 	/*
    260 	 * Request new output and verify the local epoch cache has
    261 	 * changed.
    262 	 */
    263 	arc4random_buf(buf, sizeof(buf));
    264 	ATF_CHECK(!iszero(buf, sizeof(buf)));	/* Pr[fail] = 1/2^256 */
    265 	ATF_CHECK_MSG(localepoch != local->arc4_epoch,
    266 	    "local epoch unchanged from %u", localepoch);
    267 
    268 	/*
    269 	 * Create a new thread to grab output from the global state,
    270 	 * wait for it to complete, and verify the global epoch cache
    271 	 * has changed.  (Now that we have already used the local state
    272 	 * in this thread, we can't use the global state any more.)
    273 	 */
    274 	RZ(pthread_create(&thread, NULL, &arc4random_global_thread, NULL));
    275 	RZ(pthread_join(thread, NULL));
    276 	ATF_CHECK_MSG(globalepoch != global->arc4_epoch,
    277 	    "global epoch unchanged from %u", globalepoch);
    278 }
    279 
    280 ATF_TC(fork);
    281 ATF_TC_HEAD(fork, tc)
    282 {
    283 	atf_tc_set_md_var(tc, "descr",
    284 	    "Test fork zeros the state and gets independent state");
    285 }
    286 ATF_TC_BODY(fork, tc)
    287 {
    288 	unsigned char buf[32];
    289 	struct arc4random_prng *local, *global = &arc4random_global.prng;
    290 	struct arc4random_prng childstate;
    291 	int fd[2];
    292 	pid_t child, pid;
    293 	ssize_t nread;
    294 	int status;
    295 
    296 	/*
    297 	 * Get a sample from the global state to make sure the global
    298 	 * state is initialized.
    299 	 */
    300 	arc4random_global_buf(buf, sizeof(buf));
    301 	ATF_CHECK(!iszero(buf, sizeof(buf)));	/* Pr[fail] = 1/2^256 */
    302 	ATF_CHECK(!iszero(&global->arc4_prng, sizeof(global->arc4_prng)));
    303 	ATF_CHECK(global->arc4_epoch != 0);
    304 
    305 	/*
    306 	 * Get a sample from the local state too to make sure the local
    307 	 * state is initialized.
    308 	 */
    309 	arc4random_buf(buf, sizeof(buf));
    310 	ATF_CHECK(!iszero(buf, sizeof(buf)));	/* Pr[fail] = 1/2^256 */
    311 	local = arc4random_prng();
    312 	ATF_CHECK(!iszero(&local->arc4_prng, sizeof(local->arc4_prng)));
    313 	ATF_CHECK(local->arc4_epoch != 0);
    314 
    315 	/*
    316 	 * Create a pipe to transfer the state from child to parent.
    317 	 */
    318 	RL(pipe(fd));
    319 
    320 	/*
    321 	 * Fork a child.
    322 	 */
    323 	RL(child = fork());
    324 	if (child == 0) {
    325 		status = 0;
    326 
    327 		/*
    328 		 * Verify the states have been zero'd on fork.
    329 		 */
    330 		if (!iszero(local, sizeof(*local))) {
    331 			fprintf(stderr, "failed to zero local state\n");
    332 			status = 1;
    333 		}
    334 		if (!iszero(global, sizeof(*global))) {
    335 			fprintf(stderr, "failed to zero global state\n");
    336 			status = 1;
    337 		}
    338 
    339 		/*
    340 		 * Verify we generate nonzero output.
    341 		 */
    342 		arc4random_buf(buf, sizeof(buf));
    343 		if (iszero(buf, sizeof(buf))) {
    344 			fprintf(stderr, "failed to generate nonzero output\n");
    345 			status = 1;
    346 		}
    347 
    348 		/*
    349 		 * Share the state to compare with parent.
    350 		 */
    351 		if ((size_t)write(fd[1], local, sizeof(*local)) !=
    352 		    sizeof(*local)) {
    353 			fprintf(stderr, "failed to share local state\n");
    354 			status = 1;
    355 		}
    356 		_exit(status);
    357 	}
    358 
    359 	/*
    360 	 * Verify the global state has been zeroed as expected.  (This
    361 	 * way it is never available to the child, even shortly after
    362 	 * the fork syscall returns before the atfork handler is
    363 	 * called.)
    364 	 */
    365 	ATF_CHECK(iszero(global, sizeof(*global)));
    366 
    367 	/*
    368 	 * Read the state from the child.
    369 	 */
    370 	RL(nread = read(fd[0], &childstate, sizeof(childstate)));
    371 	ATF_CHECK_EQ_MSG(nread, sizeof(childstate),
    372 	    "nread=%zu sizeof(childstate)=%zu", nread, sizeof(childstate));
    373 
    374 	/*
    375 	 * Verify the child state is distinct.  (The global state has
    376 	 * been zero'd so it's OK it if coincides.)  Check again after
    377 	 * we grab another output.
    378 	 */
    379 	ATF_CHECK(memcmp(local, &childstate, sizeof(*local)) != 0);
    380 	arc4random_buf(buf, sizeof(buf));
    381 	ATF_CHECK(!iszero(buf, sizeof(buf)));	/* Pr[fail] = 1/2^256 */
    382 	ATF_CHECK(memcmp(local, &childstate, sizeof(*local)) != 0);
    383 
    384 	/*
    385 	 * Wait for the child to complete and verify it passed.
    386 	 */
    387 	RL(pid = waitpid(child, &status, 0));
    388 	ATF_CHECK_EQ_MSG(status, 0, "child exited with nonzero status=%d",
    389 	    status);
    390 }
    391 
    392 ATF_TC(global);
    393 ATF_TC_HEAD(global, tc)
    394 {
    395 	atf_tc_set_md_var(tc, "descr",
    396 	    "Test the global state is used when address space limit is hit");
    397 }
    398 ATF_TC_BODY(global, tc)
    399 {
    400 	unsigned char buf[32], buf1[32];
    401 
    402 	/*
    403 	 * Get a sample from the global state (and verify it was using
    404 	 * the global state).
    405 	 */
    406 	arc4random_global_buf(buf, sizeof(buf));
    407 
    408 	/*
    409 	 * Verify we got a sample.
    410 	 */
    411 	ATF_CHECK(!iszero(buf, sizeof(buf)));	/* Pr[fail] = 1/2^256 */
    412 
    413 	/*
    414 	 * Get a sample from whatever state and make sure it wasn't
    415 	 * repeated, which happens only with probability 1/2^256.
    416 	 */
    417 	arc4random_buf(buf1, sizeof(buf1));
    418 	ATF_CHECK(!iszero(buf1, sizeof(buf1)));	/* Pr[fail] = 1/2^256 */
    419 	ATF_CHECK(memcmp(buf, buf1, sizeof(buf)) != 0);
    420 }
    421 
    422 ATF_TC(local);
    423 ATF_TC_HEAD(local, tc)
    424 {
    425 	atf_tc_set_md_var(tc, "descr",
    426 	    "Test arc4random uses thread-local state");
    427 	/* XXX skip if libc was built without _REENTRANT */
    428 }
    429 ATF_TC_BODY(local, tc)
    430 {
    431 	unsigned char buf[32], buf1[32];
    432 	struct arc4random_prng *prng;
    433 
    434 	/*
    435 	 * Get a sample to start things off.
    436 	 */
    437 	arc4random_buf(buf, sizeof(buf));
    438 	ATF_CHECK(!iszero(buf, sizeof(buf)));	/* Pr[fail] = 1/2^256 */
    439 
    440 	/*
    441 	 * Verify the arc4random state is _not_ the global state.
    442 	 */
    443 	prng = arc4random_prng();
    444 	ATF_CHECK(prng != &arc4random_global.prng);
    445 	ATF_CHECK(!iszero(&prng->arc4_prng, sizeof(prng->arc4_prng)));
    446 	ATF_CHECK(prng->arc4_epoch != 0);
    447 
    448 	/*
    449 	 * Get another sample and make sure it wasn't repeated, which
    450 	 * happens only with probability 1/2^256.
    451 	 */
    452 	arc4random_buf(buf1, sizeof(buf1));
    453 	ATF_CHECK(!iszero(buf1, sizeof(buf1)));	/* Pr[fail] = 1/2^256 */
    454 	ATF_CHECK(memcmp(buf, buf1, sizeof(buf)) != 0);
    455 }
    456 
    457 ATF_TP_ADD_TCS(tp)
    458 {
    459 
    460 	ATF_TP_ADD_TC(tp, addrandom);
    461 	ATF_TP_ADD_TC(tp, consolidate);
    462 	ATF_TP_ADD_TC(tp, fork);
    463 	ATF_TP_ADD_TC(tp, global);
    464 	ATF_TP_ADD_TC(tp, local);
    465 
    466 	return atf_no_error();
    467 }
    468