diff options
Diffstat (limited to 'lib/libc/gen')
| -rw-r--r-- | lib/libc/gen/Makefile.inc | 1 | ||||
| -rw-r--r-- | lib/libc/gen/h_ctype_abuse.c | 138 | ||||
| -rw-r--r-- | lib/libc/gen/h_execsig.c | 55 | ||||
| -rw-r--r-- | lib/libc/gen/t_arc4random.c | 670 | ||||
| -rw-r--r-- | lib/libc/gen/t_ctype.c | 1236 | ||||
| -rw-r--r-- | lib/libc/gen/t_timespec_get.c | 123 |
6 files changed, 2223 insertions, 0 deletions
diff --git a/lib/libc/gen/Makefile.inc b/lib/libc/gen/Makefile.inc new file mode 100644 index 000000000000..01b5f23410c8 --- /dev/null +++ b/lib/libc/gen/Makefile.inc @@ -0,0 +1 @@ +.include "../Makefile.inc" diff --git a/lib/libc/gen/h_ctype_abuse.c b/lib/libc/gen/h_ctype_abuse.c new file mode 100644 index 000000000000..fdff8552f8f4 --- /dev/null +++ b/lib/libc/gen/h_ctype_abuse.c @@ -0,0 +1,138 @@ +/* $NetBSD: h_ctype_abuse.c,v 1.2 2025/09/15 17:32:01 riastradh Exp $ */ + +/*- + * Copyright (c) 2025 The NetBSD Foundation, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * Helper program to verify effects of ctype(3) abuse. + * + * NOTE: This program intentionally triggers undefined behaviour by + * passing int values to the ctype(3) functions which are neither EOF + * nor representable by unsigned char. The purpose is to verify that + * NetBSD's ctype(3) _does not_ trap the undefined behaviour when the + * environment variable LIBC_ALLOWCTYPEABUSE. (It does not check + * anything about the results, which are perforce nonsense -- just that + * it gets a result without crashing.) + */ + +#include <sys/cdefs.h> +__RCSID("$NetBSD: h_ctype_abuse.c,v 1.2 2025/09/15 17:32:01 riastradh Exp $"); + +#include <ctype.h> +#include <err.h> +#include <limits.h> +#include <locale.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#define FOREACHCTYPE(M) \ + M(ISALPHA, isalpha) \ + M(ISUPPER, isupper) \ + M(ISLOWER, islower) \ + M(ISDIGIT, isdigit) \ + M(ISXDIGIT, isxdigit) \ + M(ISALNUM, isalnum) \ + M(ISSPACE, isspace) \ + M(ISPUNCT, ispunct) \ + M(ISPRINT, isprint) \ + M(ISGRAPH, isgraph) \ + M(ISCNTRL, iscntrl) \ + M(ISBLANK, isblank) \ + M(TOUPPER, toupper) \ + M(TOLOWER, tolower) + +int +main(int argc, char **argv) +{ + enum { +#define M(upper, lower) upper, + FOREACHCTYPE(M) +#undef M + } fn; + enum { + MACRO, + FUNCTION, + } mode; + int ch; + volatile int result; + + setprogname(argv[0]); + if (argc != 3 && argc != 4) { + errx(1, "Usage: %s <function> <mode> [<locale>]", + getprogname()); + } + +#define M(upper, lower) \ + else if (strcmp(argv[1], #lower) == 0) \ + fn = upper; + + if (0) + ; + FOREACHCTYPE(M) + else + errx(1, "unknown ctype function"); +#undef M + + if (strcmp(argv[2], "macro") == 0) + mode = MACRO; + else if (strcmp(argv[2], "function") == 0) + mode = FUNCTION; + else + errx(1, "unknown usage mode"); + + if (argc == 4) { + if (setlocale(LC_CTYPE, argv[3]) == NULL) + err(1, "setlocale"); + } + + /* + * Make sure we cover EOF as well. + */ + __CTASSERT(CHAR_MIN == 0 || CHAR_MIN <= EOF); + __CTASSERT(EOF <= UCHAR_MAX); + + for (ch = (CHAR_MIN == 0 ? EOF : CHAR_MIN); ch <= UCHAR_MAX; ch++) { + switch (fn) { +#define M(upper, lower) \ + case upper: \ + switch (mode) { \ + case MACRO: \ + result = lower(ch); \ + break; \ + case FUNCTION: \ + result = (lower)(ch); \ + break; \ + } \ + break; + FOREACHCTYPE(M) +#undef M + } + (void)result; + } + + return 0; +} diff --git a/lib/libc/gen/h_execsig.c b/lib/libc/gen/h_execsig.c new file mode 100644 index 000000000000..a954ca071094 --- /dev/null +++ b/lib/libc/gen/h_execsig.c @@ -0,0 +1,55 @@ +/* $NetBSD: h_execsig.c,v 1.1 2025/03/13 01:27:27 riastradh Exp $ */ + +/*- + * Copyright (c) 2025 The NetBSD Foundation, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include <sys/cdefs.h> +__RCSID("$NetBSD: h_execsig.c,v 1.1 2025/03/13 01:27:27 riastradh Exp $"); + +/* + * Helper program for testing signal delivery during execve(2) and + * posix_spawn(2). The caller will: + * + * 1. fork and exec, or spawn + * 2. kill the child + * 3. write a byte to be read from the child's stdin + * + * Since the caller issues syscalls in that order, the signal should be + * delivered to the child first, and it should be interrupted by a + * signal before it returnsa byte from read(2). + */ + +#include <err.h> +#include <unistd.h> + +int +main(void) +{ + + if (read(STDIN_FILENO, (char[]){0}, 1) == -1) + err(1, "read"); + return 0; +} diff --git a/lib/libc/gen/t_arc4random.c b/lib/libc/gen/t_arc4random.c new file mode 100644 index 000000000000..b26c1b8a931a --- /dev/null +++ b/lib/libc/gen/t_arc4random.c @@ -0,0 +1,670 @@ +/* $NetBSD: t_arc4random.c,v 1.5 2025/03/09 18:11:55 riastradh Exp $ */ + +/*- + * Copyright (c) 2024 The NetBSD Foundation, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#define _REENTRANT + +#include <sys/cdefs.h> +__RCSID("$NetBSD: t_arc4random.c,v 1.5 2025/03/09 18:11:55 riastradh Exp $"); + +#include <sys/resource.h> +#include <sys/stat.h> +#include <sys/sysctl.h> +#include <sys/wait.h> + +#include <atf-c.h> +#include <err.h> +#include <fcntl.h> +#include <paths.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> + +#include "arc4random.h" +#include "reentrant.h" +#include "h_macros.h" + +/* + * iszero(buf, len) + * + * True if len bytes at buf are all zero, false if any one of them + * is nonzero. + */ +static bool +iszero(const void *buf, size_t len) +{ + const unsigned char *p = buf; + size_t i; + + for (i = 0; i < len; i++) { + if (p[i] != 0) + return false; + } + return true; +} + +/* + * arc4random_prng() + * + * Get a pointer to the current arc4random state, without updating + * any of the state, not even lazy initialization. + */ +static struct arc4random_prng * +arc4random_prng(void) +{ + struct arc4random_prng *prng = NULL; + + /* + * If arc4random has been initialized and there is a thread key + * (i.e., libc was built with _REENTRANT), get the thread-local + * arc4random state if there is one. + */ + if (arc4random_global.per_thread) + prng = thr_getspecific(arc4random_global.thread_key); + + /* + * If we couldn't get the thread-local state, get the global + * state instead. + */ + if (prng == NULL) + prng = &arc4random_global.prng; + + return prng; +} + +/* + * arc4random_global_buf(buf, len) + * + * Same as arc4random_buf, but force use of the global state. + * Must happen before any other use of arc4random. + */ +static void +arc4random_global_buf(void *buf, size_t len) +{ + struct rlimit rlim, orlim; + struct arc4random_prng *prng; + + /* + * Save the address space limit. + */ + RL(getrlimit(RLIMIT_AS, &orlim)); + memcpy(&rlim, &orlim, sizeof(rlim)); + + /* + * Get a sample while the address space limit is zero. This + * should try, and fail, to allocate a thread-local arc4random + * state with mmap(2). + */ + rlim.rlim_cur = 0; + RL(setrlimit(RLIMIT_AS, &rlim)); + arc4random_buf(buf, len); + RL(setrlimit(RLIMIT_AS, &orlim)); + + /* + * Restore the address space limit. + */ + RL(setrlimit(RLIMIT_AS, &orlim)); + + /* + * Verify the PRNG is the global one, not the thread-local one, + * and that it was initialized. + */ + prng = arc4random_prng(); + ATF_CHECK_EQ(prng, &arc4random_global.prng); + ATF_CHECK(!iszero(&prng->arc4_prng, sizeof(prng->arc4_prng))); + ATF_CHECK(prng->arc4_epoch != 0); +} + +/* + * arc4random_global_thread(cookie) + * + * Start routine for a thread that just grabs an output from the + * global state. + */ +static void * +arc4random_global_thread(void *cookie) +{ + unsigned char buf[32]; + + arc4random_global_buf(buf, sizeof(buf)); + + return NULL; +} + +ATF_TC(addrandom); +ATF_TC_HEAD(addrandom, tc) +{ + atf_tc_set_md_var(tc, "descr", + "Test arc4random_addrandom updates the state"); +} +ATF_TC_BODY(addrandom, tc) +{ + unsigned char buf[32], zero32[32] = {0}; + struct arc4random_prng *prng, copy; + + /* + * Get a sample to start things off. + */ + arc4random_buf(buf, sizeof(buf)); + ATF_CHECK(!iszero(buf, sizeof(buf))); /* Pr[fail] = 1/2^256 */ + + /* + * By this point, the global state must be initialized -- if + * not, the process should have aborted. + */ + ATF_CHECK(arc4random_global.initialized); + + /* + * Get the PRNG, global or local. By this point, the PRNG + * state should be nonzero (with overwhelmingly high + * probability) and the epoch should also be nonzero. + */ + prng = arc4random_prng(); + ATF_CHECK(!iszero(&prng->arc4_prng, sizeof(prng->arc4_prng))); + ATF_CHECK(prng->arc4_epoch != 0); + + /* + * Save a copy and update the state with arc4random_addrandom. + */ + copy = *prng; + arc4random_addrandom(zero32, sizeof(zero32)); + + /* + * The state should have changed. (The epoch may or may not.) + */ + ATF_CHECK(memcmp(&prng->arc4_prng, ©.arc4_prng, + sizeof(copy.arc4_prng)) != 0); + + /* + * Save a copy and update the state with arc4random_stir. + */ + copy = *prng; + arc4random_stir(); + + /* + * The state should have changed. (The epoch may or may not.) + */ + ATF_CHECK(memcmp(&prng->arc4_prng, ©.arc4_prng, + sizeof(copy.arc4_prng)) != 0); +} + +ATF_TC(consolidate); +ATF_TC_HEAD(consolidate, tc) +{ + atf_tc_set_md_var(tc, "descr", + "Test consolidating entropy resets the epoch"); +} +ATF_TC_BODY(consolidate, tc) +{ + unsigned char buf[32]; + struct arc4random_prng *local, *global = &arc4random_global.prng; + unsigned localepoch, globalepoch; + const int consolidate = 1; + pthread_t thread; + + /* + * Get a sample from the global state to make sure the global + * state is initialized. Remember the epoch. + */ + arc4random_global_buf(buf, sizeof(buf)); + ATF_CHECK(!iszero(buf, sizeof(buf))); /* Pr[fail] = 1/2^256 */ + ATF_CHECK(!iszero(&global->arc4_prng, sizeof(global->arc4_prng))); + ATF_CHECK((globalepoch = global->arc4_epoch) != 0); + + /* + * Get a sample from the local state too to make sure the local + * state is initialized. Remember the epoch. + */ + arc4random_buf(buf, sizeof(buf)); + ATF_CHECK(!iszero(buf, sizeof(buf))); /* Pr[fail] = 1/2^256 */ + local = arc4random_prng(); + ATF_CHECK(!iszero(&local->arc4_prng, sizeof(local->arc4_prng))); + ATF_CHECK((localepoch = local->arc4_epoch) != 0); + + /* + * Trigger entropy consolidation. + */ + RL(sysctlbyname("kern.entropy.consolidate", /*oldp*/NULL, /*oldlen*/0, + &consolidate, sizeof(consolidate))); + + /* + * Verify the epoch cache isn't changed yet until we ask for + * more data. + */ + ATF_CHECK_EQ_MSG(globalepoch, global->arc4_epoch, + "global epoch was %u, now %u", globalepoch, global->arc4_epoch); + ATF_CHECK_EQ_MSG(localepoch, local->arc4_epoch, + "local epoch was %u, now %u", localepoch, local->arc4_epoch); + + /* + * Request new output and verify the local epoch cache has + * changed. + */ + arc4random_buf(buf, sizeof(buf)); + ATF_CHECK(!iszero(buf, sizeof(buf))); /* Pr[fail] = 1/2^256 */ + ATF_CHECK_MSG(localepoch != local->arc4_epoch, + "local epoch unchanged from %u", localepoch); + + /* + * Create a new thread to grab output from the global state, + * wait for it to complete, and verify the global epoch cache + * has changed. (Now that we have already used the local state + * in this thread, we can't use the global state any more.) + */ + RZ(pthread_create(&thread, NULL, &arc4random_global_thread, NULL)); + RZ(pthread_join(thread, NULL)); + ATF_CHECK_MSG(globalepoch != global->arc4_epoch, + "global epoch unchanged from %u", globalepoch); +} + +ATF_TC(chroot); +ATF_TC_HEAD(chroot, tc) +{ + atf_tc_set_md_var(tc, "descr", + "Test arc4random in an empty chroot"); + atf_tc_set_md_var(tc, "require.user", "root"); +} +ATF_TC_BODY(chroot, tc) +{ + pid_t pid; + int status; + + /* + * Create an empty chroot. + */ + RL(mkdir("root", 0500)); + + /* + * In a child process, enter the chroot and verify that we + * can't open /dev/urandom but we can use arc4random. + * + * (atf gets unhappy if we chroot in the same process, when it + * later tries to create a results file.) + */ + RL(pid = fork()); + if (pid == 0) { + unsigned char buf[32] = {0}; + + if (chroot("root") == -1) + err(1, "chroot"); + if (open(_PATH_URANDOM, O_RDONLY) != -1) + errx(1, "open /dev/urandom must fail in empty chroot"); + if (errno != ENOENT) { + err(1, "expected open to fail with %d=ENOENT, not %d", + ENOENT, errno); + } + arc4random_buf(buf, sizeof(buf)); + if (iszero(buf, sizeof(buf))) /* Pr[fail] = 1/2^256 */ + errx(1, "arc4random returned all-zero"); + if (arc4random_prng()->arc4_epoch == 0) + errx(1, "arc4random failed to observe entropy epoch"); + _exit(0); + } + + /* + * Wait for the child process to finish. + */ + RL(waitpid(pid, &status, 0)); + ATF_CHECK_MSG(WIFEXITED(status) && WEXITSTATUS(status) == 0, + "child exited status 0x%x", status); +} + +ATF_TC(fdlimit); +ATF_TC_HEAD(fdlimit, tc) +{ + atf_tc_set_md_var(tc, "descr", + "Test arc4random works even if we have hit the fd limit"); +} +ATF_TC_BODY(fdlimit, tc) +{ + pid_t pid; + int status; + + /* + * In a child process, clamp down on the file descriptor + * resource limit and verify that we can't open /dev/urandom + * but we can use arc4random. + * + * (atf gets unhappy if we chroot in the same process, when it + * later tries to create a results file.) + */ + RL(pid = fork()); + if (pid == 0) { + struct rlimit rlim = {.rlim_cur = 0, .rlim_max = 0}; + unsigned char buf[32] = {0}; + + if (setrlimit(RLIMIT_NOFILE, &rlim) == -1) + err(1, "setrlimit(RLIMIT_NOFILE)"); + if (open(_PATH_URANDOM, O_RDONLY) != -1) + errx(1, "open must fail with zero RLIMIT_NOFILE"); + if (errno != EMFILE) { + err(1, "expected open to fail with %d=EMFILE, not %d", + EMFILE, errno); + } + arc4random_buf(buf, sizeof(buf)); + if (iszero(buf, sizeof(buf))) /* Pr[fail] = 1/2^256 */ + errx(1, "arc4random returned all-zero"); + if (arc4random_prng()->arc4_epoch == 0) + errx(1, "arc4random failed to observe entropy epoch"); + _exit(0); + } + + /* + * Wait for the child process to finish. + */ + RL(waitpid(pid, &status, 0)); + ATF_CHECK_MSG(WIFEXITED(status) && WEXITSTATUS(status) == 0, + "child exited status 0x%x", status); +} + +ATF_TC(fork); +ATF_TC_HEAD(fork, tc) +{ + atf_tc_set_md_var(tc, "descr", + "Test fork zeros the state and gets independent state"); +} +ATF_TC_BODY(fork, tc) +{ + unsigned char buf[32]; + struct arc4random_prng *local, *global = &arc4random_global.prng; + struct arc4random_prng childstate; + int fd[2]; + pid_t child, pid; + ssize_t nread; + int status; + + /* + * Get a sample from the global state to make sure the global + * state is initialized. + */ + arc4random_global_buf(buf, sizeof(buf)); + ATF_CHECK(!iszero(buf, sizeof(buf))); /* Pr[fail] = 1/2^256 */ + ATF_CHECK(!iszero(&global->arc4_prng, sizeof(global->arc4_prng))); + ATF_CHECK(global->arc4_epoch != 0); + + /* + * Get a sample from the local state too to make sure the local + * state is initialized. + */ + arc4random_buf(buf, sizeof(buf)); + ATF_CHECK(!iszero(buf, sizeof(buf))); /* Pr[fail] = 1/2^256 */ + local = arc4random_prng(); + ATF_CHECK(!iszero(&local->arc4_prng, sizeof(local->arc4_prng))); + ATF_CHECK(local->arc4_epoch != 0); + + /* + * Create a pipe to transfer the state from child to parent. + */ + RL(pipe(fd)); + + /* + * Fork a child. + */ + RL(child = fork()); + if (child == 0) { + status = 0; + + /* + * Verify the states have been zero'd on fork. + */ + if (!iszero(local, sizeof(*local))) { + fprintf(stderr, "failed to zero local state\n"); + status = 1; + } + if (!iszero(global, sizeof(*global))) { + fprintf(stderr, "failed to zero global state\n"); + status = 1; + } + + /* + * Verify we generate nonzero output. + */ + arc4random_buf(buf, sizeof(buf)); + if (iszero(buf, sizeof(buf))) { + fprintf(stderr, "failed to generate nonzero output\n"); + status = 1; + } + + /* + * Share the state to compare with parent. + */ + if ((size_t)write(fd[1], local, sizeof(*local)) != + sizeof(*local)) { + fprintf(stderr, "failed to share local state\n"); + status = 1; + } + _exit(status); + } + + /* + * Verify the global state has been zeroed as expected. (This + * way it is never available to the child, even shortly after + * the fork syscall returns before the atfork handler is + * called.) + */ + ATF_CHECK(iszero(global, sizeof(*global))); + + /* + * Read the state from the child. + */ + RL(nread = read(fd[0], &childstate, sizeof(childstate))); + ATF_CHECK_EQ_MSG(nread, sizeof(childstate), + "nread=%zu sizeof(childstate)=%zu", nread, sizeof(childstate)); + + /* + * Verify the child state is distinct. (The global state has + * been zero'd so it's OK it if coincides.) Check again after + * we grab another output. + */ + ATF_CHECK(memcmp(local, &childstate, sizeof(*local)) != 0); + arc4random_buf(buf, sizeof(buf)); + ATF_CHECK(!iszero(buf, sizeof(buf))); /* Pr[fail] = 1/2^256 */ + ATF_CHECK(memcmp(local, &childstate, sizeof(*local)) != 0); + + /* + * Wait for the child to complete and verify it passed. + */ + RL(pid = waitpid(child, &status, 0)); + ATF_CHECK_EQ_MSG(status, 0, "child exited with nonzero status=%d", + status); +} + +ATF_TC(global_aslimit); +ATF_TC_HEAD(global_aslimit, tc) +{ + atf_tc_set_md_var(tc, "descr", + "Test the global state is used when address space limit is hit"); +} +ATF_TC_BODY(global_aslimit, tc) +{ + unsigned char buf[32], buf1[32]; + + /* + * Get a sample from the global state (and verify it was using + * the global state). + */ + arc4random_global_buf(buf, sizeof(buf)); + + /* + * Verify we got a sample. + */ + ATF_CHECK(!iszero(buf, sizeof(buf))); /* Pr[fail] = 1/2^256 */ + + /* + * Get a sample from whatever state and make sure it wasn't + * repeated, which happens only with probability 1/2^256. + */ + arc4random_buf(buf1, sizeof(buf1)); + ATF_CHECK(!iszero(buf1, sizeof(buf1))); /* Pr[fail] = 1/2^256 */ + ATF_CHECK(memcmp(buf, buf1, sizeof(buf)) != 0); +} + +ATF_TC(global_threadkeylimit); +ATF_TC_HEAD(global_threadkeylimit, tc) +{ + atf_tc_set_md_var(tc, "descr", + "Test the global state is used we run out of thread keys"); +} +ATF_TC_BODY(global_threadkeylimit, tc) +{ + unsigned char buf[32], buf1[32]; + + /* + * Get a sample from the global state (and verify it was using + * the global state). + */ + arc4random_global_buf(buf, sizeof(buf)); + + /* + * Verify we got a sample. + */ + ATF_CHECK(!iszero(buf, sizeof(buf))); /* Pr[fail] = 1/2^256 */ + + /* + * Artificially disable the per-thread state, make it an + * invalid thread key altogether, and clear the epoch. Make + * sure we're using the global PRNG state now. + */ + arc4random_global.per_thread = false; + memset(&arc4random_global.thread_key, 0x5a, + sizeof(arc4random_global.thread_key)); + arc4random_global.prng.arc4_epoch = 0; + ATF_CHECK(arc4random_prng() == &arc4random_global.prng); + + /* + * Get a sample again and make sure it wasn't repeated, which + * happens only with probability 1/2^256. + */ + arc4random_buf(buf1, sizeof(buf1)); + ATF_CHECK(!iszero(buf1, sizeof(buf1))); /* Pr[fail] = 1/2^256 */ + ATF_CHECK(memcmp(buf, buf1, sizeof(buf)) != 0); + + /* + * Verify this had the effect of updating the global epoch, + * meaning we used the global state and not the per-thread + * state. + */ + ATF_CHECK(arc4random_global.prng.arc4_epoch != 0); +} + +ATF_TC(local); +ATF_TC_HEAD(local, tc) +{ + atf_tc_set_md_var(tc, "descr", + "Test arc4random uses thread-local state"); + /* XXX skip if libc was built without _REENTRANT */ +} +ATF_TC_BODY(local, tc) +{ + unsigned char buf[32], buf1[32]; + struct arc4random_prng *prng; + + /* + * Get a sample to start things off. + */ + arc4random_buf(buf, sizeof(buf)); + ATF_CHECK(!iszero(buf, sizeof(buf))); /* Pr[fail] = 1/2^256 */ + + /* + * Verify the arc4random state is _not_ the global state. + */ + prng = arc4random_prng(); + ATF_CHECK(prng != &arc4random_global.prng); + ATF_CHECK(!iszero(&prng->arc4_prng, sizeof(prng->arc4_prng))); + ATF_CHECK(prng->arc4_epoch != 0); + + /* + * Get another sample and make sure it wasn't repeated, which + * happens only with probability 1/2^256. + */ + arc4random_buf(buf1, sizeof(buf1)); + ATF_CHECK(!iszero(buf1, sizeof(buf1))); /* Pr[fail] = 1/2^256 */ + ATF_CHECK(memcmp(buf, buf1, sizeof(buf)) != 0); +} + +ATF_TC(stackfallback); +ATF_TC_HEAD(stackfallback, tc) +{ + atf_tc_set_md_var(tc, "descr", + "Test arc4random with pthread_atfork and thr_keycreate failure"); +} +ATF_TC_BODY(stackfallback, tc) +{ + unsigned char buf[32], buf1[32]; + struct arc4random_prng *local; + + /* + * Get a sample to start things off. This makes the library + * gets initialized. + */ + arc4random_buf(buf, sizeof(buf)); + ATF_CHECK(!iszero(buf, sizeof(buf))); /* Pr[fail] = 1/2^256 */ + + /* + * Clear the arc4random global state, and the local state if it + * exists, and pretend pthread_atfork and thr_keycreate had + * both failed. + */ + memset(&arc4random_global.prng, 0, sizeof(arc4random_global.prng)); + if ((local = arc4random_prng()) != NULL) + memset(local, 0, sizeof(*local)); + arc4random_global.forksafe = false; + arc4random_global.per_thread = false; + + /* + * Make sure it still works to get a sample. + */ + arc4random_buf(buf1, sizeof(buf1)); + ATF_CHECK(!iszero(buf, sizeof(buf))); /* Pr[fail] = 1/2^256 */ + ATF_CHECK(memcmp(buf, buf1, sizeof(buf)) != 0); + + /* + * Make sure the global and local epochs did not change. + */ + ATF_CHECK_EQ_MSG(arc4random_global.prng.arc4_epoch, 0, + "global epoch: %d", arc4random_global.prng.arc4_epoch); + if (local != NULL) { + ATF_CHECK_EQ_MSG(local->arc4_epoch, 0, + "local epoch: %d", local->arc4_epoch); + } +} + +ATF_TP_ADD_TCS(tp) +{ + + ATF_TP_ADD_TC(tp, addrandom); + ATF_TP_ADD_TC(tp, chroot); + ATF_TP_ADD_TC(tp, consolidate); + ATF_TP_ADD_TC(tp, fdlimit); + ATF_TP_ADD_TC(tp, fork); + ATF_TP_ADD_TC(tp, global_aslimit); + ATF_TP_ADD_TC(tp, global_threadkeylimit); + ATF_TP_ADD_TC(tp, local); + ATF_TP_ADD_TC(tp, stackfallback); + + return atf_no_error(); +} diff --git a/lib/libc/gen/t_ctype.c b/lib/libc/gen/t_ctype.c new file mode 100644 index 000000000000..ee4db34304b7 --- /dev/null +++ b/lib/libc/gen/t_ctype.c @@ -0,0 +1,1236 @@ +/* $NetBSD: t_ctype.c,v 1.12 2025/09/15 00:11:55 riastradh Exp $ */ + +/*- + * Copyright (c) 2025 The NetBSD Foundation, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * Tests for the ctype(3) character classification macros. + * + * NOTE: These tests intentionally trigger undefined behaviour by + * passing int values to the ctype(3) functions which are neither EOF + * nor representable by unsigned char. The purpose is to verify + * NetBSD's intentional trapping of this undefined behaviour -- or + * intentional allowing of this undefined behaviour, when the + * environment variable LIBC_ALLOWCTYPEABUSE is set. + */ + +#include <sys/cdefs.h> +__RCSID("$NetBSD: t_ctype.c,v 1.12 2025/09/15 00:11:55 riastradh Exp $"); + +#include <sys/wait.h> + +#include <atf-c.h> +#include <ctype.h> +#include <locale.h> +#include <limits.h> +#include <setjmp.h> +#include <signal.h> +#include <stdio.h> +#include <unistd.h> + +#include "h_macros.h" + +#ifdef __CHAR_UNSIGNED__ +enum { CHAR_UNSIGNED = 1 }; +#else +enum { CHAR_UNSIGNED = 0 }; +#endif + +/* + * libc has a guard page for the LC_CTYPE=C ctype(3) tables only on + * some platforms. We skip it if char is unsigned (in which case the + * common kind of ctype(3) abuse is unlikely). We also skip it in + * static builds -- this is determined in the Makefile. + */ +#ifndef _CTYPE_GUARD_PAGE +# ifdef __CHAR_UNSIGNED__ +# define _CTYPE_GUARD_PAGE 0 +# else +# define _CTYPE_GUARD_PAGE 1 +# endif +#endif + +static const char *const locales[] = { "C.UTF-8", "fr_FR.ISO8859-1", "C" }; + +static int isalpha_wrapper(int ch) { return isalpha(ch); } +static int isupper_wrapper(int ch) { return isupper(ch); } +static int islower_wrapper(int ch) { return islower(ch); } +static int isdigit_wrapper(int ch) { return isdigit(ch); } +static int isxdigit_wrapper(int ch) { return isxdigit(ch); } +static int isalnum_wrapper(int ch) { return isalnum(ch); } +static int isspace_wrapper(int ch) { return isspace(ch); } +static int ispunct_wrapper(int ch) { return ispunct(ch); } +static int isprint_wrapper(int ch) { return isprint(ch); } +static int isgraph_wrapper(int ch) { return isgraph(ch); } +static int iscntrl_wrapper(int ch) { return iscntrl(ch); } +static int isblank_wrapper(int ch) { return isblank(ch); } +static int toupper_wrapper(int ch) { return toupper(ch); } +static int tolower_wrapper(int ch) { return tolower(ch); } + +jmp_buf env; + +static void +handle_signal(int signo) +{ + + longjmp(env, 1); +} + +static void +test_abuse(const char *name, int (*ctypefn)(int)) +{ + volatile int ch; /* for longjmp */ + + for (ch = CHAR_MIN; ch < 0; ch++) { + volatile int result; + + if (ch == EOF) + continue; + ATF_REQUIRE_MSG(ch != (int)(unsigned char)ch, "ch=%d", ch); + if (setjmp(env) == 0) { + REQUIRE_LIBC(signal(SIGABRT, &handle_signal), SIG_ERR); + REQUIRE_LIBC(signal(SIGSEGV, &handle_signal), SIG_ERR); + result = (*ctypefn)(ch); + REQUIRE_LIBC(signal(SIGABRT, SIG_DFL), SIG_ERR); + REQUIRE_LIBC(signal(SIGSEGV, SIG_DFL), SIG_ERR); + atf_tc_fail_nonfatal("%s failed to detect invalid %d," + " returned %d", + name, ch, result); + } else { + REQUIRE_LIBC(signal(SIGABRT, SIG_DFL), SIG_ERR); + REQUIRE_LIBC(signal(SIGSEGV, SIG_DFL), SIG_ERR); + } + } + + for (; ch <= CHAR_MAX; ch++) + ATF_REQUIRE_MSG(ch == (int)(unsigned char)ch, "ch=%d", ch); +} + +static void +test_abuse_in_locales(const char *name, int (*ctypefn)(int), bool macro) +{ + size_t i; + + for (i = 0; i < __arraycount(locales); i++) { + char buf[128]; + + if (!_CTYPE_GUARD_PAGE && macro && + strcmp(locales[i], "C") == 0) { + fprintf(stderr, "skip LC_CTYPE=C ctype(3) abuse --" + " no libc guard page on this platform\n"); + } + + ATF_REQUIRE_MSG(setlocale(LC_CTYPE, locales[i]) != NULL, + "locales[i]=%s", locales[i]); + snprintf(buf, sizeof(buf), "[%s]%s", locales[i], name); + test_abuse(buf, ctypefn); + } +} + +static void +test_use(const char *name, int (*ctypefn)(int)) +{ + volatile int ch; /* for longjmp */ + + for (ch = EOF; ch <= CHAR_MAX; ch = (ch == EOF ? 0 : ch + 1)) { + volatile int result; + + if (setjmp(env) == 0) { + REQUIRE_LIBC(signal(SIGABRT, &handle_signal), SIG_ERR); + REQUIRE_LIBC(signal(SIGSEGV, &handle_signal), SIG_ERR); + result = (*ctypefn)(ch); + REQUIRE_LIBC(signal(SIGABRT, SIG_DFL), SIG_ERR); + REQUIRE_LIBC(signal(SIGSEGV, SIG_DFL), SIG_ERR); + (void)result; + } else { + REQUIRE_LIBC(signal(SIGABRT, SIG_DFL), SIG_ERR); + REQUIRE_LIBC(signal(SIGSEGV, SIG_DFL), SIG_ERR); + atf_tc_fail_nonfatal("%s(%d) raised SIGSEGV", + name, ch); + } + } +} + +static void +test_isalpha_locale(const char *L, int (*ctypefn)(int)) +{ + + ATF_REQUIRE_MSG(setlocale(LC_CTYPE, L) != NULL, "L=%s", L); + test_use("isalpha", ctypefn); + ATF_CHECK(!(*ctypefn)(EOF)); +} + +static void +test_isupper_locale(const char *L, int (*ctypefn)(int)) +{ + + ATF_REQUIRE_MSG(setlocale(LC_CTYPE, L) != NULL, "L=%s", L); + test_use("isupper", ctypefn); + ATF_CHECK(!(*ctypefn)(EOF)); +} + +static void +test_islower_locale(const char *L, int (*ctypefn)(int)) +{ + + ATF_REQUIRE_MSG(setlocale(LC_CTYPE, L) != NULL, "L=%s", L); + test_use("islower", ctypefn); + ATF_CHECK(!(*ctypefn)(EOF)); +} + +static void +test_isdigit_locale(const char *L, int (*ctypefn)(int)) +{ + + ATF_REQUIRE_MSG(setlocale(LC_CTYPE, L) != NULL, "L=%s", L); + test_use("isdigit", ctypefn); + ATF_CHECK(!(*ctypefn)(EOF)); +} + +static void +test_isxdigit_locale(const char *L, int (*ctypefn)(int)) +{ + + ATF_REQUIRE_MSG(setlocale(LC_CTYPE, L) != NULL, "L=%s", L); + test_use("isxdigit", ctypefn); + ATF_CHECK(!(*ctypefn)(EOF)); +} + +static void +test_isalnum_locale(const char *L, int (*ctypefn)(int)) +{ + + ATF_REQUIRE_MSG(setlocale(LC_CTYPE, L) != NULL, "L=%s", L); + test_use("isalnum", ctypefn); + ATF_CHECK(!(*ctypefn)(EOF)); +} + +static void +test_isspace_locale(const char *L, int (*ctypefn)(int)) +{ + + ATF_REQUIRE_MSG(setlocale(LC_CTYPE, L) != NULL, "L=%s", L); + test_use("isspace", ctypefn); + ATF_CHECK(!(*ctypefn)(EOF)); +} + +static void +test_ispunct_locale(const char *L, int (*ctypefn)(int)) +{ + + ATF_REQUIRE_MSG(setlocale(LC_CTYPE, L) != NULL, "L=%s", L); + test_use("ispunct", ctypefn); + ATF_CHECK(!(*ctypefn)(EOF)); +} + +static void +test_isprint_locale(const char *L, int (*ctypefn)(int)) +{ + + ATF_REQUIRE_MSG(setlocale(LC_CTYPE, L) != NULL, "L=%s", L); + test_use("isprint", ctypefn); + ATF_CHECK(!(*ctypefn)(EOF)); +} + +static void +test_isgraph_locale(const char *L, int (*ctypefn)(int)) +{ + + ATF_REQUIRE_MSG(setlocale(LC_CTYPE, L) != NULL, "L=%s", L); + test_use("isgraph", ctypefn); + ATF_CHECK(!(*ctypefn)(EOF)); +} + +static void +test_iscntrl_locale(const char *L, int (*ctypefn)(int)) +{ + + ATF_REQUIRE_MSG(setlocale(LC_CTYPE, L) != NULL, "L=%s", L); + test_use("iscntrl", ctypefn); + ATF_CHECK(!(*ctypefn)(EOF)); +} + +static void +test_isblank_locale(const char *L, int (*ctypefn)(int)) +{ + + ATF_REQUIRE_MSG(setlocale(LC_CTYPE, L) != NULL, "L=%s", L); + test_use("isblank", ctypefn); + ATF_CHECK(!(*ctypefn)(EOF)); +} + +static void +test_toupper_locale(const char *L, int (*ctypefn)(int)) +{ + int result; + + ATF_REQUIRE_MSG(setlocale(LC_CTYPE, L) != NULL, "L=%s", L); + test_use("toupper", ctypefn); + ATF_CHECK_MSG((result = (*ctypefn)(EOF)) == EOF, + "result=%d, expected EOF=%d", result, EOF); +} + +static void +test_tolower_locale(const char *L, int (*ctypefn)(int)) +{ + int result; + + ATF_REQUIRE_MSG(setlocale(LC_CTYPE, L) != NULL, "L=%s", L); + test_use("tolower", ctypefn); + ATF_CHECK_MSG((result = (*ctypefn)(EOF)) == EOF, + "result=%d, expected EOF=%d", result, EOF); +} + +static void +test_isalpha_c(int (*ctypefn)(int)) +{ + int ch; + + ATF_CHECK(!(*ctypefn)(EOF)); + for (ch = 0; ch <= UCHAR_MAX; ch++) { + switch (ch) { + case 'a': case 'A': + case 'b': case 'B': + case 'c': case 'C': + case 'd': case 'D': + case 'e': case 'E': + case 'f': case 'F': + case 'g': case 'G': + case 'h': case 'H': + case 'i': case 'I': + case 'j': case 'J': + case 'k': case 'K': + case 'l': case 'L': + case 'm': case 'M': + case 'n': case 'N': + case 'o': case 'O': + case 'p': case 'P': + case 'q': case 'Q': + case 'r': case 'R': + case 's': case 'S': + case 't': case 'T': + case 'u': case 'U': + case 'v': case 'V': + case 'w': case 'W': + case 'x': case 'X': + case 'y': case 'Y': + case 'z': case 'Z': + ATF_CHECK_MSG((*ctypefn)(ch), "ch=0x%x", ch); + break; + default: + ATF_CHECK_MSG(!(*ctypefn)(ch), "ch=0x%x", ch); + break; + } + } +} + +static void +test_isupper_c(int (*ctypefn)(int)) +{ + int ch; + + ATF_CHECK(!(*ctypefn)(EOF)); + for (ch = 0; ch <= UCHAR_MAX; ch++) { + switch (ch) { + case 'A': + case 'B': + case 'C': + case 'D': + case 'E': + case 'F': + case 'G': + case 'H': + case 'I': + case 'J': + case 'K': + case 'L': + case 'M': + case 'N': + case 'O': + case 'P': + case 'Q': + case 'R': + case 'S': + case 'T': + case 'U': + case 'V': + case 'W': + case 'X': + case 'Y': + case 'Z': + ATF_CHECK_MSG((*ctypefn)(ch), "ch=0x%x", ch); + break; + default: + ATF_CHECK_MSG(!(*ctypefn)(ch), "ch=0x%x", ch); + break; + } + } +} + +static void +test_islower_c(int (*ctypefn)(int)) +{ + int ch; + + ATF_CHECK(!(*ctypefn)(EOF)); + for (ch = 0; ch <= UCHAR_MAX; ch++) { + switch (ch) { + case 'a': + case 'b': + case 'c': + case 'd': + case 'e': + case 'f': + case 'g': + case 'h': + case 'i': + case 'j': + case 'k': + case 'l': + case 'm': + case 'n': + case 'o': + case 'p': + case 'q': + case 'r': + case 's': + case 't': + case 'u': + case 'v': + case 'w': + case 'x': + case 'y': + case 'z': + ATF_CHECK_MSG((*ctypefn)(ch), "ch=0x%x", ch); + break; + default: + ATF_CHECK_MSG(!(*ctypefn)(ch), "ch=0x%x", ch); + break; + } + } +} + +static void +test_isdigit_c(int (*ctypefn)(int)) +{ + int ch; + + ATF_CHECK(!(*ctypefn)(EOF)); + for (ch = 0; ch <= UCHAR_MAX; ch++) { + switch (ch) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + ATF_CHECK_MSG((*ctypefn)(ch), "ch=0x%x", ch); + break; + default: + ATF_CHECK_MSG(!(*ctypefn)(ch), "ch=0x%x", ch); + break; + } + } +} + +static void +test_isxdigit_c(int (*ctypefn)(int)) +{ + int ch; + + ATF_CHECK(!(*ctypefn)(EOF)); + for (ch = 0; ch <= UCHAR_MAX; ch++) { + switch (ch) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case 'a': case 'A': + case 'b': case 'B': + case 'c': case 'C': + case 'd': case 'D': + case 'e': case 'E': + case 'f': case 'F': + ATF_CHECK_MSG((*ctypefn)(ch), "ch=0x%x", ch); + break; + default: + ATF_CHECK_MSG(!(*ctypefn)(ch), "ch=0x%x", ch); + break; + } + } +} + +static void +test_isalnum_c(int (*ctypefn)(int)) +{ + int ch; + + ATF_CHECK(!(*ctypefn)(EOF)); + for (ch = 0; ch <= UCHAR_MAX; ch++) { + switch (ch) { + case 'a': case 'A': + case 'b': case 'B': + case 'c': case 'C': + case 'd': case 'D': + case 'e': case 'E': + case 'f': case 'F': + case 'g': case 'G': + case 'h': case 'H': + case 'i': case 'I': + case 'j': case 'J': + case 'k': case 'K': + case 'l': case 'L': + case 'm': case 'M': + case 'n': case 'N': + case 'o': case 'O': + case 'p': case 'P': + case 'q': case 'Q': + case 'r': case 'R': + case 's': case 'S': + case 't': case 'T': + case 'u': case 'U': + case 'v': case 'V': + case 'w': case 'W': + case 'x': case 'X': + case 'y': case 'Y': + case 'z': case 'Z': + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + ATF_CHECK_MSG((*ctypefn)(ch), "ch=0x%x", ch); + break; + default: + ATF_CHECK_MSG(!(*ctypefn)(ch), "ch=0x%x", ch); + break; + } + } +} + +static void +test_isspace_c(int (*ctypefn)(int)) +{ + int ch; + + ATF_CHECK(!(*ctypefn)(EOF)); + for (ch = 0; ch <= UCHAR_MAX; ch++) { + switch (ch) { + case ' ': + case '\f': + case '\n': + case '\r': + case '\t': + case '\v': + ATF_CHECK_MSG((*ctypefn)(ch), "ch=0x%x", ch); + break; + default: + ATF_CHECK_MSG(!(*ctypefn)(ch), "ch=0x%x", ch); + break; + } + } +} + +static void +test_ispunct_c(int (*ctypefn)(int)) +{ + int ch; + + ATF_CHECK(!(*ctypefn)(EOF)); + for (ch = 0; ch <= UCHAR_MAX; ch++) { + switch (ch) { + default: + ATF_CHECK_MSG((*ctypefn)(ch), "ch=0x%x", ch); + break; + case 0 ... 0x1f: + case 0x20: /* space is printing but not punctuation */ + case 0x7f: + case 0x80 ... 0xff: + ATF_CHECK_MSG(!(*ctypefn)(ch), "ch=0x%x", ch); + break; + case 'a': case 'A': + case 'b': case 'B': + case 'c': case 'C': + case 'd': case 'D': + case 'e': case 'E': + case 'f': case 'F': + case 'g': case 'G': + case 'h': case 'H': + case 'i': case 'I': + case 'j': case 'J': + case 'k': case 'K': + case 'l': case 'L': + case 'm': case 'M': + case 'n': case 'N': + case 'o': case 'O': + case 'p': case 'P': + case 'q': case 'Q': + case 'r': case 'R': + case 's': case 'S': + case 't': case 'T': + case 'u': case 'U': + case 'v': case 'V': + case 'w': case 'W': + case 'x': case 'X': + case 'y': case 'Y': + case 'z': case 'Z': + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + ATF_CHECK_MSG(!(*ctypefn)(ch), "ch=0x%x", ch); + break; + } + } +} + +static void +test_isprint_c(int (*ctypefn)(int)) +{ + int ch; + + ATF_CHECK(!(*ctypefn)(EOF)); + for (ch = 0; ch <= UCHAR_MAX; ch++) { + switch (ch) { + case 0x20: /* space is printing but not graphic */ + default: + ATF_CHECK_MSG((*ctypefn)(ch), "ch=0x%x", ch); + break; + case 0 ... 0x1f: + case 0x7f: + case 0x80 ... 0xff: + ATF_CHECK_MSG(!(*ctypefn)(ch), "ch=0x%x", ch); + break; + } + } +} + +static void +test_isgraph_c(int (*ctypefn)(int)) +{ + int ch; + + ATF_CHECK(!(*ctypefn)(EOF)); + for (ch = 0; ch <= UCHAR_MAX; ch++) { + switch (ch) { + default: + ATF_CHECK_MSG((*ctypefn)(ch), "ch=0x%x", ch); + break; + case 0 ... 0x1f: + case 0x20: /* space is printing but not graphic */ + case 0x7f: + case 0x80 ... 0xff: + ATF_CHECK_MSG(!(*ctypefn)(ch), "ch=0x%x", ch); + break; + } + } +} + +static void +test_iscntrl_c(int (*ctypefn)(int)) +{ + int ch; + + ATF_CHECK(!(*ctypefn)(EOF)); + for (ch = 0; ch <= UCHAR_MAX; ch++) { + switch (ch) { + case 0 ... 0x1f: + case 0x7f: + ATF_CHECK_MSG((*ctypefn)(ch), "ch=0x%x", ch); + break; + default: + ATF_CHECK_MSG(!(*ctypefn)(ch), "ch=0x%x", ch); + break; + } + } +} + +static void +test_isblank_c(int (*ctypefn)(int)) +{ + int ch; + + ATF_CHECK(!(*ctypefn)(EOF)); + for (ch = 0; ch <= UCHAR_MAX; ch++) { + switch (ch) { + case ' ': + case '\t': + ATF_CHECK_MSG((*ctypefn)(ch), "ch=0x%x", ch); + break; + default: + ATF_CHECK_MSG(!(*ctypefn)(ch), "ch=0x%x", ch); + break; + } + } +} + +static void +test_toupper_c(int (*ctypefn)(int)) +{ + int ch, result, expected; + + ATF_CHECK_MSG((result = (*ctypefn)(EOF)) == EOF, + "result=%d, expected EOF=%d", result, EOF); + for (ch = 0; ch <= UCHAR_MAX; ch++) { + switch (ch) { + case 'a': case 'A': expected = 'A'; break; + case 'b': case 'B': expected = 'B'; break; + case 'c': case 'C': expected = 'C'; break; + case 'd': case 'D': expected = 'D'; break; + case 'e': case 'E': expected = 'E'; break; + case 'f': case 'F': expected = 'F'; break; + case 'g': case 'G': expected = 'G'; break; + case 'h': case 'H': expected = 'H'; break; + case 'i': case 'I': expected = 'I'; break; + case 'j': case 'J': expected = 'J'; break; + case 'k': case 'K': expected = 'K'; break; + case 'l': case 'L': expected = 'L'; break; + case 'm': case 'M': expected = 'M'; break; + case 'n': case 'N': expected = 'N'; break; + case 'o': case 'O': expected = 'O'; break; + case 'p': case 'P': expected = 'P'; break; + case 'q': case 'Q': expected = 'Q'; break; + case 'r': case 'R': expected = 'R'; break; + case 's': case 'S': expected = 'S'; break; + case 't': case 'T': expected = 'T'; break; + case 'u': case 'U': expected = 'U'; break; + case 'v': case 'V': expected = 'V'; break; + case 'w': case 'W': expected = 'W'; break; + case 'x': case 'X': expected = 'X'; break; + case 'y': case 'Y': expected = 'Y'; break; + case 'z': case 'Z': expected = 'Z'; break; + default: + expected = ch; + break; + } + ATF_CHECK_MSG((result = (*ctypefn)(ch)) == expected, + "result=%d expected=%d", result, expected); + } +} + +static void +test_tolower_c(int (*ctypefn)(int)) +{ + int ch, result, expected; + + ATF_CHECK_MSG((result = (*ctypefn)(EOF)) == EOF, + "result=%d, expected EOF=%d", result, EOF); + for (ch = 0; ch <= UCHAR_MAX; ch++) { + switch (ch) { + case 'a': case 'A': expected = 'a'; break; + case 'b': case 'B': expected = 'b'; break; + case 'c': case 'C': expected = 'c'; break; + case 'd': case 'D': expected = 'd'; break; + case 'e': case 'E': expected = 'e'; break; + case 'f': case 'F': expected = 'f'; break; + case 'g': case 'G': expected = 'g'; break; + case 'h': case 'H': expected = 'h'; break; + case 'i': case 'I': expected = 'i'; break; + case 'j': case 'J': expected = 'j'; break; + case 'k': case 'K': expected = 'k'; break; + case 'l': case 'L': expected = 'l'; break; + case 'm': case 'M': expected = 'm'; break; + case 'n': case 'N': expected = 'n'; break; + case 'o': case 'O': expected = 'o'; break; + case 'p': case 'P': expected = 'p'; break; + case 'q': case 'Q': expected = 'q'; break; + case 'r': case 'R': expected = 'r'; break; + case 's': case 'S': expected = 's'; break; + case 't': case 'T': expected = 't'; break; + case 'u': case 'U': expected = 'u'; break; + case 'v': case 'V': expected = 'v'; break; + case 'w': case 'W': expected = 'w'; break; + case 'x': case 'X': expected = 'x'; break; + case 'y': case 'Y': expected = 'y'; break; + case 'z': case 'Z': expected = 'z'; break; + default: + expected = ch; + break; + } + ATF_CHECK_MSG((result = (*ctypefn)(ch)) == expected, + "result=%d expected=%d", result, expected); + } +} + +extern char **environ; + +static void +test_abuse_override(const struct atf_tc *tc, const char *fn, const char *mode, + const char *locale) +{ + char h_ctype_abuse[PATH_MAX]; + pid_t pid; + int status; + + RL(snprintf(h_ctype_abuse, sizeof(h_ctype_abuse), "%s/h_ctype_abuse", + atf_tc_get_config_var(tc, "srcdir"))); + + RL(setenv("LIBC_ALLOWCTYPEABUSE", "", 1)); + + RL(pid = vfork()); + if (pid == 0) { /* child */ + char *const argv[] = { + h_ctype_abuse, + __UNCONST(fn), + __UNCONST(mode), + __UNCONST(locale), + NULL, + }; + + if (execve(argv[0], argv, environ) == -1) + _exit(1); + _exit(2); + } + + RL(waitpid(pid, &status, 0)); + if (WIFSIGNALED(status)) { + atf_tc_fail_nonfatal("child exited on signal %d (%s)", + WTERMSIG(status), strsignal(WTERMSIG(status))); + } else if (!WIFEXITED(status)) { + atf_tc_fail_nonfatal("child exited status=0x%x", status); + } else { + ATF_CHECK_MSG(WEXITSTATUS(status) == 0, + "child exited with code %d", + WEXITSTATUS(status)); + } +} + +static void +test_abuse_override_in_locales(const struct atf_tc *tc, const char *fn, + const char *mode) +{ + size_t i; + + for (i = 0; i < __arraycount(locales); i++) { + fprintf(stderr, "# locale %s\n", locales[i]); + test_abuse_override(tc, fn, mode, locales[i]); + } +} + +#define ADD_TEST_ABUSE(TP, FN) do \ +{ \ + ATF_TP_ADD_TC(TP, abuse_##FN##_macro_c); \ + ATF_TP_ADD_TC(TP, abuse_##FN##_function_c); \ + ATF_TP_ADD_TC(TP, abuse_##FN##_macro_locale); \ + ATF_TP_ADD_TC(TP, abuse_##FN##_function_locale); \ +} while (0) + +#define DEF_TEST_ABUSE(FN) \ +ATF_TC(abuse_##FN##_macro_c); \ +ATF_TC_HEAD(abuse_##FN##_macro_c, tc) \ +{ \ + atf_tc_set_md_var(tc, "descr", \ + "Test abusing "#FN" macro with default LC_CTYPE=C"); \ +} \ +ATF_TC_BODY(abuse_##FN##_macro_c, tc) \ +{ \ + if (CHAR_UNSIGNED) { \ + atf_tc_skip("runtime ctype(3) abuse is impossible with" \ + " unsigned char"); \ + } \ + if (!_CTYPE_GUARD_PAGE) \ + atf_tc_skip("no LC_CTYPE=C guard page on this platform"); \ + test_abuse(#FN, &FN##_wrapper); \ +} \ +ATF_TC(abuse_##FN##_function_c); \ +ATF_TC_HEAD(abuse_##FN##_function_c, tc) \ +{ \ + atf_tc_set_md_var(tc, "descr", \ + "Test abusing "#FN" function with default LC_CTYPE=C"); \ +} \ +ATF_TC_BODY(abuse_##FN##_function_c, tc) \ +{ \ + if (CHAR_UNSIGNED) { \ + atf_tc_skip("runtime ctype(3) abuse is impossible with" \ + " unsigned char"); \ + } \ + if (!_CTYPE_GUARD_PAGE) \ + atf_tc_skip("no LC_CTYPE=C guard page on this platform"); \ + test_abuse(#FN, &FN); \ +} \ +ATF_TC(abuse_##FN##_macro_locale); \ +ATF_TC_HEAD(abuse_##FN##_macro_locale, tc) \ +{ \ + atf_tc_set_md_var(tc, "descr", \ + "Test abusing "#FN" macro with locales"); \ +} \ +ATF_TC_BODY(abuse_##FN##_macro_locale, tc) \ +{ \ + if (CHAR_UNSIGNED) { \ + atf_tc_skip("runtime ctype(3) abuse is impossible with" \ + " unsigned char"); \ + } \ + test_abuse_in_locales(#FN, &FN##_wrapper, /*macro*/true); \ +} \ +ATF_TC(abuse_##FN##_function_locale); \ +ATF_TC_HEAD(abuse_##FN##_function_locale, tc) \ +{ \ + atf_tc_set_md_var(tc, "descr", \ + "Test abusing "#FN" function with locales"); \ +} \ +ATF_TC_BODY(abuse_##FN##_function_locale, tc) \ +{ \ + if (CHAR_UNSIGNED) { \ + atf_tc_skip("runtime ctype(3) abuse is impossible with" \ + " unsigned char"); \ + } \ + test_abuse_in_locales(#FN, &FN, /*macro*/false); \ +} + +#define ADD_TEST_ABUSE_OVERRIDE(TP, FN) do \ +{ \ + ATF_TP_ADD_TC(TP, abuse_override_##FN##_macro_c); \ + ATF_TP_ADD_TC(TP, abuse_override_##FN##_function_c); \ + ATF_TP_ADD_TC(TP, abuse_override_##FN##_macro_locale); \ + ATF_TP_ADD_TC(TP, abuse_override_##FN##_function_locale); \ +} while (0) + +#define DEF_TEST_ABUSE_OVERRIDE(FN) \ +ATF_TC(abuse_override_##FN##_macro_c); \ +ATF_TC_HEAD(abuse_override_##FN##_macro_c, tc) \ +{ \ + atf_tc_set_md_var(tc, "descr", \ + "Test allowing abuse of "#FN" macro with default LC_CTYPE=C"); \ +} \ +ATF_TC_BODY(abuse_override_##FN##_macro_c, tc) \ +{ \ + test_abuse_override(tc, #FN, "macro", NULL); \ +} \ +ATF_TC(abuse_override_##FN##_function_c); \ +ATF_TC_HEAD(abuse_override_##FN##_function_c, tc) \ +{ \ + atf_tc_set_md_var(tc, "descr", \ + "Test allowing abuse "#FN" function with default LC_CTYPE=C"); \ +} \ +ATF_TC_BODY(abuse_override_##FN##_function_c, tc) \ +{ \ + test_abuse_override(tc, #FN, "function", NULL); \ +} \ +ATF_TC(abuse_override_##FN##_macro_locale); \ +ATF_TC_HEAD(abuse_override_##FN##_macro_locale, tc) \ +{ \ + atf_tc_set_md_var(tc, "descr", \ + "Test allowing abuse of "#FN" macro with locales"); \ +} \ +ATF_TC_BODY(abuse_override_##FN##_macro_locale, tc) \ +{ \ + test_abuse_override_in_locales(tc, #FN, "macro"); \ +} \ +ATF_TC(abuse_override_##FN##_function_locale); \ +ATF_TC_HEAD(abuse_override_##FN##_function_locale, tc) \ +{ \ + atf_tc_set_md_var(tc, "descr", \ + "Test allowing abuse of "#FN" function with locales"); \ +} \ +ATF_TC_BODY(abuse_override_##FN##_function_locale, tc) \ +{ \ + test_abuse_override_in_locales(tc, #FN, "function"); \ +} + +#define ADD_TEST_USE(TP, FN) do \ +{ \ + ATF_TP_ADD_TC(TP, FN##_macro_c); \ + ATF_TP_ADD_TC(TP, FN##_function_c); \ + ATF_TP_ADD_TC(TP, FN##_macro_locale); \ + ATF_TP_ADD_TC(TP, FN##_function_locale); \ +} while (0) + +#define DEF_TEST_USE(FN) \ +ATF_TC(FN##_macro_c); \ +ATF_TC_HEAD(FN##_macro_c, tc) \ +{ \ + atf_tc_set_md_var(tc, "descr", \ + "Test "#FN" macro with default LC_CTYPE=C"); \ +} \ +ATF_TC_BODY(FN##_macro_c, tc) \ +{ \ + test_##FN##_c(&FN##_wrapper); \ +} \ +ATF_TC(FN##_function_c); \ +ATF_TC_HEAD(FN##_function_c, tc) \ +{ \ + atf_tc_set_md_var(tc, "descr", \ + "Test "#FN" function with default LC_CTYPE=C"); \ +} \ +ATF_TC_BODY(FN##_function_c, tc) \ +{ \ + test_##FN##_c(&FN); \ +} \ +ATF_TC(FN##_macro_locale); \ +ATF_TC_HEAD(FN##_macro_locale, tc) \ +{ \ + atf_tc_set_md_var(tc, "descr", \ + "Test "#FN" macro with locales"); \ +} \ +ATF_TC_BODY(FN##_macro_locale, tc) \ +{ \ + size_t i; \ + \ + for (i = 0; i < __arraycount(locales); i++) \ + test_##FN##_locale(locales[i], &FN##_wrapper); \ +} \ +ATF_TC(FN##_function_locale); \ +ATF_TC_HEAD(FN##_function_locale, tc) \ +{ \ + atf_tc_set_md_var(tc, "descr", \ + "Test "#FN" function with locales"); \ +} \ +ATF_TC_BODY(FN##_function_locale, tc) \ +{ \ + size_t i; \ + \ + for (i = 0; i < __arraycount(locales); i++) \ + test_##FN##_locale(locales[i], &FN); \ +} + +DEF_TEST_ABUSE(isalpha) +DEF_TEST_ABUSE(isupper) +DEF_TEST_ABUSE(islower) +DEF_TEST_ABUSE(isdigit) +DEF_TEST_ABUSE(isxdigit) +DEF_TEST_ABUSE(isalnum) +DEF_TEST_ABUSE(isspace) +DEF_TEST_ABUSE(ispunct) +DEF_TEST_ABUSE(isprint) +DEF_TEST_ABUSE(isgraph) +DEF_TEST_ABUSE(iscntrl) +DEF_TEST_ABUSE(isblank) +DEF_TEST_ABUSE(toupper) +DEF_TEST_ABUSE(tolower) + +DEF_TEST_ABUSE_OVERRIDE(isalpha) +DEF_TEST_ABUSE_OVERRIDE(isupper) +DEF_TEST_ABUSE_OVERRIDE(islower) +DEF_TEST_ABUSE_OVERRIDE(isdigit) +DEF_TEST_ABUSE_OVERRIDE(isxdigit) +DEF_TEST_ABUSE_OVERRIDE(isalnum) +DEF_TEST_ABUSE_OVERRIDE(isspace) +DEF_TEST_ABUSE_OVERRIDE(ispunct) +DEF_TEST_ABUSE_OVERRIDE(isprint) +DEF_TEST_ABUSE_OVERRIDE(isgraph) +DEF_TEST_ABUSE_OVERRIDE(iscntrl) +DEF_TEST_ABUSE_OVERRIDE(isblank) +DEF_TEST_ABUSE_OVERRIDE(toupper) +DEF_TEST_ABUSE_OVERRIDE(tolower) + +DEF_TEST_USE(isalpha) +DEF_TEST_USE(isupper) +DEF_TEST_USE(islower) +DEF_TEST_USE(isdigit) +DEF_TEST_USE(isxdigit) +DEF_TEST_USE(isalnum) +DEF_TEST_USE(isspace) +DEF_TEST_USE(ispunct) +DEF_TEST_USE(isprint) +DEF_TEST_USE(isgraph) +DEF_TEST_USE(iscntrl) +DEF_TEST_USE(isblank) +DEF_TEST_USE(toupper) +DEF_TEST_USE(tolower) + +ATF_TC(eof_confusion_iso8859_1); +ATF_TC_HEAD(eof_confusion_iso8859_1, tc) +{ + atf_tc_set_md_var(tc, "descr", + "Test potential confusion with EOF in ISO-8859-1"); +} +ATF_TC_BODY(eof_confusion_iso8859_1, tc) +{ + int ydots = 0xff; /* ÿ, LATIN SMALL LETTER Y WITH DIAERESIS */ + int ch; + + /* + * The LATIN SMALL LETTER Y WITH DIAERESIS code point 0xff in + * ISO-8859-1 is curious primarily because its bit pattern + * coincides with an 8-bit signed -1, which is to say, EOF as + * an 8-bit quantity; of course, for EOF, all of the is* + * functions are supposed to return false (as we test above). + * It also has the curious property that it lacks any + * corresponding uppercase code point in ISO-8859-1, so we + * can't distinguish it from EOF by tolower/toupper. + */ + ATF_REQUIRE(setlocale(LC_CTYPE, "fr_FR.ISO8859-1") != NULL); + ATF_CHECK(isalpha(ydots)); + ATF_CHECK(!isupper(ydots)); + ATF_CHECK(islower(ydots)); + ATF_CHECK(!isdigit(ydots)); + ATF_CHECK(!isxdigit(ydots)); + ATF_CHECK(isalnum(ydots)); + ATF_CHECK(!isspace(ydots)); + ATF_CHECK(!ispunct(ydots)); + ATF_CHECK(isprint(ydots)); + ATF_CHECK(isgraph(ydots)); + ATF_CHECK(!iscntrl(ydots)); + ATF_CHECK(!isblank(ydots)); + ATF_CHECK_MSG((ch = toupper(ydots)) == ydots, "ch=0x%x", ch); + ATF_CHECK_MSG((ch = tolower(ydots)) == ydots, "ch=0x%x", ch); +} + +ATF_TC(eof_confusion_koi8_u); +ATF_TC_HEAD(eof_confusion_koi8_u, tc) +{ + atf_tc_set_md_var(tc, "descr", + "Test potential confusion with EOF in KOI8-U"); +} +ATF_TC_BODY(eof_confusion_koi8_u, tc) +{ + int Hard = 0xff; /* Ъ, CYRILLIC CAPITAL LETTER HARD SIGN */ + int hard = 0xdf; /* ъ, CYRILLIC SMALL LETTER HARD SIGN */ + int ch; + + /* + * The CYRILLIC CAPITAL LETTER HARD SIGN code point 0xff in + * KOI8-U (and KOI8-R) also coincides with the bit pattern of + * an 8-bit signed -1. Unlike LATIN SMALL LETTER Y WITH + * DIAERESIS, it has a lowercase equivalent in KOI8-U. + */ + ATF_REQUIRE(setlocale(LC_CTYPE, "uk_UA.KOI8-U") != NULL); + ATF_CHECK(isalpha(Hard)); + ATF_CHECK(isupper(Hard)); + ATF_CHECK(!islower(Hard)); + ATF_CHECK(!isdigit(Hard)); + ATF_CHECK(!isxdigit(Hard)); + ATF_CHECK(isalnum(Hard)); + ATF_CHECK(!isspace(Hard)); + ATF_CHECK(!ispunct(Hard)); + ATF_CHECK(isprint(Hard)); + ATF_CHECK(isgraph(Hard)); + ATF_CHECK(!iscntrl(Hard)); + ATF_CHECK(!isblank(Hard)); + ATF_CHECK_MSG((ch = toupper(Hard)) == Hard, "ch=0x%x", ch); + ATF_CHECK_MSG((ch = tolower(Hard)) == hard, "ch=0x%x", ch); +} + +ATF_TC(eof_confusion_pt154); +ATF_TC_HEAD(eof_confusion_pt154, tc) +{ + atf_tc_set_md_var(tc, "descr", + "Test potential confusion with EOF in PT154"); +} +ATF_TC_BODY(eof_confusion_pt154, tc) +{ + int ya = 0xff; /* я, CYRILLIC SMALL LETTER YA */ + int Ya = 0xdf; /* Я, CYRILLIC CAPITAL LETTER YA */ + int ch; + + /* + * The CYRILLIC SMALL LETTER YA code point 0xff in PT154 also + * coincides with the bit pattern of an 8-bit signed -1, and is + * lowercase with a corresponding uppercase code point in + * PT154. + */ + ATF_REQUIRE(setlocale(LC_CTYPE, "kk_KZ.PT154") != NULL); + ATF_CHECK(isalpha(ya)); + ATF_CHECK(!isupper(ya)); + ATF_CHECK(islower(ya)); + ATF_CHECK(!isdigit(ya)); + ATF_CHECK(!isxdigit(ya)); + ATF_CHECK(isalnum(ya)); + ATF_CHECK(!isspace(ya)); + ATF_CHECK(!ispunct(ya)); + ATF_CHECK(isprint(ya)); + ATF_CHECK(isgraph(ya)); + ATF_CHECK(!iscntrl(ya)); + ATF_CHECK(!isblank(ya)); + ATF_CHECK_MSG((ch = toupper(ya)) == Ya, "ch=0x%x", ch); + ATF_CHECK_MSG((ch = tolower(ya)) == ya, "ch=0x%x", ch); +} + +ATF_TP_ADD_TCS(tp) +{ + + ADD_TEST_ABUSE(tp, isalpha); + ADD_TEST_ABUSE(tp, isupper); + ADD_TEST_ABUSE(tp, islower); + ADD_TEST_ABUSE(tp, isdigit); + ADD_TEST_ABUSE(tp, isxdigit); + ADD_TEST_ABUSE(tp, isalnum); + ADD_TEST_ABUSE(tp, isspace); + ADD_TEST_ABUSE(tp, ispunct); + ADD_TEST_ABUSE(tp, isprint); + ADD_TEST_ABUSE(tp, isgraph); + ADD_TEST_ABUSE(tp, iscntrl); + ADD_TEST_ABUSE(tp, isblank); + ADD_TEST_ABUSE(tp, toupper); + ADD_TEST_ABUSE(tp, tolower); + + ADD_TEST_ABUSE_OVERRIDE(tp, isalpha); + ADD_TEST_ABUSE_OVERRIDE(tp, isupper); + ADD_TEST_ABUSE_OVERRIDE(tp, islower); + ADD_TEST_ABUSE_OVERRIDE(tp, isdigit); + ADD_TEST_ABUSE_OVERRIDE(tp, isxdigit); + ADD_TEST_ABUSE_OVERRIDE(tp, isalnum); + ADD_TEST_ABUSE_OVERRIDE(tp, isspace); + ADD_TEST_ABUSE_OVERRIDE(tp, ispunct); + ADD_TEST_ABUSE_OVERRIDE(tp, isprint); + ADD_TEST_ABUSE_OVERRIDE(tp, isgraph); + ADD_TEST_ABUSE_OVERRIDE(tp, iscntrl); + ADD_TEST_ABUSE_OVERRIDE(tp, isblank); + ADD_TEST_ABUSE_OVERRIDE(tp, toupper); + ADD_TEST_ABUSE_OVERRIDE(tp, tolower); + + ADD_TEST_USE(tp, isalpha); + ADD_TEST_USE(tp, isupper); + ADD_TEST_USE(tp, islower); + ADD_TEST_USE(tp, isdigit); + ADD_TEST_USE(tp, isxdigit); + ADD_TEST_USE(tp, isalnum); + ADD_TEST_USE(tp, isspace); + ADD_TEST_USE(tp, ispunct); + ADD_TEST_USE(tp, isprint); + ADD_TEST_USE(tp, isgraph); + ADD_TEST_USE(tp, iscntrl); + ADD_TEST_USE(tp, isblank); + ADD_TEST_USE(tp, toupper); + ADD_TEST_USE(tp, tolower); + + ATF_TP_ADD_TC(tp, eof_confusion_iso8859_1); + ATF_TP_ADD_TC(tp, eof_confusion_koi8_u); + ATF_TP_ADD_TC(tp, eof_confusion_pt154); + + return atf_no_error(); +} diff --git a/lib/libc/gen/t_timespec_get.c b/lib/libc/gen/t_timespec_get.c new file mode 100644 index 000000000000..b51625311032 --- /dev/null +++ b/lib/libc/gen/t_timespec_get.c @@ -0,0 +1,123 @@ +/*- + * Copyright (c) 2025 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Nia Alarie. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include <atf-c.h> + +#include <limits.h> +#include <time.h> + +ATF_TC(timespec_getres); +ATF_TC_HEAD(timespec_getres, tc) +{ + atf_tc_set_md_var(tc, "descr", "Resolution tests for timespec_getres"); +} + +ATF_TC_BODY(timespec_getres, tc) +{ + struct timespec ts, ts2; + + ATF_REQUIRE_EQ(timespec_getres(&ts, TIME_MONOTONIC), TIME_MONOTONIC); + ATF_REQUIRE_EQ(clock_getres(CLOCK_MONOTONIC, &ts2), 0); + + ATF_REQUIRE_EQ(ts.tv_sec, ts2.tv_sec); + ATF_REQUIRE_EQ(ts.tv_nsec, ts2.tv_nsec); + + ATF_REQUIRE_EQ(timespec_getres(&ts, TIME_UTC), TIME_UTC); + ATF_REQUIRE_EQ(clock_getres(CLOCK_REALTIME, &ts2), 0); + + ATF_REQUIRE_EQ(ts.tv_sec, ts2.tv_sec); + ATF_REQUIRE_EQ(ts.tv_nsec, ts2.tv_nsec); + + /* now an invalid value */ + ATF_REQUIRE_EQ(timespec_getres(&ts, INT_MAX), 0); +} + +ATF_TC(timespec_get); +ATF_TC_HEAD(timespec_get, tc) +{ + atf_tc_set_md_var(tc, "descr", "Basic tests for timespec_get"); +} + +ATF_TC_BODY(timespec_get, tc) +{ + struct timespec ts, ts2; + + ATF_REQUIRE_EQ(timespec_get(&ts, TIME_UTC), TIME_UTC); + ATF_REQUIRE_EQ(clock_gettime(CLOCK_REALTIME, &ts2), 0); + + /* + * basically test that these clocks (which should be the same source) + * aren't too wildly apart... + */ + + if (ts2.tv_sec >= ts.tv_sec) { + ATF_REQUIRE((ts2.tv_sec - ts.tv_sec) < 86400); + } else { + ATF_REQUIRE((ts.tv_sec - ts2.tv_sec) < 86400); + } + + /* now an invalid value */ + ATF_REQUIRE_EQ(timespec_get(&ts, INT_MAX), 0); +} + +ATF_TC(timespec_get_monotonic); +ATF_TC_HEAD(timespec_get_monotonic, tc) +{ + atf_tc_set_md_var(tc, "descr", "Monotonic tests for timespec_getres"); +} + +ATF_TC_BODY(timespec_get_monotonic, tc) +{ + struct timespec ts, ts2; + + ATF_REQUIRE_EQ(timespec_get(&ts, TIME_MONOTONIC), TIME_MONOTONIC); + ATF_REQUIRE_EQ(clock_gettime(CLOCK_MONOTONIC, &ts2), 0); + + /* + * basically test that these clocks (which should be the same source) + * aren't too wildly apart... + */ + + ATF_REQUIRE(ts2.tv_sec >= ts.tv_sec); + ATF_REQUIRE((ts2.tv_sec - ts.tv_sec) < 86400); + + /* test that it's actually monotonic */ + ATF_REQUIRE_EQ(timespec_get(&ts2, TIME_MONOTONIC), TIME_MONOTONIC); + ATF_REQUIRE(ts2.tv_sec >= ts.tv_sec); +} + +ATF_TP_ADD_TCS(tp) +{ + ATF_TP_ADD_TC(tp, timespec_getres); + ATF_TP_ADD_TC(tp, timespec_get); + ATF_TP_ADD_TC(tp, timespec_get_monotonic); + + return atf_no_error(); +} + |
