@@ -319,6 +319,7 @@ tests := \
tst-sysconf-empty-chroot \
tst-truncate \
tst-truncate64 \
+ tst-vfork-mappings \
tst-vfork1 \
tst-vfork2 \
tst-wait3 \
@@ -635,6 +636,7 @@ $(objpfx)runptests.o: $(objpfx)ptestcases.h
$(objpfx)tst-getopt-cancel: $(shared-thread-library)
$(objpfx)tst-_Fork: $(shared-thread-library)
+$(objpfx)tst-vfork-mappings: $(shared-thread-library)
test-xfail-annexc = yes
$(objpfx)annexc.out: $(objpfx)annexc
new file mode 100644
@@ -0,0 +1,195 @@
+/* Test that vfork, *exec* does not accumulate mappings.
+ Copyright (C) 2024 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ <https://www.gnu.org/licenses/>. */
+
+#include <array_length.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <support/check.h>
+#include <support/support.h>
+#include <support/xthread.h>
+#include <support/xunistd.h>
+#include <sys/mman.h>
+#include <spawn.h>
+#include <unistd.h>
+
+/* 0 for direct calls, 1 for using an empheral thread. */
+static int use_thread;
+
+/* Wrapper used by test_one to invoke the callback on a new thread. */
+static void *
+thread_wrapper (void *action)
+{
+ ((void (*) (void)) action) ();
+ return NULL;
+}
+
+static void
+test_one (const char *label, void (*action) (void))
+{
+ printf ("info: testing %s%s\n",
+ label, use_thread ? " (threading mode)" : "");
+ size_t before = support_count_maps ();
+
+ /* Create mappings which should prevent merging of any persistant
+ new mappings, so that the count actually goes up in case of a
+ leak. */
+ void *gap_maps[50];
+
+ for (int i = 0; i < array_length (gap_maps); )
+ {
+ gap_maps[i++] = xmmap (NULL, 1, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1);
+ gap_maps[i++] = xmmap (NULL, 1, PROT_NONE,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1);
+ if (use_thread)
+ xpthread_join (xpthread_create (NULL, thread_wrapper, action));
+ else
+ action ();
+ }
+
+ for (int i = 0; i < array_length (gap_maps); ++i)
+ xmunmap (gap_maps[i], 1);
+
+ size_t afterwards = support_count_maps ();
+ if (afterwards > before + 1)
+ {
+ printf ("error: %s: additional maps created: %zu (%zu -> %zu)\n",
+ label, afterwards - before, before, afterwards);
+ support_record_failure ();
+ }
+}
+
+static void *
+dummy_thread (void *ignored)
+{
+ return NULL;
+}
+
+static void
+do_system (void)
+{
+ TEST_COMPARE (system ("exit"), 0);
+}
+
+static void
+do_vfork_execl (void)
+{
+ pid_t ret = vfork ();
+ if (ret == 0)
+ {
+ execl ("/bin/true", "true", NULL);
+ _exit (1);
+ }
+ if (ret < 0)
+ FAIL_EXIT1 ("vfork");
+ int status;
+ xwaitpid (ret, &status, 0);
+ TEST_COMPARE (status, 0);
+}
+
+static void
+do_vfork_execlp (void)
+{
+ pid_t ret = vfork ();
+ if (ret == 0)
+ {
+ execlp ("true", "true", NULL);
+ _exit (1);
+ }
+ if (ret < 0)
+ FAIL_EXIT1 ("vfork");
+ int status;
+ xwaitpid (ret, &status, 0);
+ TEST_COMPARE (status, 0);
+}
+
+static void
+do_posix_spawn (void)
+{
+ pid_t pid;
+ char *const argv[] = { NULL };
+ TEST_COMPARE (posix_spawn (&pid, "/bin/true", NULL, NULL, argv, environ), 0);
+ int status;
+ xwaitpid (pid, &status, 0);
+ TEST_COMPARE (status, 0);
+}
+
+static void
+do_posix_spawnp (void)
+{
+ pid_t pid;
+ char *const argv[] = { NULL };
+ TEST_COMPARE (posix_spawnp (&pid, "true", NULL, NULL, argv, environ), 0);
+ int status;
+ xwaitpid (pid, &status, 0);
+ TEST_COMPARE (status, 0);
+}
+
+static int
+do_test (void)
+{
+ xpthread_join (xpthread_create (NULL, dummy_thread, NULL));
+
+ for (int do_pass = 0; do_pass < 4; ++do_pass)
+ {
+ printf ("info: pass %d\n", do_pass);
+ switch (do_pass)
+ {
+ case 1:
+ /* Switch on multi-threaded mode. */
+ xpthread_join (xpthread_create (NULL, dummy_thread, NULL));
+ break;
+ case 2:
+ /* Stuff the environment with some values. Aim for
+ consuming at least one page of pointers. */
+ for (int i = 0; i <= 4096 / sizeof (char *) ; ++i)
+ {
+ char buf[20];
+ snprintf (buf, sizeof (buf), "V%d", i);
+ TEST_COMPARE (setenv (buf, buf + 1, 1), 0);
+ }
+ break;
+ case 3:
+ /* Stuff the environment with even more values. Aim for three
+ pages (for small page sizes). */
+ for (int i = 0; i <= 2 * 4096 / sizeof (char *); ++i)
+ {
+ char buf[20];
+ snprintf (buf, sizeof (buf), "U%d", i);
+ TEST_COMPARE (setenv (buf, buf + 1, 1), 0);
+ }
+ break;
+ }
+
+ for (use_thread = 0; use_thread < 2; ++use_thread)
+ {
+ if (do_pass >= 3)
+ /* With many environment variables, bash becomes rather
+ slow, so skip testing it. */
+ test_one ("system", do_system);
+ test_one ("vfork/execl", do_vfork_execl);
+ test_one ("vfork/execlp", do_vfork_execlp);
+ test_one ("posix_spawn", do_posix_spawn);
+ test_one ("posix_spawnp", do_posix_spawnp);
+ }
+ }
+
+ return 0;
+}
+
+#include <support/test-driver.c>