aboutsummaryrefslogtreecommitdiff
path: root/lib/libc/gen
diff options
context:
space:
mode:
Diffstat (limited to 'lib/libc/gen')
-rw-r--r--lib/libc/gen/Makefile.inc1
-rw-r--r--lib/libc/gen/h_ctype_abuse.c138
-rw-r--r--lib/libc/gen/h_execsig.c55
-rw-r--r--lib/libc/gen/t_arc4random.c670
-rw-r--r--lib/libc/gen/t_ctype.c1236
-rw-r--r--lib/libc/gen/t_timespec_get.c123
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, &copy.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, &copy.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();
+}
+