xref: /netbsd-src/tests/lib/libc/gen/t_arc4random.c (revision 0d283a3a7ed9024e9a0b490e3ba1950a6e625f25)
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