]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
process-util: add clone_with_nested_stack() helper
authorLennart Poettering <lennart@poettering.net>
Thu, 22 Jun 2023 08:27:17 +0000 (10:27 +0200)
committerLennart Poettering <lennart@poettering.net>
Fri, 23 Jun 2023 08:00:30 +0000 (10:00 +0200)
This wraps glibc's clone() but deals with the 'stack' parameter in a
sensible way. Only supports invocations without CLONE_VM, i.e. when
child is a CoW copy of parent.

src/basic/process-util.c
src/basic/process-util.h

index 6373909bc770db4f4e898e9ccfd88f66498ebc4b..483fc7b19241b5c54731454dec6e6a770e314f7c 100644 (file)
@@ -1149,6 +1149,41 @@ static void restore_sigsetp(sigset_t **ssp) {
                 (void) sigprocmask(SIG_SETMASK, *ssp, NULL);
 }
 
+pid_t clone_with_nested_stack(int (*fn)(void *), int flags, void *userdata) {
+        size_t ps;
+        pid_t pid;
+        void *mystack;
+
+        /* A wrapper around glibc's clone() call that automatically sets up a "nested" stack. Only supports
+         * invocations without CLONE_VM, so that we can continue to use the parent's stack mapping.
+         *
+         * Note: glibc's clone() wrapper does not synchronize malloc() locks. This means that if the parent
+         * is threaded these locks will be in an undefined state in the child, and hence memory allocations
+         * are likely going to run into deadlocks. Hence: if you use this function make sure your parent is
+         * strictly single-threaded or your child never calls malloc(). */
+
+        assert((flags & (CLONE_VM|CLONE_PARENT_SETTID|CLONE_CHILD_SETTID|
+                         CLONE_CHILD_CLEARTID|CLONE_SETTLS)) == 0);
+
+        /* We allocate some space on the stack to use as the stack for the child (hence "nested"). Note that
+         * the net effect is that the child will have the start of its stack inside the stack of the parent,
+         * but since they are a CoW copy of each other that's fine. We allocate one page-aligned page. But
+         * since we don't want to deal with differences between systems where the stack grows backwards or
+         * forwards we'll allocate one more and place the stack address in the middle. Except that we also
+         * want it page aligned, hence we'll allocate one page more. Makes 3. */
+
+        ps = page_size();
+        mystack = alloca(ps*3);
+        mystack = (uint8_t*) mystack + ps; /* move pointer one page ahead since stacks usually grow backwards */
+        mystack = (void*) ALIGN_TO((uintptr_t) mystack, ps); /* align to page size (moving things further ahead) */
+
+        pid = clone(fn, mystack, flags, userdata);
+        if (pid < 0)
+                return -errno;
+
+        return pid;
+}
+
 int safe_fork_full(
                 const char *name,
                 const int stdio_fds[3],
index 6ad4316614d9013c35baa6e8c762271cec528fe9..920074815e9487c5391a04406da436d255280d9e 100644 (file)
@@ -139,6 +139,8 @@ void reset_cached_pid(void);
 
 int must_be_root(void);
 
+pid_t clone_with_nested_stack(int (*fn)(void *), int flags, void *userdata);
+
 typedef enum ForkFlags {
         FORK_RESET_SIGNALS      = 1 <<  0, /* Reset all signal handlers and signal mask */
         FORK_CLOSE_ALL_FDS      = 1 <<  1, /* Close all open file descriptors in the child, except for 0,1,2 */