diff options
| author | Kyle Evans <kevans@FreeBSD.org> | 2026-06-30 13:12:51 +0000 |
|---|---|---|
| committer | Kyle Evans <kevans@FreeBSD.org> | 2026-06-30 13:12:51 +0000 |
| commit | 3de9dc5bf4b438e0d98222dc028982380d5a25d2 (patch) | |
| tree | cafde1ef5f63c3a292c240057c500e1ebc45f63e | |
| parent | f7f916d755fe5b48a2a68a937beea770b7bafa95 (diff) | |
libc: gen: add a test for rtld underflowing our posix_spawn thread
This is a distillation of the environment described in the PR, using
a dummy shlib and mapping it repeatedly. This takes advantage of the
guard page added in 2767a1f3686e5b16 to reliably crash if rtld tries to
scale its stack usage excessively with the # DSOs loaded.
PR: 295991
Reviewed by: kib
Differential Revision: https://reviews.freebsd.org/D57954
| -rw-r--r-- | lib/libc/tests/gen/Makefile | 2 | ||||
| -rw-r--r-- | lib/libc/tests/gen/libdummy/Makefile | 9 | ||||
| -rw-r--r-- | lib/libc/tests/gen/libdummy/libdummy.c | 14 | ||||
| -rw-r--r-- | lib/libc/tests/gen/posix_spawn_test.c | 68 |
4 files changed, 93 insertions, 0 deletions
diff --git a/lib/libc/tests/gen/Makefile b/lib/libc/tests/gen/Makefile index f5d57275cb44..e451fbea644c 100644 --- a/lib/libc/tests/gen/Makefile +++ b/lib/libc/tests/gen/Makefile @@ -1,5 +1,7 @@ .include <bsd.own.mk> +SUBDIR+= libdummy + ATF_TESTS_C+= arc4random_test ATF_TESTS_C+= dir2_test ATF_TESTS_C+= dlopen_empty_test diff --git a/lib/libc/tests/gen/libdummy/Makefile b/lib/libc/tests/gen/libdummy/Makefile new file mode 100644 index 000000000000..58a22f1a6c32 --- /dev/null +++ b/lib/libc/tests/gen/libdummy/Makefile @@ -0,0 +1,9 @@ +SHLIB?= dummy +SHLIB_MAJOR= 0 + +LIBDIR= ${TESTSBASE}/lib/libc/gen +SHLIBDIR= ${TESTSBASE}/lib/libc/gen + +SRCS= libdummy.c + +.include <bsd.lib.mk> diff --git a/lib/libc/tests/gen/libdummy/libdummy.c b/lib/libc/tests/gen/libdummy/libdummy.c new file mode 100644 index 000000000000..89e94c016311 --- /dev/null +++ b/lib/libc/tests/gen/libdummy/libdummy.c @@ -0,0 +1,14 @@ +/* + * + * Copyright (C) 2026 Kyle Evans <kevans@FreeBSD.org> + * + * SPDX-license-Identifier: BSD-2-Clause + */ + +int dummy_random(void); + +int +dummy_random(void) +{ + return (42); +} diff --git a/lib/libc/tests/gen/posix_spawn_test.c b/lib/libc/tests/gen/posix_spawn_test.c index 22133cf1d59a..9edd81b57d30 100644 --- a/lib/libc/tests/gen/posix_spawn_test.c +++ b/lib/libc/tests/gen/posix_spawn_test.c @@ -30,8 +30,10 @@ */ #include <sys/param.h> +#include <sys/mman.h> #include <sys/stat.h> #include <sys/wait.h> +#include <dlfcn.h> #include <errno.h> #include <fcntl.h> #include <stdio.h> @@ -173,6 +175,71 @@ ATF_TC_BODY(posix_spawnp_eacces, tc) } } +#define NUM_DSO 512 +ATF_TC_WITHOUT_HEAD(posix_spawnp_stackunderflow); +ATF_TC_BODY(posix_spawnp_stackunderflow, tc) +{ + struct stat sb; + char dsopath[MAXPATHLEN]; + char *myargs[] = { "true", NULL }; + void **handles; + char *dsomap; + size_t dsosz; + int error, fd, nfd, status; + pid_t pid, waitres; + + /* Make sure we have no child processes. */ + while (waitpid(-1, NULL, 0) != -1) + ; + ATF_REQUIRE_MSG(errno == ECHILD, "errno was not ECHILD: %d", errno); + + (void)snprintf(dsopath, sizeof(dsopath), "%s/libdummy.so", + atf_tc_get_config_var(tc, "srcdir")); + + fd = open(dsopath, O_RDONLY); + ATF_REQUIRE(fd >= 0); + + /* + * We'll open our original shlib and fdlopen() it repeatedly until we + * have a lot of DSOs open, then we'll trigger a posix_spawnp. This + * previously unearthed suboptimal stack usage in rtld that caused + * posix_spawnp()'s effectively-vforked environment to underflow its + * stack. + * + * We only get one shot to trigger the underflow, as rtld binding the + * symbols in the exec path in the rfork-child will affect the main + * process, so we only test that we don't have a problem with a large + * number of DSOs loaded. + */ + ATF_REQUIRE(fstat(fd, &sb) == 0); + dsosz = sb.st_size; + dsomap = mmap(NULL, dsosz, PROT_READ, MAP_SHARED, fd, 0); + ATF_REQUIRE(dsomap != MAP_FAILED); + + handles = calloc(sizeof(*handles), NUM_DSO); + ATF_REQUIRE(handles != NULL); + + for (int i = 0; i < NUM_DSO; i++) { + nfd = memfd_create("dsobase", MFD_CLOEXEC); + ATF_REQUIRE(nfd >= 0); + ATF_REQUIRE(ftruncate(nfd, dsosz) == 0); + ATF_REQUIRE(write(nfd, dsomap, dsosz) == dsosz); + + handles[i] = fdlopen(nfd, RTLD_LAZY); + ATF_REQUIRE(handles[i] != NULL); + if (i > 0) + ATF_REQUIRE(handles[i] != handles[i - 1]); + + close(nfd); + } + + error = posix_spawnp(&pid, myargs[0], NULL, NULL, myargs, myenv); + ATF_REQUIRE(error == 0); + waitres = waitpid(pid, &status, 0); + ATF_REQUIRE(waitres == pid); + ATF_REQUIRE(WIFEXITED(status) && WEXITSTATUS(status) == 0); +} + ATF_TP_ADD_TCS(tp) { @@ -181,6 +248,7 @@ ATF_TP_ADD_TCS(tp) ATF_TP_ADD_TC(tp, posix_spawnp_enoexec_fallback); ATF_TP_ADD_TC(tp, posix_spawnp_enoexec_fallback_null_argv0); ATF_TP_ADD_TC(tp, posix_spawnp_eacces); + ATF_TP_ADD_TC(tp, posix_spawnp_stackunderflow); return (atf_no_error()); } |
