From 75829a9845b21e32a1202b63f865861baccb55da Mon Sep 17 00:00:00 2001 From: Pat DeSantis Date: Mon, 14 Dec 2020 16:38:43 -0500 Subject: [PATCH] darwin,libuv: use posix_spawn MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Spawning child processes in an Electron application with a hardened runtime has become slow in macOS Big Sur. This patch is a squashed version of https://github.com/libuv/libuv/pull/3064, with the addition of API availability annotations to fix a build warning (since Electron compiles with the `-Wunguarded-availability-new` flag). This patch should be removed when libuv PR 3064 is merged. Fixes: https://github.com/libuv/libuv/issues/3050 Fixes: https://github.com/electron/electron/issues/26143 PR-URL: https://github.com/libuv/libuv/pull/3064 Authored-by: Juan Pablo Canepa Co-authored-by: Marcello Bastéa-Forte Electron patch prepared by: Pat DeSantis --- patches/node/.patches | 1 + .../node/darwin_libuv_use_posix_spawn.patch | 266 ++++++++++++++++++ 2 files changed, 267 insertions(+) create mode 100644 patches/node/darwin_libuv_use_posix_spawn.patch diff --git a/patches/node/.patches b/patches/node/.patches index 53b1838944ef8..4091b510f8ef0 100644 --- a/patches/node/.patches +++ b/patches/node/.patches @@ -47,3 +47,4 @@ crypto_update_certdata_to_nss_3_56.patch n-api_src_provide_asynchronous_cleanup_hooks.patch fix_add_v8_enable_reverse_jsargs_defines_in_common_gypi.patch chore_expose_v8_initialization_isolate_callbacks.patch +darwin_libuv_use_posix_spawn.patch diff --git a/patches/node/darwin_libuv_use_posix_spawn.patch b/patches/node/darwin_libuv_use_posix_spawn.patch new file mode 100644 index 0000000000000..7cb27da01909a --- /dev/null +++ b/patches/node/darwin_libuv_use_posix_spawn.patch @@ -0,0 +1,266 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Pat DeSantis +Date: Mon, 14 Dec 2020 16:34:33 -0500 +Subject: darwin,libuv: use posix_spawn + +Spawning child processes in an Electron application with a hardened runtime has become slow in macOS Big Sur. + +This patch is a squashed version of https://github.com/libuv/libuv/pull/3064, with the addition of API availability annotations to fix a build warning (since Electron compiles with the `-Wunguarded-availability-new` flag). This patch should be removed when libuv PR 3064 is merged. + +Fixes: https://github.com/libuv/libuv/issues/3050 +Fixes: https://github.com/electron/electron/issues/26143 +PR-URL: https://github.com/libuv/libuv/pull/3064 + +diff --git a/deps/uv/src/unix/darwin-stub.h b/deps/uv/src/unix/darwin-stub.h +index b93cf67c59628577774cae5238c0349d59b5081a..b79c2e29fb6d58b1497851c25078b1efb6421a1e 100644 +--- a/deps/uv/src/unix/darwin-stub.h ++++ b/deps/uv/src/unix/darwin-stub.h +@@ -23,6 +23,7 @@ + #define UV_DARWIN_STUB_H_ + + #include ++#include + + struct CFArrayCallBacks; + struct CFRunLoopSourceContext; +@@ -94,4 +95,14 @@ static const int kFSEventStreamEventFlagRootChanged = 32; + static const int kFSEventStreamEventFlagUnmount = 128; + static const int kFSEventStreamEventFlagUserDropped = 2; + ++/* Copied from https://opensource.apple.com/source/xnu/xnu-6153.101.6/libsyscall/wrappers/spawn/spawn_private.h.auto.html */ ++int posix_spawnattr_set_uid_np(const posix_spawnattr_t*, uid_t) ++__API_AVAILABLE(macos(10.15), ios(13.0), tvos(13.0), watchos(6.0)); ++ ++int posix_spawnattr_set_gid_np(const posix_spawnattr_t*, gid_t) ++__API_AVAILABLE(macos(10.15), ios(13.0), tvos(13.0), watchos(6.0)); ++ ++int posix_spawnattr_set_groups_np(const posix_spawnattr_t*, int, gid_t*, uid_t) ++__API_AVAILABLE(macos(10.15), ios(13.0), tvos(13.0), watchos(6.0)); ++ + #endif /* UV_DARWIN_STUB_H_ */ +diff --git a/deps/uv/src/unix/process.c b/deps/uv/src/unix/process.c +index b021aaeba87d0b466341f40a016ef69e8beb7543..2022d00335d7f49eda556c9b00e22c15c70895cc 100644 +--- a/deps/uv/src/unix/process.c ++++ b/deps/uv/src/unix/process.c +@@ -34,8 +34,11 @@ + #include + + #if defined(__APPLE__) && !TARGET_OS_IPHONE ++#include ++#include + # include + # define environ (*_NSGetEnviron()) ++#include "darwin-stub.h" + #else + extern char **environ; + #endif +@@ -404,6 +407,182 @@ static void uv__process_child_init(const uv_process_options_t* options, + } + #endif + ++#if defined(__APPLE__) ++int uv__spawn_and_init_child_posix_spawn(const uv_process_options_t* options, ++ int stdio_count, ++ int (*pipes)[2], ++ pid_t* pid) ++ __API_AVAILABLE(macos(10.15)) { ++ int fd; ++ int err; ++ sigset_t signal_set; ++ posix_spawnattr_t attrs; ++ posix_spawn_file_actions_t actions; ++ unsigned int flags; ++ ++ err = posix_spawnattr_init(&attrs); ++ if(err != 0) ++ return err; ++ ++ if (options->flags & UV_PROCESS_SETUID) ++ if(posix_spawnattr_set_uid_np(&attrs, options->uid) != 0) ++ return err; ++ ++ if (options->flags & UV_PROCESS_SETGID) ++ if (posix_spawnattr_set_gid_np(&attrs, options->gid) != 0) ++ return err; ++ ++ if(options->flags & (UV_PROCESS_SETUID | UV_PROCESS_SETGID)) { ++ /* See the comment on the call to setgroups in uv__process_child_init above ++ * for why this is not a fatal error */ ++ SAVE_ERRNO(posix_spawnattr_set_groups_np(&attrs, 0, NULL, KAUTH_UID_NONE)); ++ } ++ ++ /* Set flags for spawn behavior ++ * 1) POSIX_SPAWN_CLOEXEC_DEFAULT: (Apple Extension) All descriptors in ++ * the parent will be treated as if they had been created with O_CLOEXEC. ++ * The only fds that will be passed on to the child are those manipulated by the file actions ++ * 2) POSIX_SPAWN_SETSIGDEF: Signals mentioned in spawn-sigdefault in the spawn attributes ++ * will be reset to behave as their default ++ * 3) POSIX_SPAWN_SETSIGMASK: Signal mask will be set to the value of spawn-sigmask in attributes ++ * 4) POSIX_SPAWN_SETSID: Make the process a new session leader if a detached session was requested. */ ++ flags = POSIX_SPAWN_CLOEXEC_DEFAULT | ++ POSIX_SPAWN_SETSIGDEF | ++ POSIX_SPAWN_SETSIGMASK; ++ if(options->flags & UV_PROCESS_DETACHED) ++ flags |= POSIX_SPAWN_SETSID; ++ err = posix_spawnattr_setflags(&attrs, flags); ++ if(err != 0) ++ return err; ++ ++ // Reset all signal the child to their default behavior ++ sigfillset(&signal_set); ++ err = posix_spawnattr_setsigdefault(&attrs, &signal_set); ++ if(err != 0) ++ return err; ++ ++ // Reset the signal mask for all signals ++ sigemptyset(&signal_set); ++ err = posix_spawnattr_setsigmask(&attrs, &signal_set); ++ if(err != 0) ++ return err; ++ ++ err = posix_spawn_file_actions_init(&actions); ++ if(err != 0) ++ return err; ++ ++ if (options->cwd != NULL && posix_spawn_file_actions_addchdir_np(&actions, options->cwd)) ++ return err; ++ ++ // First, dupe any required fd into orbit, out of the range of ++ // the descriptors that should be mapped in. ++ for(fd = 0 ; fd < stdio_count; ++fd) { ++ if(pipes[fd][1] < 0) ++ continue; ++ ++ err = posix_spawn_file_actions_adddup2(&actions, pipes[fd][1], stdio_count + fd); ++ if(err != 0) ++ return err; ++ } ++ ++ // Second, move the descriptors into their respective places ++ for(fd = 0 ; fd < stdio_count; ++fd) { ++ if(pipes[fd][1] < 0) ++ continue; ++ ++ err = posix_spawn_file_actions_adddup2(&actions, stdio_count + fd, fd); ++ if(err != 0) ++ return err; ++ } ++ ++ // Finally, close all the superfluous descriptors ++ for(fd = 0; fd < stdio_count; ++fd) { ++ if(pipes[fd][1] < 0) ++ continue; ++ ++ err = posix_spawn_file_actions_addclose(&actions, stdio_count + fd); ++ if(err != 0) ++ return err; ++ } ++ ++ // Finally process the standard streams as per de documentation ++ for(fd = 0 ; fd < 3 ; ++fd) { ++ // If ignored, open as /dev/null ++ const int oflags = fd == 0 ? O_RDONLY : O_RDWR; ++ const int mode = 0; ++ ++ if(pipes[fd][1] != -1) ++ continue; ++ ++ err = posix_spawn_file_actions_addopen(&actions, fd, "/dev/null", oflags, mode); ++ if(err != 0) ++ return err; ++ } ++ ++ // Preserve parent environment if not explicitly set ++ char ** env = options->env ? options->env : environ; ++ ++ // Spawn the child ++ err = posix_spawnp(pid, options->file, &actions, &attrs, options->args, env); ++ ++ return UV__ERR(err); ++} ++#endif ++ ++int uv__spawn_and_init_child_fork(const uv_process_options_t* options, ++ int stdio_count, ++ int (*pipes)[2], ++ int error_fd, ++ pid_t* pid) { ++ *pid = fork(); ++ ++ if (*pid == -1) { ++ /* Failed to fork */ ++ return UV__ERR(errno); ++ } ++ ++ if (*pid == 0) { ++ /* Fork succeeded, in the child process */ ++ uv__process_child_init(options, stdio_count, pipes, error_fd); ++ abort(); ++ } ++ ++ /* Fork succeeded, in the parent process */ ++ return 0; ++} ++ ++int uv__spawn_and_init_child(const uv_process_options_t* options, ++ int stdio_count, ++ int (*pipes)[2], ++ int error_fd, ++ pid_t* pid) { ++ ++#if defined(__APPLE__) ++ if(__builtin_available(macOS 10.15, *)) { ++ /* Especial child process spawn case for macOS Big Sur (11.0) onwards ++ * ++ * Big Sur introduced a significant performance degradation on a call to ++ * fork/exec when the process has many pages mmaped in with MAP_JIT, like, say ++ * a javascript interpreter. Electron-based applications, for example, ++ * are impacted; though the magnitude of the impact depends on how much the ++ * app relies on subprocesses. ++ * ++ * On macOS, though, posix_spawn is implemented in a way that does not ++ * exhibit the problem. This block implements the forking and preparation ++ * logic with poxis_spawn and its related primitves. It also takes advantage of ++ * the macOS extension POSIX_SPAWN_CLOEXEC_DEFAULT that makes impossible to ++ * leak descriptors to the child process. ++ * ++ * see https://github.com/libuv/libuv/issues/3050 ++ */ ++ return uv__spawn_and_init_child_posix_spawn(options, stdio_count, pipes, pid); ++ } else { ++#endif ++ return uv__spawn_and_init_child_fork(options, stdio_count, pipes, error_fd, pid); ++#if defined(__APPLE__) ++ } ++#endif ++} + + int uv_spawn(uv_loop_t* loop, + uv_process_t* process, +@@ -486,21 +665,16 @@ int uv_spawn(uv_loop_t* loop, + + /* Acquire write lock to prevent opening new fds in worker threads */ + uv_rwlock_wrlock(&loop->cloexec_lock); +- pid = fork(); +- +- if (pid == -1) { +- err = UV__ERR(errno); ++ ++ /* Spawn the child */ ++ err = uv__spawn_and_init_child(options, stdio_count, pipes, signal_pipe[1], &pid); ++ if(err != 0) { + uv_rwlock_wrunlock(&loop->cloexec_lock); + uv__close(signal_pipe[0]); + uv__close(signal_pipe[1]); + goto error; + } +- +- if (pid == 0) { +- uv__process_child_init(options, stdio_count, pipes, signal_pipe[1]); +- abort(); +- } +- ++ + /* Release lock in parent process */ + uv_rwlock_wrunlock(&loop->cloexec_lock); + uv__close(signal_pipe[1]);