diff mbox series

[v2,09/13] posix: New tst-vfork-mappings test case

Message ID 37178c0361a4e8d2be00daeef8241f0028488bfd.1722193092.git.fweimer@redhat.com
State New
Headers show
Series getenv/environ thread safety | expand

Commit Message

Florian Weimer July 28, 2024, 7:03 p.m. UTC
It checks if vfork followed by execve leaves additional memory mappings
behind.
---
 posix/Makefile             |   2 +
 posix/tst-vfork-mappings.c | 195 +++++++++++++++++++++++++++++++++++++
 2 files changed, 197 insertions(+)
 create mode 100644 posix/tst-vfork-mappings.c
diff mbox series

Patch

diff --git a/posix/Makefile b/posix/Makefile
index 2c598cd20a..ed3c9d0659 100644
--- a/posix/Makefile
+++ b/posix/Makefile
@@ -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
diff --git a/posix/tst-vfork-mappings.c b/posix/tst-vfork-mappings.c
new file mode 100644
index 0000000000..fcab423383
--- /dev/null
+++ b/posix/tst-vfork-mappings.c
@@ -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>