diff mbox series

[v2,11/13] posix: Implement environ snapshotting in __spawni

Message ID b5927afe9408f9118035c45202b83e3a70e47f4a.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
This keeps stack usage bounded if environ is used with the
functions in the posix_spawn family.
---
 posix/spawni.c                   | 94 +++++++++++++++++++++++++++++++-
 stdlib/Makefile                  |  3 +
 stdlib/tst-environ-posix_spawn.c | 31 +++++++++++
 3 files changed, 127 insertions(+), 1 deletion(-)
 create mode 100644 stdlib/tst-environ-posix_spawn.c
diff mbox series

Patch

diff --git a/posix/spawni.c b/posix/spawni.c
index ef47dd9ea4..02e3ad0c8d 100644
--- a/posix/spawni.c
+++ b/posix/spawni.c
@@ -20,6 +20,29 @@ 
 #include <spawn.h>
 #include "spawn_int.h"
 #include <arch-spawni.h>
+#include <stdlib/setenv.h>
+#include <sys/mman.h>
+#include <unistd.h>
+
+/* Used to keep track of the memory mapping for the environment
+   snapshot.  */
+struct spawni_environ_snapshot
+{
+  char **data;
+  size_t size;			/* In bytes.  */
+};
+
+static void
+__spawni_environ_snapshot_unmap (void *arg)
+{
+  struct spawni_environ_snapshot *ses = arg;
+  if (ses->data != NULL)
+    {
+      int save_errno = errno;
+      __munmap (ses->data, ses->size);
+      __set_errno (save_errno);
+    }
+}
 
 /* Spawn a new process executing PATH with the attributes describes in *ATTRP.
    Before running the process perform the actions described in FILE-ACTIONS. */
@@ -29,5 +52,74 @@  __spawni (pid_t *pid, const char *file,
 	  const posix_spawnattr_t *attrp, char *const argv[],
 	  char *const envp[], int xflags)
 {
-  return __arch_spawni (pid, file, file_actions, attrp, argv, envp, xflags);
+  if (envp == NULL || envp != __environ
+      || !__environ_is_from_array_list ((char **) envp)
+      || __environ_single_threaded_no_snapshot (envp))
+    return __arch_spawni (pid, file, file_actions, attrp, argv, envp, xflags);
+
+  /* Perform the environment snapshot.  It is beneficial to do this
+     here, and not in execveat, because it is possible to use an
+     mmap-backed array to avoid a huge stack allocation.  Memory
+     management is easier because the allocation happens before vfork
+     and can be undone completely before the function returns.  */
+
+  size_t env_size;
+  {
+    char *on_stack[ENVIRON_STACK_SNAPSHOT_SIZE];
+    env_size = __getenvarray (on_stack, ENVIRON_STACK_SNAPSHOT_SIZE);
+    if (env_size < ENVIRON_STACK_SNAPSHOT_SIZE)
+      return __arch_spawni (pid, file, file_actions, attrp, argv,
+			    on_stack, xflags);
+  }
+
+  int ret;
+
+  /* Use mmap for async-signal-safety.  Async-cancel-safety has
+     historically not been provided, so do not disable asynchronous
+     cancellation.  As a result, there are memory leaks.  */
+  struct spawni_environ_snapshot ses = { NULL, 0 };
+  __libc_cleanup_push (__spawni_environ_snapshot_unmap, &ses);
+
+  while (true)
+    {
+      /* Make room for the null terminator.  */
+      ++env_size;
+
+      /* Create the allocation.  */
+      {
+	size_t size = env_size * sizeof (char *);
+	void *ptr = __mmap (NULL, size,
+			    PROT_READ | PROT_WRITE,
+			    MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+	if (ptr == MAP_FAILED)
+	  {
+	    ret = -1;
+	    break;
+	  }
+	ses.data = ptr;
+	ses.size = size;
+      }
+
+      /* Try to get a snapshot.  */
+      size_t env_count = __getenvarray (ses.data, env_size);
+      if (env_count < env_size)
+	{
+	  ret = __arch_spawni (pid, file, file_actions, attrp, argv,
+			       ses.data, xflags);
+	  break;
+	}
+
+      /* The environment grew.  Unmap the allocation that turned out
+	 to be too small.  Make a copy to avoid spurious unmaps with
+	 asynchronous cancellation.  */
+      struct spawni_environ_snapshot copy = ses;
+      ses.data = NULL;
+      __spawni_environ_snapshot_unmap (&copy);
+
+      /* Try again with the updated size.  */
+      env_size = env_count;
+    }
+
+  __libc_cleanup_pop (1);
+  return ret;
 }
diff --git a/stdlib/Makefile b/stdlib/Makefile
index 498ec15e91..5934b683b1 100644
--- a/stdlib/Makefile
+++ b/stdlib/Makefile
@@ -379,6 +379,7 @@  tests-container := \
 # statically, or use harcoded paths.
 tests-with-reexec := \
   tst-environ-execl \
+  tst-environ-posix_spawn \
   # tests-with-reexec
 tests += $(tests-with-reexec)
 
@@ -644,6 +645,8 @@  $(objpfx)tst-getenv-unsetenv: $(shared-thread-library)
 # See above for $(tests-with-reexec).
 ifeq ($(build-hardcoded-path-in-tests),yes)
 $(objpfx)tst-environ-execl: $(shared-thread-library)
+$(objpfx)tst-environ-posix_spawn: $(shared-thread-library)
 else
 $(objpfx)tst-environ-execl: $(static-thread-library)
+$(objpfx)tst-environ-posix_spawn: $(static-thread-library)
 endif
diff --git a/stdlib/tst-environ-posix_spawn.c b/stdlib/tst-environ-posix_spawn.c
new file mode 100644
index 0000000000..4791e19f1f
--- /dev/null
+++ b/stdlib/tst-environ-posix_spawn.c
@@ -0,0 +1,31 @@ 
+/* Test environment snapshots with posix_spawn.
+   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/>.  */
+
+#define PROGRAM "tst-environ-posix_spawn"
+#include "tst-environ-snapshot-skeleton.c"
+
+#include <spawn.h>
+
+static pid_t
+create_process (void)
+{
+  char *argv[] = { self_path, (char *) "verify", NULL };
+  pid_t pid;
+  return posix_spawn (&pid, self_path, NULL, NULL, argv, environ);
+  return pid;
+}