1 /* $NetBSD: t_arc4random.c,v 1.5 2025/03/09 18:11:55 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.5 2025/03/09 18:11:55 riastradh Exp $"); 33 34 #include <sys/resource.h> 35 #include <sys/stat.h> 36 #include <sys/sysctl.h> 37 #include <sys/wait.h> 38 39 #include <atf-c.h> 40 #include <err.h> 41 #include <fcntl.h> 42 #include <paths.h> 43 #include <stdio.h> 44 #include <string.h> 45 #include <unistd.h> 46 47 #include "arc4random.h" 48 #include "reentrant.h" 49 #include "h_macros.h" 50 51 /* 52 * iszero(buf, len) 53 * 54 * True if len bytes at buf are all zero, false if any one of them 55 * is nonzero. 56 */ 57 static bool 58 iszero(const void *buf, size_t len) 59 { 60 const unsigned char *p = buf; 61 size_t i; 62 63 for (i = 0; i < len; i++) { 64 if (p[i] != 0) 65 return false; 66 } 67 return true; 68 } 69 70 /* 71 * arc4random_prng() 72 * 73 * Get a pointer to the current arc4random state, without updating 74 * any of the state, not even lazy initialization. 75 */ 76 static struct arc4random_prng * 77 arc4random_prng(void) 78 { 79 struct arc4random_prng *prng = NULL; 80 81 /* 82 * If arc4random has been initialized and there is a thread key 83 * (i.e., libc was built with _REENTRANT), get the thread-local 84 * arc4random state if there is one. 85 */ 86 if (arc4random_global.per_thread) 87 prng = thr_getspecific(arc4random_global.thread_key); 88 89 /* 90 * If we couldn't get the thread-local state, get the global 91 * state instead. 92 */ 93 if (prng == NULL) 94 prng = &arc4random_global.prng; 95 96 return prng; 97 } 98 99 /* 100 * arc4random_global_buf(buf, len) 101 * 102 * Same as arc4random_buf, but force use of the global state. 103 * Must happen before any other use of arc4random. 104 */ 105 static void 106 arc4random_global_buf(void *buf, size_t len) 107 { 108 struct rlimit rlim, orlim; 109 struct arc4random_prng *prng; 110 111 /* 112 * Save the address space limit. 113 */ 114 RL(getrlimit(RLIMIT_AS, &orlim)); 115 memcpy(&rlim, &orlim, sizeof(rlim)); 116 117 /* 118 * Get a sample while the address space limit is zero. This 119 * should try, and fail, to allocate a thread-local arc4random 120 * state with mmap(2). 121 */ 122 rlim.rlim_cur = 0; 123 RL(setrlimit(RLIMIT_AS, &rlim)); 124 arc4random_buf(buf, len); 125 RL(setrlimit(RLIMIT_AS, &orlim)); 126 127 /* 128 * Restore the address space limit. 129 */ 130 RL(setrlimit(RLIMIT_AS, &orlim)); 131 132 /* 133 * Verify the PRNG is the global one, not the thread-local one, 134 * and that it was initialized. 135 */ 136 prng = arc4random_prng(); 137 ATF_CHECK_EQ(prng, &arc4random_global.prng); 138 ATF_CHECK(!iszero(&prng->arc4_prng, sizeof(prng->arc4_prng))); 139 ATF_CHECK(prng->arc4_epoch != 0); 140 } 141 142 /* 143 * arc4random_global_thread(cookie) 144 * 145 * Start routine for a thread that just grabs an output from the 146 * global state. 147 */ 148 static void * 149 arc4random_global_thread(void *cookie) 150 { 151 unsigned char buf[32]; 152 153 arc4random_global_buf(buf, sizeof(buf)); 154 155 return NULL; 156 } 157 158 ATF_TC(addrandom); 159 ATF_TC_HEAD(addrandom, tc) 160 { 161 atf_tc_set_md_var(tc, "descr", 162 "Test arc4random_addrandom updates the state"); 163 } 164 ATF_TC_BODY(addrandom, tc) 165 { 166 unsigned char buf[32], zero32[32] = {0}; 167 struct arc4random_prng *prng, copy; 168 169 /* 170 * Get a sample to start things off. 171 */ 172 arc4random_buf(buf, sizeof(buf)); 173 ATF_CHECK(!iszero(buf, sizeof(buf))); /* Pr[fail] = 1/2^256 */ 174 175 /* 176 * By this point, the global state must be initialized -- if 177 * not, the process should have aborted. 178 */ 179 ATF_CHECK(arc4random_global.initialized); 180 181 /* 182 * Get the PRNG, global or local. By this point, the PRNG 183 * state should be nonzero (with overwhelmingly high 184 * probability) and the epoch should also be nonzero. 185 */ 186 prng = arc4random_prng(); 187 ATF_CHECK(!iszero(&prng->arc4_prng, sizeof(prng->arc4_prng))); 188 ATF_CHECK(prng->arc4_epoch != 0); 189 190 /* 191 * Save a copy and update the state with arc4random_addrandom. 192 */ 193 copy = *prng; 194 arc4random_addrandom(zero32, sizeof(zero32)); 195 196 /* 197 * The state should have changed. (The epoch may or may not.) 198 */ 199 ATF_CHECK(memcmp(&prng->arc4_prng, ©.arc4_prng, 200 sizeof(copy.arc4_prng)) != 0); 201 202 /* 203 * Save a copy and update the state with arc4random_stir. 204 */ 205 copy = *prng; 206 arc4random_stir(); 207 208 /* 209 * The state should have changed. (The epoch may or may not.) 210 */ 211 ATF_CHECK(memcmp(&prng->arc4_prng, ©.arc4_prng, 212 sizeof(copy.arc4_prng)) != 0); 213 } 214 215 ATF_TC(consolidate); 216 ATF_TC_HEAD(consolidate, tc) 217 { 218 atf_tc_set_md_var(tc, "descr", 219 "Test consolidating entropy resets the epoch"); 220 } 221 ATF_TC_BODY(consolidate, tc) 222 { 223 unsigned char buf[32]; 224 struct arc4random_prng *local, *global = &arc4random_global.prng; 225 unsigned localepoch, globalepoch; 226 const int consolidate = 1; 227 pthread_t thread; 228 229 /* 230 * Get a sample from the global state to make sure the global 231 * state is initialized. Remember the epoch. 232 */ 233 arc4random_global_buf(buf, sizeof(buf)); 234 ATF_CHECK(!iszero(buf, sizeof(buf))); /* Pr[fail] = 1/2^256 */ 235 ATF_CHECK(!iszero(&global->arc4_prng, sizeof(global->arc4_prng))); 236 ATF_CHECK((globalepoch = global->arc4_epoch) != 0); 237 238 /* 239 * Get a sample from the local state too to make sure the local 240 * state is initialized. Remember the epoch. 241 */ 242 arc4random_buf(buf, sizeof(buf)); 243 ATF_CHECK(!iszero(buf, sizeof(buf))); /* Pr[fail] = 1/2^256 */ 244 local = arc4random_prng(); 245 ATF_CHECK(!iszero(&local->arc4_prng, sizeof(local->arc4_prng))); 246 ATF_CHECK((localepoch = local->arc4_epoch) != 0); 247 248 /* 249 * Trigger entropy consolidation. 250 */ 251 RL(sysctlbyname("kern.entropy.consolidate", /*oldp*/NULL, /*oldlen*/0, 252 &consolidate, sizeof(consolidate))); 253 254 /* 255 * Verify the epoch cache isn't changed yet until we ask for 256 * more data. 257 */ 258 ATF_CHECK_EQ_MSG(globalepoch, global->arc4_epoch, 259 "global epoch was %u, now %u", globalepoch, global->arc4_epoch); 260 ATF_CHECK_EQ_MSG(localepoch, local->arc4_epoch, 261 "local epoch was %u, now %u", localepoch, local->arc4_epoch); 262 263 /* 264 * Request new output and verify the local epoch cache has 265 * changed. 266 */ 267 arc4random_buf(buf, sizeof(buf)); 268 ATF_CHECK(!iszero(buf, sizeof(buf))); /* Pr[fail] = 1/2^256 */ 269 ATF_CHECK_MSG(localepoch != local->arc4_epoch, 270 "local epoch unchanged from %u", localepoch); 271 272 /* 273 * Create a new thread to grab output from the global state, 274 * wait for it to complete, and verify the global epoch cache 275 * has changed. (Now that we have already used the local state 276 * in this thread, we can't use the global state any more.) 277 */ 278 RZ(pthread_create(&thread, NULL, &arc4random_global_thread, NULL)); 279 RZ(pthread_join(thread, NULL)); 280 ATF_CHECK_MSG(globalepoch != global->arc4_epoch, 281 "global epoch unchanged from %u", globalepoch); 282 } 283 284 ATF_TC(chroot); 285 ATF_TC_HEAD(chroot, tc) 286 { 287 atf_tc_set_md_var(tc, "descr", 288 "Test arc4random in an empty chroot"); 289 atf_tc_set_md_var(tc, "require.user", "root"); 290 } 291 ATF_TC_BODY(chroot, tc) 292 { 293 pid_t pid; 294 int status; 295 296 /* 297 * Create an empty chroot. 298 */ 299 RL(mkdir("root", 0500)); 300 301 /* 302 * In a child process, enter the chroot and verify that we 303 * can't open /dev/urandom but we can use arc4random. 304 * 305 * (atf gets unhappy if we chroot in the same process, when it 306 * later tries to create a results file.) 307 */ 308 RL(pid = fork()); 309 if (pid == 0) { 310 unsigned char buf[32] = {0}; 311 312 if (chroot("root") == -1) 313 err(1, "chroot"); 314 if (open(_PATH_URANDOM, O_RDONLY) != -1) 315 errx(1, "open /dev/urandom must fail in empty chroot"); 316 if (errno != ENOENT) { 317 err(1, "expected open to fail with %d=ENOENT, not %d", 318 ENOENT, errno); 319 } 320 arc4random_buf(buf, sizeof(buf)); 321 if (iszero(buf, sizeof(buf))) /* Pr[fail] = 1/2^256 */ 322 errx(1, "arc4random returned all-zero"); 323 if (arc4random_prng()->arc4_epoch == 0) 324 errx(1, "arc4random failed to observe entropy epoch"); 325 _exit(0); 326 } 327 328 /* 329 * Wait for the child process to finish. 330 */ 331 RL(waitpid(pid, &status, 0)); 332 ATF_CHECK_MSG(WIFEXITED(status) && WEXITSTATUS(status) == 0, 333 "child exited status 0x%x", status); 334 } 335 336 ATF_TC(fdlimit); 337 ATF_TC_HEAD(fdlimit, tc) 338 { 339 atf_tc_set_md_var(tc, "descr", 340 "Test arc4random works even if we have hit the fd limit"); 341 } 342 ATF_TC_BODY(fdlimit, tc) 343 { 344 pid_t pid; 345 int status; 346 347 /* 348 * In a child process, clamp down on the file descriptor 349 * resource limit and verify that we can't open /dev/urandom 350 * but we can use arc4random. 351 * 352 * (atf gets unhappy if we chroot in the same process, when it 353 * later tries to create a results file.) 354 */ 355 RL(pid = fork()); 356 if (pid == 0) { 357 struct rlimit rlim = {.rlim_cur = 0, .rlim_max = 0}; 358 unsigned char buf[32] = {0}; 359 360 if (setrlimit(RLIMIT_NOFILE, &rlim) == -1) 361 err(1, "setrlimit(RLIMIT_NOFILE)"); 362 if (open(_PATH_URANDOM, O_RDONLY) != -1) 363 errx(1, "open must fail with zero RLIMIT_NOFILE"); 364 if (errno != EMFILE) { 365 err(1, "expected open to fail with %d=EMFILE, not %d", 366 EMFILE, errno); 367 } 368 arc4random_buf(buf, sizeof(buf)); 369 if (iszero(buf, sizeof(buf))) /* Pr[fail] = 1/2^256 */ 370 errx(1, "arc4random returned all-zero"); 371 if (arc4random_prng()->arc4_epoch == 0) 372 errx(1, "arc4random failed to observe entropy epoch"); 373 _exit(0); 374 } 375 376 /* 377 * Wait for the child process to finish. 378 */ 379 RL(waitpid(pid, &status, 0)); 380 ATF_CHECK_MSG(WIFEXITED(status) && WEXITSTATUS(status) == 0, 381 "child exited status 0x%x", status); 382 } 383 384 ATF_TC(fork); 385 ATF_TC_HEAD(fork, tc) 386 { 387 atf_tc_set_md_var(tc, "descr", 388 "Test fork zeros the state and gets independent state"); 389 } 390 ATF_TC_BODY(fork, tc) 391 { 392 unsigned char buf[32]; 393 struct arc4random_prng *local, *global = &arc4random_global.prng; 394 struct arc4random_prng childstate; 395 int fd[2]; 396 pid_t child, pid; 397 ssize_t nread; 398 int status; 399 400 /* 401 * Get a sample from the global state to make sure the global 402 * state is initialized. 403 */ 404 arc4random_global_buf(buf, sizeof(buf)); 405 ATF_CHECK(!iszero(buf, sizeof(buf))); /* Pr[fail] = 1/2^256 */ 406 ATF_CHECK(!iszero(&global->arc4_prng, sizeof(global->arc4_prng))); 407 ATF_CHECK(global->arc4_epoch != 0); 408 409 /* 410 * Get a sample from the local state too to make sure the local 411 * state is initialized. 412 */ 413 arc4random_buf(buf, sizeof(buf)); 414 ATF_CHECK(!iszero(buf, sizeof(buf))); /* Pr[fail] = 1/2^256 */ 415 local = arc4random_prng(); 416 ATF_CHECK(!iszero(&local->arc4_prng, sizeof(local->arc4_prng))); 417 ATF_CHECK(local->arc4_epoch != 0); 418 419 /* 420 * Create a pipe to transfer the state from child to parent. 421 */ 422 RL(pipe(fd)); 423 424 /* 425 * Fork a child. 426 */ 427 RL(child = fork()); 428 if (child == 0) { 429 status = 0; 430 431 /* 432 * Verify the states have been zero'd on fork. 433 */ 434 if (!iszero(local, sizeof(*local))) { 435 fprintf(stderr, "failed to zero local state\n"); 436 status = 1; 437 } 438 if (!iszero(global, sizeof(*global))) { 439 fprintf(stderr, "failed to zero global state\n"); 440 status = 1; 441 } 442 443 /* 444 * Verify we generate nonzero output. 445 */ 446 arc4random_buf(buf, sizeof(buf)); 447 if (iszero(buf, sizeof(buf))) { 448 fprintf(stderr, "failed to generate nonzero output\n"); 449 status = 1; 450 } 451 452 /* 453 * Share the state to compare with parent. 454 */ 455 if ((size_t)write(fd[1], local, sizeof(*local)) != 456 sizeof(*local)) { 457 fprintf(stderr, "failed to share local state\n"); 458 status = 1; 459 } 460 _exit(status); 461 } 462 463 /* 464 * Verify the global state has been zeroed as expected. (This 465 * way it is never available to the child, even shortly after 466 * the fork syscall returns before the atfork handler is 467 * called.) 468 */ 469 ATF_CHECK(iszero(global, sizeof(*global))); 470 471 /* 472 * Read the state from the child. 473 */ 474 RL(nread = read(fd[0], &childstate, sizeof(childstate))); 475 ATF_CHECK_EQ_MSG(nread, sizeof(childstate), 476 "nread=%zu sizeof(childstate)=%zu", nread, sizeof(childstate)); 477 478 /* 479 * Verify the child state is distinct. (The global state has 480 * been zero'd so it's OK it if coincides.) Check again after 481 * we grab another output. 482 */ 483 ATF_CHECK(memcmp(local, &childstate, sizeof(*local)) != 0); 484 arc4random_buf(buf, sizeof(buf)); 485 ATF_CHECK(!iszero(buf, sizeof(buf))); /* Pr[fail] = 1/2^256 */ 486 ATF_CHECK(memcmp(local, &childstate, sizeof(*local)) != 0); 487 488 /* 489 * Wait for the child to complete and verify it passed. 490 */ 491 RL(pid = waitpid(child, &status, 0)); 492 ATF_CHECK_EQ_MSG(status, 0, "child exited with nonzero status=%d", 493 status); 494 } 495 496 ATF_TC(global_aslimit); 497 ATF_TC_HEAD(global_aslimit, tc) 498 { 499 atf_tc_set_md_var(tc, "descr", 500 "Test the global state is used when address space limit is hit"); 501 } 502 ATF_TC_BODY(global_aslimit, tc) 503 { 504 unsigned char buf[32], buf1[32]; 505 506 /* 507 * Get a sample from the global state (and verify it was using 508 * the global state). 509 */ 510 arc4random_global_buf(buf, sizeof(buf)); 511 512 /* 513 * Verify we got a sample. 514 */ 515 ATF_CHECK(!iszero(buf, sizeof(buf))); /* Pr[fail] = 1/2^256 */ 516 517 /* 518 * Get a sample from whatever state and make sure it wasn't 519 * repeated, which happens only with probability 1/2^256. 520 */ 521 arc4random_buf(buf1, sizeof(buf1)); 522 ATF_CHECK(!iszero(buf1, sizeof(buf1))); /* Pr[fail] = 1/2^256 */ 523 ATF_CHECK(memcmp(buf, buf1, sizeof(buf)) != 0); 524 } 525 526 ATF_TC(global_threadkeylimit); 527 ATF_TC_HEAD(global_threadkeylimit, tc) 528 { 529 atf_tc_set_md_var(tc, "descr", 530 "Test the global state is used we run out of thread keys"); 531 } 532 ATF_TC_BODY(global_threadkeylimit, tc) 533 { 534 unsigned char buf[32], buf1[32]; 535 536 /* 537 * Get a sample from the global state (and verify it was using 538 * the global state). 539 */ 540 arc4random_global_buf(buf, sizeof(buf)); 541 542 /* 543 * Verify we got a sample. 544 */ 545 ATF_CHECK(!iszero(buf, sizeof(buf))); /* Pr[fail] = 1/2^256 */ 546 547 /* 548 * Artificially disable the per-thread state, make it an 549 * invalid thread key altogether, and clear the epoch. Make 550 * sure we're using the global PRNG state now. 551 */ 552 arc4random_global.per_thread = false; 553 memset(&arc4random_global.thread_key, 0x5a, 554 sizeof(arc4random_global.thread_key)); 555 arc4random_global.prng.arc4_epoch = 0; 556 ATF_CHECK(arc4random_prng() == &arc4random_global.prng); 557 558 /* 559 * Get a sample again and make sure it wasn't repeated, which 560 * happens only with probability 1/2^256. 561 */ 562 arc4random_buf(buf1, sizeof(buf1)); 563 ATF_CHECK(!iszero(buf1, sizeof(buf1))); /* Pr[fail] = 1/2^256 */ 564 ATF_CHECK(memcmp(buf, buf1, sizeof(buf)) != 0); 565 566 /* 567 * Verify this had the effect of updating the global epoch, 568 * meaning we used the global state and not the per-thread 569 * state. 570 */ 571 ATF_CHECK(arc4random_global.prng.arc4_epoch != 0); 572 } 573 574 ATF_TC(local); 575 ATF_TC_HEAD(local, tc) 576 { 577 atf_tc_set_md_var(tc, "descr", 578 "Test arc4random uses thread-local state"); 579 /* XXX skip if libc was built without _REENTRANT */ 580 } 581 ATF_TC_BODY(local, tc) 582 { 583 unsigned char buf[32], buf1[32]; 584 struct arc4random_prng *prng; 585 586 /* 587 * Get a sample to start things off. 588 */ 589 arc4random_buf(buf, sizeof(buf)); 590 ATF_CHECK(!iszero(buf, sizeof(buf))); /* Pr[fail] = 1/2^256 */ 591 592 /* 593 * Verify the arc4random state is _not_ the global state. 594 */ 595 prng = arc4random_prng(); 596 ATF_CHECK(prng != &arc4random_global.prng); 597 ATF_CHECK(!iszero(&prng->arc4_prng, sizeof(prng->arc4_prng))); 598 ATF_CHECK(prng->arc4_epoch != 0); 599 600 /* 601 * Get another sample and make sure it wasn't repeated, which 602 * happens only with probability 1/2^256. 603 */ 604 arc4random_buf(buf1, sizeof(buf1)); 605 ATF_CHECK(!iszero(buf1, sizeof(buf1))); /* Pr[fail] = 1/2^256 */ 606 ATF_CHECK(memcmp(buf, buf1, sizeof(buf)) != 0); 607 } 608 609 ATF_TC(stackfallback); 610 ATF_TC_HEAD(stackfallback, tc) 611 { 612 atf_tc_set_md_var(tc, "descr", 613 "Test arc4random with pthread_atfork and thr_keycreate failure"); 614 } 615 ATF_TC_BODY(stackfallback, tc) 616 { 617 unsigned char buf[32], buf1[32]; 618 struct arc4random_prng *local; 619 620 /* 621 * Get a sample to start things off. This makes the library 622 * gets initialized. 623 */ 624 arc4random_buf(buf, sizeof(buf)); 625 ATF_CHECK(!iszero(buf, sizeof(buf))); /* Pr[fail] = 1/2^256 */ 626 627 /* 628 * Clear the arc4random global state, and the local state if it 629 * exists, and pretend pthread_atfork and thr_keycreate had 630 * both failed. 631 */ 632 memset(&arc4random_global.prng, 0, sizeof(arc4random_global.prng)); 633 if ((local = arc4random_prng()) != NULL) 634 memset(local, 0, sizeof(*local)); 635 arc4random_global.forksafe = false; 636 arc4random_global.per_thread = false; 637 638 /* 639 * Make sure it still works to get a sample. 640 */ 641 arc4random_buf(buf1, sizeof(buf1)); 642 ATF_CHECK(!iszero(buf, sizeof(buf))); /* Pr[fail] = 1/2^256 */ 643 ATF_CHECK(memcmp(buf, buf1, sizeof(buf)) != 0); 644 645 /* 646 * Make sure the global and local epochs did not change. 647 */ 648 ATF_CHECK_EQ_MSG(arc4random_global.prng.arc4_epoch, 0, 649 "global epoch: %d", arc4random_global.prng.arc4_epoch); 650 if (local != NULL) { 651 ATF_CHECK_EQ_MSG(local->arc4_epoch, 0, 652 "local epoch: %d", local->arc4_epoch); 653 } 654 } 655 656 ATF_TP_ADD_TCS(tp) 657 { 658 659 ATF_TP_ADD_TC(tp, addrandom); 660 ATF_TP_ADD_TC(tp, chroot); 661 ATF_TP_ADD_TC(tp, consolidate); 662 ATF_TP_ADD_TC(tp, fdlimit); 663 ATF_TP_ADD_TC(tp, fork); 664 ATF_TP_ADD_TC(tp, global_aslimit); 665 ATF_TP_ADD_TC(tp, global_threadkeylimit); 666 ATF_TP_ADD_TC(tp, local); 667 ATF_TP_ADD_TC(tp, stackfallback); 668 669 return atf_no_error(); 670 } 671