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, ©.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, ©.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