The branch main has been updated by kevans:

URL: 
https://cgit.FreeBSD.org/src/commit/?id=3de9dc5bf4b438e0d98222dc028982380d5a25d2

commit 3de9dc5bf4b438e0d98222dc028982380d5a25d2
Author:     Kyle Evans <[email protected]>
AuthorDate: 2026-06-30 13:12:51 +0000
Commit:     Kyle Evans <[email protected]>
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 <[email protected]>
+ *
+ * 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());
 }

Reply via email to