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