Date: Tue, 30 Jun 2026 13:13:27 +0000 From: Kyle Evans <kevans@FreeBSD.org> To: src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-main@FreeBSD.org Subject: git: 3de9dc5bf4b4 - main - libc: gen: add a test for rtld underflowing our posix_spawn thread Message-ID: <6a43c0f7.23b9c.7c984d1c@gitrepo.freebsd.org>
index | next in thread | raw e-mail
The branch main has been updated by kevans: URL: https://cgit.FreeBSD.org/src/commit/?id=3de9dc5bf4b438e0d98222dc028982380d5a25d2 commit 3de9dc5bf4b438e0d98222dc028982380d5a25d2 Author: Kyle Evans <kevans@FreeBSD.org> AuthorDate: 2026-06-30 13:12:51 +0000 Commit: Kyle Evans <kevans@FreeBSD.org> CommitDate: 2026-06-30 13:12:51 +0000 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 --- lib/libc/tests/gen/Makefile | 2 + lib/libc/tests/gen/libdummy/Makefile | 9 +++++ lib/libc/tests/gen/libdummy/libdummy.c | 14 +++++++ lib/libc/tests/gen/posix_spawn_test.c | 68 ++++++++++++++++++++++++++++++++++ 4 files changed, 93 insertions(+) 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()); }home | help
Want to link to this message? Use this
URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?6a43c0f7.23b9c.7c984d1c>
