[PATCH v2 11/13] posix: Implement environ snapshotting in __spawni
Florian Weimer
fweimer@redhat.com
Sun Jul 28 19:03:21 GMT 2024
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 --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 (©);
+
+ /* 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;
+}
--
2.45.2
More information about the Libc-alpha
mailing list