fix: libuv patches to address child_process.spawn slowness (#33406)

* fix: libuv patches to address child_process.spawn slowness

* chore: backport additional patches

* Update .patches

* chore: update patches

* chore: update patches

Co-authored-by: Jeremy Rose <nornagon@nornagon.net>
Co-authored-by: deepak1556 <hop2deep@gmail.com>
This commit is contained in:
trop[bot]
2022-03-29 19:45:46 -04:00
committed by GitHub
parent 4e75499270
commit e5e8dd5156
12 changed files with 1858 additions and 0 deletions

View File

@@ -31,3 +31,14 @@ fix_remove_expired_dst_root_ca_x3.patch
fix_system_python2_error.patch
fix_event_with_invalid_timestamp_in_trace_log.patch
test_fix_test-datetime-change-notify_after_daylight_change.patch
unix_protect_fork_in_uv_spawn_from_signals.patch
process_monitor_for_exit_with_kqueue_on_bsds_3441.patch
process_bsd_handle_kevent_note_exit_failure_3451.patch
reland_macos_use_posix_spawn_instead_of_fork_3257.patch
process_only_use_f_dupfd_cloexec_if_it_is_defined_3512.patch
unix_simplify_uv_cloexec_fcntl_3492.patch
unix_remove_uv_cloexec_ioctl_3515.patch
process_simplify_uv_write_int_calls_3519.patch
macos_don_t_use_thread-unsafe_strtok_3524.patch
process_fix_hang_after_note_exit_3521.patch
process_reset_the_signal_mask_if_the_fork_fails_3537.patch

View File

@@ -0,0 +1,54 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Ben Noordhuis <info@bnoordhuis.nl>
Date: Wed, 9 Mar 2022 11:06:39 +0100
Subject: macos: don't use thread-unsafe strtok() (#3524)
Refs https://github.com/libuv/libuv/pull/3524
diff --git a/deps/uv/src/unix/process.c b/deps/uv/src/unix/process.c
index d40e3f9bebcdfbc5051c9a6af2452c2421728c9e..c0ec01c4b0ad32dcaa5438f5ca02030c31517095 100644
--- a/deps/uv/src/unix/process.c
+++ b/deps/uv/src/unix/process.c
@@ -380,30 +380,22 @@ static void uv__spawn_init_posix_spawn_fncs(void) {
static void uv__spawn_init_can_use_setsid(void) {
- static const int MACOS_CATALINA_VERSION_MAJOR = 19;
- char version_str[256];
- char* version_major_str;
- size_t version_str_size = 256;
- int r;
- int version_major;
-
- /* Get a version string */
- r = sysctlbyname("kern.osrelease", version_str, &version_str_size, NULL, 0);
- if (r != 0)
+ int which[] = {CTL_KERN, KERN_OSRELEASE};
+ unsigned major;
+ unsigned minor;
+ unsigned patch;
+ char buf[256];
+ size_t len;
+
+ len = sizeof(buf);
+ if (sysctl(which, ARRAY_SIZE(which), buf, &len, NULL, 0))
return;
- /* Try to get the major version number. If not found
- * fall back to the fork/exec flow */
- version_major_str = strtok(version_str, ".");
- if (version_major_str == NULL)
+ /* NULL specifies to use LC_C_LOCALE */
+ if (3 != sscanf_l(buf, NULL, "%u.%u.%u", &major, &minor, &patch))
return;
- /* Parse the version major as a number. If it is greater than
- * the major version for macOS Catalina (aka macOS 10.15), then
- * the POSIX_SPAWN_SETSID flag is available */
- version_major = atoi_l(version_major_str, NULL); /* Use LC_C_LOCALE */
- if (version_major >= MACOS_CATALINA_VERSION_MAJOR)
- posix_spawn_can_use_setsid = 1;
+ posix_spawn_can_use_setsid = (major >= 19); /* macOS Catalina */
}

View File

@@ -0,0 +1,70 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Jameson Nash <vtjnash@gmail.com>
Date: Tue, 1 Feb 2022 15:27:12 -0500
Subject: process,bsd: handle kevent NOTE_EXIT failure (#3451)
The kernel may return ESRCH if the child has already exited here.
This is rather annoying, and means we must indirectly handle
notification to our event loop of the process exit.
Refs: https://github.com/libuv/libuv/pull/3441
Refs: https://github.com/libuv/libuv/pull/3257
diff --git a/deps/uv/src/unix/internal.h b/deps/uv/src/unix/internal.h
index cc15596f431aa42c30bc0510e38c0d5879a47c85..5bc9f36037594006e204dee31bc7d969bbf650d4 100644
--- a/deps/uv/src/unix/internal.h
+++ b/deps/uv/src/unix/internal.h
@@ -134,7 +134,8 @@ typedef struct uv__stream_queued_fds_s uv__stream_queued_fds_t;
/* loop flags */
enum {
- UV_LOOP_BLOCK_SIGPROF = 1
+ UV_LOOP_BLOCK_SIGPROF = 0x1,
+ UV_LOOP_REAP_CHILDREN = 0x2
};
/* flags of excluding ifaddr */
diff --git a/deps/uv/src/unix/kqueue.c b/deps/uv/src/unix/kqueue.c
index 35200f17495d80ed2d19ef9f6f76bbc92ee042f6..071fe0ce0938657d0fb840af62a432352e938a8a 100644
--- a/deps/uv/src/unix/kqueue.c
+++ b/deps/uv/src/unix/kqueue.c
@@ -285,7 +285,7 @@ void uv__io_poll(uv_loop_t* loop, int timeout) {
for (i = 0; i < nfds; i++) {
ev = events + i;
if (ev->filter == EVFILT_PROC) {
- uv__wait_children(loop);
+ loop->flags |= UV_LOOP_REAP_CHILDREN;
nevents++;
continue;
}
@@ -383,6 +383,11 @@ void uv__io_poll(uv_loop_t* loop, int timeout) {
nevents++;
}
+ if (loop->flags & UV_LOOP_REAP_CHILDREN) {
+ loop->flags &= ~UV_LOOP_REAP_CHILDREN;
+ uv__wait_children(loop);
+ }
+
if (reset_timeout != 0) {
timeout = user_timeout;
reset_timeout = 0;
diff --git a/deps/uv/src/unix/process.c b/deps/uv/src/unix/process.c
index 3ee298b473ae4fbc2fa2039ba7150740b6717b73..690f542520160c7314a92b3f452867e83ee34594 100644
--- a/deps/uv/src/unix/process.c
+++ b/deps/uv/src/unix/process.c
@@ -507,8 +507,12 @@ int uv_spawn(uv_loop_t* loop,
#if defined(__APPLE__) || defined(__DragonFly__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__)
struct kevent event;
EV_SET(&event, pid, EVFILT_PROC, EV_ADD | EV_ONESHOT, NOTE_EXIT, 0, 0);
- if (kevent(loop->backend_fd, &event, 1, NULL, 0, NULL))
- abort();
+ if (kevent(loop->backend_fd, &event, 1, NULL, 0, NULL)) {
+ if (errno != ESRCH)
+ abort();
+ /* Process already exited. Call waitpid on the next loop iteration. */
+ loop->flags |= UV_LOOP_REAP_CHILDREN;
+ }
#endif
QUEUE_INSERT_TAIL(&loop->process_handles, &process->queue);

View File

@@ -0,0 +1,158 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Jameson Nash <vtjnash@gmail.com>
Date: Wed, 23 Mar 2022 15:39:38 +0900
Subject: process: fix hang after NOTE_EXIT (#3521)
Bug #3504 seems to affect more platforms than just OpenBSD. As this
seems to be a race condition in these kernels, we do not want to fail
because of it. Instead, we remove the WNOHANG flag from waitpid, and
track exactly which processes have exited. Should also be a slight speed
improvement for excessively large numbers of live children.
diff --git a/deps/uv/src/unix/kqueue.c b/deps/uv/src/unix/kqueue.c
index 071fe0ce0938657d0fb840af62a432352e938a8a..4c4d990ff5fa6c8ab937be2e4f79ccdaf90670c2 100644
--- a/deps/uv/src/unix/kqueue.c
+++ b/deps/uv/src/unix/kqueue.c
@@ -117,6 +117,7 @@ void uv__io_poll(uv_loop_t* loop, int timeout) {
unsigned int revents;
QUEUE* q;
uv__io_t* w;
+ uv_process_t* process;
sigset_t* pset;
sigset_t set;
uint64_t base;
@@ -284,12 +285,22 @@ void uv__io_poll(uv_loop_t* loop, int timeout) {
loop->watchers[loop->nwatchers + 1] = (void*) (uintptr_t) nfds;
for (i = 0; i < nfds; i++) {
ev = events + i;
+ fd = ev->ident;
+
+ /* Handle kevent NOTE_EXIT results */
if (ev->filter == EVFILT_PROC) {
- loop->flags |= UV_LOOP_REAP_CHILDREN;
+ QUEUE_FOREACH(q, &loop->process_handles) {
+ process = QUEUE_DATA(q, uv_process_t, queue);
+ if (process->pid == fd) {
+ process->flags |= UV_HANDLE_REAP;
+ loop->flags |= UV_LOOP_REAP_CHILDREN;
+ break;
+ }
+ }
nevents++;
continue;
}
- fd = ev->ident;
+
/* Skip invalidated events, see uv__platform_invalidate_fd */
if (fd == -1)
continue;
diff --git a/deps/uv/src/unix/process.c b/deps/uv/src/unix/process.c
index c0ec01c4b0ad32dcaa5438f5ca02030c31517095..3b00fc272a27f65cbd026362f12a1099ff14da4f 100644
--- a/deps/uv/src/unix/process.c
+++ b/deps/uv/src/unix/process.c
@@ -59,12 +59,18 @@ extern char **environ;
# include <grp.h>
#endif
-#if defined(__APPLE__) || defined(__DragonFly__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__)
+#if defined(__APPLE__) || \
+ defined(__DragonFly__) || \
+ defined(__FreeBSD__) || \
+ defined(__NetBSD__) || \
+ defined(__OpenBSD__)
#include <sys/event.h>
+#else
+#define UV_USE_SIGCHLD
#endif
-#if !(defined(__APPLE__) || defined(__DragonFly__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__))
+#ifdef UV_USE_SIGCHLD
static void uv__chld(uv_signal_t* handle, int signum) {
assert(signum == SIGCHLD);
uv__wait_children(handle->loop);
@@ -76,6 +82,7 @@ void uv__wait_children(uv_loop_t* loop) {
int exit_status;
int term_signal;
int status;
+ int options;
pid_t pid;
QUEUE pending;
QUEUE* q;
@@ -89,19 +96,33 @@ void uv__wait_children(uv_loop_t* loop) {
process = QUEUE_DATA(q, uv_process_t, queue);
q = QUEUE_NEXT(q);
+#ifndef UV_USE_SIGCHLD
+ if ((process->flags & UV_HANDLE_REAP) == 0)
+ continue;
+ options = 0;
+ process->flags &= ~UV_HANDLE_REAP;
+#else
+ options = WNOHANG;
+#endif
+
do
- pid = waitpid(process->pid, &status, WNOHANG);
+ pid = waitpid(process->pid, &status, options);
while (pid == -1 && errno == EINTR);
- if (pid == 0)
+#ifdef UV_USE_SIGCHLD
+ if (pid == 0) /* Not yet exited */
continue;
+#endif
if (pid == -1) {
if (errno != ECHILD)
abort();
+ /* The child died, and we missed it. This probably means someone else
+ * stole the waitpid from us. Handle this by not handling it at all. */
continue;
}
+ assert(pid == process->pid);
process->status = status;
QUEUE_REMOVE(&process->queue);
QUEUE_INSERT_TAIL(&pending, &process->queue);
@@ -958,7 +979,7 @@ int uv_spawn(uv_loop_t* loop,
goto error;
}
-#if !(defined(__APPLE__) || defined(__DragonFly__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__))
+#ifdef UV_USE_SIGCHLD
uv_signal_start(&loop->child_watcher, uv__chld, SIGCHLD);
#endif
@@ -977,13 +998,14 @@ int uv_spawn(uv_loop_t* loop,
* fail to open a stdio handle. This ensures we can eventually reap the child
* with waitpid. */
if (exec_errorno == 0) {
-#if defined(__APPLE__) || defined(__DragonFly__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__)
+#ifndef UV_USE_SIGCHLD
struct kevent event;
EV_SET(&event, pid, EVFILT_PROC, EV_ADD | EV_ONESHOT, NOTE_EXIT, 0, 0);
if (kevent(loop->backend_fd, &event, 1, NULL, 0, NULL)) {
if (errno != ESRCH)
abort();
/* Process already exited. Call waitpid on the next loop iteration. */
+ process->flags |= UV_HANDLE_REAP;
loop->flags |= UV_LOOP_REAP_CHILDREN;
}
#endif
diff --git a/deps/uv/src/uv-common.h b/deps/uv/src/uv-common.h
index a92912fdb1a72ea54cc7ddd374cee5ec339a6a33..82cdb914f91dcf3bbd718fba6e39ce39648ea507 100644
--- a/deps/uv/src/uv-common.h
+++ b/deps/uv/src/uv-common.h
@@ -129,7 +129,10 @@ enum {
UV_SIGNAL_ONE_SHOT = 0x02000000,
/* Only used by uv_poll_t handles. */
- UV_HANDLE_POLL_SLOW = 0x01000000
+ UV_HANDLE_POLL_SLOW = 0x01000000,
+
+ /* Only used by uv_process_t handles. */
+ UV_HANDLE_REAP = 0x10000000
};
int uv__loop_configure(uv_loop_t* loop, uv_loop_option option, va_list ap);

View File

@@ -0,0 +1,170 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Jeremy Rose <nornagon@nornagon.net>
Date: Mon, 31 Jan 2022 11:49:22 -0800
Subject: process: monitor for exit with kqueue on BSDs (#3441)
This adds a workaround for an xnu kernel bug that sometimes results in
SIGCHLD not being delivered. The workaround is to use kevent to listen
for EVFILT_PROC/NOTE_EXIT events instead of relying on SIGCHLD on *BSD.
Apple rdar: FB9529664
Refs: https://github.com/libuv/libuv/pull/3257
diff --git a/deps/uv/src/unix/internal.h b/deps/uv/src/unix/internal.h
index 3bdf7283bd4fd87da3fbad82711ad703f1b4accc..cc15596f431aa42c30bc0510e38c0d5879a47c85 100644
--- a/deps/uv/src/unix/internal.h
+++ b/deps/uv/src/unix/internal.h
@@ -267,6 +267,7 @@ uv_handle_type uv__handle_type(int fd);
FILE* uv__open_file(const char* path);
int uv__getpwuid_r(uv_passwd_t* pwd);
int uv__search_path(const char* prog, char* buf, size_t* buflen);
+void uv__wait_children(uv_loop_t* loop);
/* random */
int uv__random_devurandom(void* buf, size_t buflen);
diff --git a/deps/uv/src/unix/kqueue.c b/deps/uv/src/unix/kqueue.c
index bf183d5fdc0ba89913469a294322eef84bc4cee8..35200f17495d80ed2d19ef9f6f76bbc92ee042f6 100644
--- a/deps/uv/src/unix/kqueue.c
+++ b/deps/uv/src/unix/kqueue.c
@@ -284,6 +284,11 @@ void uv__io_poll(uv_loop_t* loop, int timeout) {
loop->watchers[loop->nwatchers + 1] = (void*) (uintptr_t) nfds;
for (i = 0; i < nfds; i++) {
ev = events + i;
+ if (ev->filter == EVFILT_PROC) {
+ uv__wait_children(loop);
+ nevents++;
+ continue;
+ }
fd = ev->ident;
/* Skip invalidated events, see uv__platform_invalidate_fd */
if (fd == -1)
diff --git a/deps/uv/src/unix/process.c b/deps/uv/src/unix/process.c
index a72660ec1c4e7a2734cc73da3766aa620099512f..3ee298b473ae4fbc2fa2039ba7150740b6717b73 100644
--- a/deps/uv/src/unix/process.c
+++ b/deps/uv/src/unix/process.c
@@ -45,10 +45,20 @@ extern char **environ;
# include <grp.h>
#endif
+#if defined(__APPLE__) || defined(__DragonFly__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__)
+#include <sys/event.h>
+#endif
+
+#if !(defined(__APPLE__) || defined(__DragonFly__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__))
static void uv__chld(uv_signal_t* handle, int signum) {
+ assert(signum == SIGCHLD);
+ uv__wait_children(handle->loop);
+}
+#endif
+
+void uv__wait_children(uv_loop_t* loop) {
uv_process_t* process;
- uv_loop_t* loop;
int exit_status;
int term_signal;
int status;
@@ -57,10 +67,7 @@ static void uv__chld(uv_signal_t* handle, int signum) {
QUEUE* q;
QUEUE* h;
- assert(signum == SIGCHLD);
-
QUEUE_INIT(&pending);
- loop = handle->loop;
h = &loop->process_handles;
q = QUEUE_HEAD(h);
@@ -420,7 +427,9 @@ int uv_spawn(uv_loop_t* loop,
if (err)
goto error;
+#if !(defined(__APPLE__) || defined(__DragonFly__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__))
uv_signal_start(&loop->child_watcher, uv__chld, SIGCHLD);
+#endif
/* Acquire write lock to prevent opening new fds in worker threads */
uv_rwlock_wrlock(&loop->cloexec_lock);
@@ -495,6 +504,13 @@ int uv_spawn(uv_loop_t* loop,
/* Only activate this handle if exec() happened successfully */
if (exec_errorno == 0) {
+#if defined(__APPLE__) || defined(__DragonFly__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__)
+ struct kevent event;
+ EV_SET(&event, pid, EVFILT_PROC, EV_ADD | EV_ONESHOT, NOTE_EXIT, 0, 0);
+ if (kevent(loop->backend_fd, &event, 1, NULL, 0, NULL))
+ abort();
+#endif
+
QUEUE_INSERT_TAIL(&loop->process_handles, &process->queue);
uv__handle_start(process);
}
diff --git a/deps/uv/test/test-list.h b/deps/uv/test/test-list.h
index 74588407cfbd8d9430d1d6e675361de3db420ab9..e9b79b1b62dc2409a9173f59029fedf22b5acb2b 100644
--- a/deps/uv/test/test-list.h
+++ b/deps/uv/test/test-list.h
@@ -317,6 +317,7 @@ TEST_DECLARE (spawn_reads_child_path)
TEST_DECLARE (spawn_inherit_streams)
TEST_DECLARE (spawn_quoted_path)
TEST_DECLARE (spawn_tcp_server)
+TEST_DECLARE (spawn_exercise_sigchld_issue)
TEST_DECLARE (fs_poll)
TEST_DECLARE (fs_poll_getpath)
TEST_DECLARE (fs_poll_close_request)
@@ -936,6 +937,7 @@ TASK_LIST_START
TEST_ENTRY (spawn_inherit_streams)
TEST_ENTRY (spawn_quoted_path)
TEST_ENTRY (spawn_tcp_server)
+ TEST_ENTRY (spawn_exercise_sigchld_issue)
TEST_ENTRY (fs_poll)
TEST_ENTRY (fs_poll_getpath)
TEST_ENTRY (fs_poll_close_request)
diff --git a/deps/uv/test/test-spawn.c b/deps/uv/test/test-spawn.c
index 886ddaf63b509deb310afb9d7ab1826bea495552..3791201992db3e0d31141ac40b24adb58e2dc556 100644
--- a/deps/uv/test/test-spawn.c
+++ b/deps/uv/test/test-spawn.c
@@ -1891,6 +1891,44 @@ TEST_IMPL(spawn_quoted_path) {
#endif
}
+TEST_IMPL(spawn_exercise_sigchld_issue) {
+ int r;
+ int i;
+ uv_process_options_t dummy_options = {0};
+ uv_process_t dummy_processes[100];
+ char* args[2];
+
+ init_process_options("spawn_helper1", exit_cb);
+
+ r = uv_spawn(uv_default_loop(), &process, &options);
+ ASSERT_EQ(r, 0);
+
+ // This test exercises a bug in the darwin kernel that causes SIGCHLD not to
+ // be delivered sometimes. Calling posix_spawn many times increases the
+ // likelihood of encountering this issue, so spin a few times to make this
+ // test more reliable.
+ dummy_options.file = args[0] = "program-that-had-better-not-exist";
+ args[1] = NULL;
+ dummy_options.args = args;
+ dummy_options.exit_cb = fail_cb;
+ dummy_options.flags = 0;
+ for (i = 0; i < 100; i++) {
+ r = uv_spawn(uv_default_loop(), &dummy_processes[i], &dummy_options);
+ if (r != UV_ENOENT)
+ ASSERT_EQ(r, UV_EACCES);
+ uv_close((uv_handle_t*) &dummy_processes[i], close_cb);
+ }
+
+ r = uv_run(uv_default_loop(), UV_RUN_DEFAULT);
+ ASSERT_EQ(r, 0);
+
+ ASSERT_EQ(exit_cb_called, 1);
+ ASSERT_EQ(close_cb_called, 101);
+
+ MAKE_VALGRIND_HAPPY();
+ return 0;
+}
+
/* Helper for child process of spawn_inherit_streams */
#ifndef _WIN32
void spawn_stdin_stdout(void) {

View File

@@ -0,0 +1,56 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Jameson Nash <vtjnash@gmail.com>
Date: Sat, 5 Mar 2022 12:52:04 -0500
Subject: process: only use F_DUPFD_CLOEXEC if it is defined (#3512)
We can save a syscall on most modern systems (required by POSIX 2008),
but not on all systems.
Also handle errors from CLOEXEC. Even though fcntl does not really
define there to be any, it could theoretically be EBADF if the user
happened to pass a bad file descriptor to the same number fd (such that
no other code happened to already fail on that).
diff --git a/deps/uv/src/unix/process.c b/deps/uv/src/unix/process.c
index 6adcfabc7416f84cdcde865d21cf2727a4d3b5e1..44d9af9a17626a2f85e12f4983b0540b5993e290 100644
--- a/deps/uv/src/unix/process.c
+++ b/deps/uv/src/unix/process.c
@@ -266,11 +266,22 @@ static void uv__process_child_init(const uv_process_options_t* options,
use_fd = pipes[fd][1];
if (use_fd < 0 || use_fd >= fd)
continue;
+#ifdef F_DUPFD_CLOEXEC /* POSIX 2008 */
pipes[fd][1] = fcntl(use_fd, F_DUPFD_CLOEXEC, stdio_count);
+#else
+ pipes[fd][1] = fcntl(use_fd, F_DUPFD, stdio_count);
+#endif
if (pipes[fd][1] == -1) {
uv__write_int(error_fd, UV__ERR(errno));
_exit(127);
}
+#ifndef F_DUPFD_CLOEXEC /* POSIX 2008 */
+ n = uv__cloexec_fcntl(pipes[fd][1], 1);
+ if (n) {
+ uv__write_int(error_fd, n);
+ _exit(127);
+ }
+#endif
}
for (fd = 0; fd < stdio_count; fd++) {
@@ -295,8 +306,13 @@ static void uv__process_child_init(const uv_process_options_t* options,
}
if (fd == use_fd) {
- if (close_fd == -1)
- uv__cloexec_fcntl(use_fd, 0);
+ if (close_fd == -1) {
+ n = uv__cloexec_fcntl(use_fd, 0);
+ if (n) {
+ uv__write_int(error_fd, n);
+ _exit(127);
+ }
+ }
}
else {
fd = dup2(use_fd, fd);

View File

@@ -0,0 +1,36 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Jameson Nash <vtjnash@gmail.com>
Date: Fri, 11 Mar 2022 12:05:24 -0500
Subject: process: reset the signal mask if the fork fails (#3537)
Fix a regression that sneaked into posix spawn changes.
Refs: https://github.com/libuv/libuv/pull/3257
diff --git a/deps/uv/src/unix/process.c b/deps/uv/src/unix/process.c
index 3b00fc272a27f65cbd026362f12a1099ff14da4f..71bc995e80ec99afade710bca2faeddf56261b9f 100644
--- a/deps/uv/src/unix/process.c
+++ b/deps/uv/src/unix/process.c
@@ -805,11 +805,6 @@ static int uv__spawn_and_init_child_fork(const uv_process_options_t* options,
*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);
@@ -819,6 +814,10 @@ static int uv__spawn_and_init_child_fork(const uv_process_options_t* options,
if (pthread_sigmask(SIG_SETMASK, &sigoldset, NULL) != 0)
abort();
+ if (*pid == -1)
+ /* Failed to fork */
+ return UV__ERR(errno);
+
/* Fork succeeded, in the parent process */
return 0;
}

View File

@@ -0,0 +1,107 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Jameson Nash <vtjnash@gmail.com>
Date: Mon, 7 Mar 2022 17:07:49 -0500
Subject: process: simplify uv__write_int calls (#3519)
Refs https://github.com/libuv/libuv/pull/3519
diff --git a/deps/uv/src/unix/process.c b/deps/uv/src/unix/process.c
index ef9db6cdb8bdd9867cfdc3e9a29573ea7b9a560a..d40e3f9bebcdfbc5051c9a6af2452c2421728c9e 100644
--- a/deps/uv/src/unix/process.c
+++ b/deps/uv/src/unix/process.c
@@ -212,10 +212,9 @@ static void uv__write_int(int fd, int val) {
n = write(fd, &val, sizeof(val));
while (n == -1 && errno == EINTR);
- if (n == -1 && errno == EPIPE)
- return; /* parent process has quit */
-
- assert(n == sizeof(val));
+ /* The write might have failed (e.g. if the parent process has died),
+ * but we have nothing left but to _exit ourself now too. */
+ _exit(127);
}
@@ -252,7 +251,6 @@ static void uv__process_child_init(const uv_process_options_t* options,
continue;
uv__write_int(error_fd, UV__ERR(errno));
- _exit(127);
}
if (options->flags & UV_PROCESS_DETACHED)
@@ -273,14 +271,11 @@ static void uv__process_child_init(const uv_process_options_t* options,
#endif
if (pipes[fd][1] == -1) {
uv__write_int(error_fd, UV__ERR(errno));
- _exit(127);
}
#ifndef F_DUPFD_CLOEXEC /* POSIX 2008 */
n = uv__cloexec(pipes[fd][1], 1);
- if (n) {
+ if (n)
uv__write_int(error_fd, n);
- _exit(127);
- }
#endif
}
@@ -300,7 +295,6 @@ static void uv__process_child_init(const uv_process_options_t* options,
if (use_fd < 0) {
uv__write_int(error_fd, UV__ERR(errno));
- _exit(127);
}
}
}
@@ -308,10 +302,8 @@ static void uv__process_child_init(const uv_process_options_t* options,
if (fd == use_fd) {
if (close_fd == -1) {
n = uv__cloexec(use_fd, 0);
- if (n) {
+ if (n)
uv__write_int(error_fd, n);
- _exit(127);
- }
}
}
else {
@@ -320,7 +312,6 @@ static void uv__process_child_init(const uv_process_options_t* options,
if (fd == -1) {
uv__write_int(error_fd, UV__ERR(errno));
- _exit(127);
}
if (fd <= 2 && close_fd == -1)
@@ -332,7 +323,6 @@ static void uv__process_child_init(const uv_process_options_t* options,
if (options->cwd != NULL && chdir(options->cwd)) {
uv__write_int(error_fd, UV__ERR(errno));
- _exit(127);
}
if (options->flags & (UV_PROCESS_SETUID | UV_PROCESS_SETGID)) {
@@ -348,12 +338,10 @@ static void uv__process_child_init(const uv_process_options_t* options,
if ((options->flags & UV_PROCESS_SETGID) && setgid(options->gid)) {
uv__write_int(error_fd, UV__ERR(errno));
- _exit(127);
}
if ((options->flags & UV_PROCESS_SETUID) && setuid(options->uid)) {
uv__write_int(error_fd, UV__ERR(errno));
- _exit(127);
}
if (options->env != NULL)
@@ -367,8 +355,6 @@ static void uv__process_child_init(const uv_process_options_t* options,
execvp(options->file, options->args);
uv__write_int(error_fd, UV__ERR(errno));
- _exit(127);
- abort();
}
#endif

View File

@@ -0,0 +1,893 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Jameson Nash <vtjnash@gmail.com>
Date: Wed, 2 Mar 2022 15:15:39 -0500
Subject: Reland "macos: use posix_spawn instead of fork" (#3257)
Fixes: https://github.com/libuv/libuv/issues/3050
Refs: https://github.com/libuv/libuv/issues/3086
Refs: https://github.com/libuv/libuv/pull/3064
Refs: https://github.com/libuv/libuv/pull/3107
Refs: https://github.com/libuv/libuv/pull/3064
This reverts commit 217fdf4265589889d00c7c0622fde2710971a020, then fixes
several issues with it:
* remove error fast-cleanup code that triggers a nodejs bug
Refs: https://github.com/libuv/libuv/pull/3107#issuecomment-782482608
* protect posix_spawn from EINTR
This is not a documented valid error, but seems to have been observed.
* ignore setuid/setgid syscall
This kernel function is not permitted unless the process is setuid root,
so disable this syscall. Falling back to fork/exec should be okay for
the rare cases that the user decides they need to do setuid(getuid()) or
setuid(geteuid()) for the child.
Refs: https://github.com/libuv/libuv/pull/3107#issuecomment-782482608
* improve posix_spawn path search
Ports the improvements in musl back to this function
* fix some additional problems and formatting issues
We previously might fail to start a watcher, in rare failure cases,
resulting in a zombie that we would fail to kill. Also avoid creating
the signal-pipe unless required (addresses a review comment from Apple)
* fix fd->fd mapping reuse
There was a chance that when duplicating the fd's into stdio_count+fd we
might be closing a currently opened fd with that value.
diff --git a/deps/uv/src/unix/process.c b/deps/uv/src/unix/process.c
index 690f542520160c7314a92b3f452867e83ee34594..6adcfabc7416f84cdcde865d21cf2727a4d3b5e1 100644
--- a/deps/uv/src/unix/process.c
+++ b/deps/uv/src/unix/process.c
@@ -27,6 +27,7 @@
#include <assert.h>
#include <errno.h>
#include <signal.h>
+#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
@@ -35,8 +36,21 @@
#include <poll.h>
#if defined(__APPLE__) && !TARGET_OS_IPHONE
+# include <spawn.h>
+# include <paths.h>
+# include <sys/kauth.h>
+# include <sys/types.h>
+# include <sys/sysctl.h>
+# include <dlfcn.h>
# include <crt_externs.h>
+# include <xlocale.h>
# define environ (*_NSGetEnviron())
+
+/* macOS 10.14 back does not define this constant */
+# ifndef POSIX_SPAWN_SETSID
+# define POSIX_SPAWN_SETSID 1024
+# endif
+
#else
extern char **environ;
#endif
@@ -252,7 +266,7 @@ static void uv__process_child_init(const uv_process_options_t* options,
use_fd = pipes[fd][1];
if (use_fd < 0 || use_fd >= fd)
continue;
- pipes[fd][1] = fcntl(use_fd, F_DUPFD, stdio_count);
+ pipes[fd][1] = fcntl(use_fd, F_DUPFD_CLOEXEC, stdio_count);
if (pipes[fd][1] == -1) {
uv__write_int(error_fd, UV__ERR(errno));
_exit(127);
@@ -260,16 +274,16 @@ static void uv__process_child_init(const uv_process_options_t* options,
}
for (fd = 0; fd < stdio_count; fd++) {
- close_fd = pipes[fd][0];
+ close_fd = -1;
use_fd = pipes[fd][1];
if (use_fd < 0) {
if (fd >= 3)
continue;
else {
- /* redirect stdin, stdout and stderr to /dev/null even if UV_IGNORE is
- * set
- */
+ /* Redirect stdin, stdout and stderr to /dev/null even if UV_IGNORE is
+ * set. */
+ uv__close_nocheckstdio(fd); /* Free up fd, if it happens to be open. */
use_fd = open("/dev/null", fd == 0 ? O_RDONLY : O_RDWR);
close_fd = use_fd;
@@ -280,30 +294,26 @@ static void uv__process_child_init(const uv_process_options_t* options,
}
}
- if (fd == use_fd)
- uv__cloexec_fcntl(use_fd, 0);
- else
+ if (fd == use_fd) {
+ if (close_fd == -1)
+ uv__cloexec_fcntl(use_fd, 0);
+ }
+ else {
fd = dup2(use_fd, fd);
+ }
if (fd == -1) {
uv__write_int(error_fd, UV__ERR(errno));
_exit(127);
}
- if (fd <= 2)
+ if (fd <= 2 && close_fd == -1)
uv__nonblock_fcntl(fd, 0);
if (close_fd >= stdio_count)
uv__close(close_fd);
}
- for (fd = 0; fd < stdio_count; fd++) {
- use_fd = pipes[fd][1];
-
- if (use_fd >= stdio_count)
- uv__close(use_fd);
- }
-
if (options->cwd != NULL && chdir(options->cwd)) {
uv__write_int(error_fd, UV__ERR(errno));
_exit(127);
@@ -330,9 +340,8 @@ static void uv__process_child_init(const uv_process_options_t* options,
_exit(127);
}
- if (options->env != NULL) {
+ if (options->env != NULL)
environ = options->env;
- }
/* Reset signal mask just before exec. */
sigemptyset(&signewset);
@@ -348,6 +357,562 @@ static void uv__process_child_init(const uv_process_options_t* options,
#endif
+#if defined(__APPLE__)
+typedef struct uv__posix_spawn_fncs_tag {
+ struct {
+ int (*addchdir_np)(const posix_spawn_file_actions_t *, const char *);
+ } file_actions;
+} uv__posix_spawn_fncs_t;
+
+
+static uv_once_t posix_spawn_init_once = UV_ONCE_INIT;
+static uv__posix_spawn_fncs_t posix_spawn_fncs;
+static int posix_spawn_can_use_setsid;
+
+
+static void uv__spawn_init_posix_spawn_fncs(void) {
+ /* Try to locate all non-portable functions at runtime */
+ posix_spawn_fncs.file_actions.addchdir_np =
+ dlsym(RTLD_DEFAULT, "posix_spawn_file_actions_addchdir_np");
+}
+
+
+static void uv__spawn_init_can_use_setsid(void) {
+ static const int MACOS_CATALINA_VERSION_MAJOR = 19;
+ char version_str[256];
+ char* version_major_str;
+ size_t version_str_size = 256;
+ int r;
+ int version_major;
+
+ /* Get a version string */
+ r = sysctlbyname("kern.osrelease", version_str, &version_str_size, NULL, 0);
+ if (r != 0)
+ return;
+
+ /* Try to get the major version number. If not found
+ * fall back to the fork/exec flow */
+ version_major_str = strtok(version_str, ".");
+ if (version_major_str == NULL)
+ return;
+
+ /* Parse the version major as a number. If it is greater than
+ * the major version for macOS Catalina (aka macOS 10.15), then
+ * the POSIX_SPAWN_SETSID flag is available */
+ version_major = atoi_l(version_major_str, NULL); /* Use LC_C_LOCALE */
+ if (version_major >= MACOS_CATALINA_VERSION_MAJOR)
+ posix_spawn_can_use_setsid = 1;
+}
+
+
+static void uv__spawn_init_posix_spawn(void) {
+ /* Init handles to all potentially non-defined functions */
+ uv__spawn_init_posix_spawn_fncs();
+
+ /* Init feature detection for POSIX_SPAWN_SETSID flag */
+ uv__spawn_init_can_use_setsid();
+}
+
+
+static int uv__spawn_set_posix_spawn_attrs(
+ posix_spawnattr_t* attrs,
+ const uv__posix_spawn_fncs_t* posix_spawn_fncs,
+ const uv_process_options_t* options) {
+ int err;
+ unsigned int flags;
+ sigset_t signal_set;
+
+ err = posix_spawnattr_init(attrs);
+ if (err != 0) {
+ /* If initialization fails, no need to de-init, just return */
+ return err;
+ }
+
+ if (options->flags & (UV_PROCESS_SETUID | UV_PROCESS_SETGID)) {
+ /* kauth_cred_issuser currently requires exactly uid == 0 for these
+ * posixspawn_attrs (set_groups_np, setuid_np, setgid_np), which deviates
+ * from the normal specification of setuid (which also uses euid), and they
+ * are also undocumented syscalls, so we do not use them. */
+ err = ENOSYS;
+ goto error;
+ }
+
+ /* 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) {
+ /* If running on a version of macOS where this flag is not supported,
+ * revert back to the fork/exec flow. Otherwise posix_spawn will
+ * silently ignore the flag. */
+ if (!posix_spawn_can_use_setsid) {
+ err = ENOSYS;
+ goto error;
+ }
+
+ flags |= POSIX_SPAWN_SETSID;
+ }
+ err = posix_spawnattr_setflags(attrs, flags);
+ if (err != 0)
+ goto error;
+
+ /* Reset all signal the child to their default behavior */
+ sigfillset(&signal_set);
+ err = posix_spawnattr_setsigdefault(attrs, &signal_set);
+ if (err != 0)
+ goto error;
+
+ /* Reset the signal mask for all signals */
+ sigemptyset(&signal_set);
+ err = posix_spawnattr_setsigmask(attrs, &signal_set);
+ if (err != 0)
+ goto error;
+
+ return err;
+
+error:
+ (void) posix_spawnattr_destroy(attrs);
+ return err;
+}
+
+
+static int uv__spawn_set_posix_spawn_file_actions(
+ posix_spawn_file_actions_t* actions,
+ const uv__posix_spawn_fncs_t* posix_spawn_fncs,
+ const uv_process_options_t* options,
+ int stdio_count,
+ int (*pipes)[2]) {
+ int fd;
+ int fd2;
+ int use_fd;
+ int err;
+
+ err = posix_spawn_file_actions_init(actions);
+ if (err != 0) {
+ /* If initialization fails, no need to de-init, just return */
+ return err;
+ }
+
+ /* Set the current working directory if requested */
+ if (options->cwd != NULL) {
+ if (posix_spawn_fncs->file_actions.addchdir_np == NULL) {
+ err = ENOSYS;
+ goto error;
+ }
+
+ err = posix_spawn_fncs->file_actions.addchdir_np(actions, options->cwd);
+ if (err != 0)
+ goto error;
+ }
+
+ /* Do not return ENOSYS after this point, as we may mutate pipes. */
+
+ /* First duplicate low numbered fds, since it's not safe to duplicate them,
+ * they could get replaced. Example: swapping stdout and stderr; without
+ * this fd 2 (stderr) would be duplicated into fd 1, thus making both
+ * stdout and stderr go to the same fd, which was not the intention. */
+ for (fd = 0; fd < stdio_count; fd++) {
+ use_fd = pipes[fd][1];
+ if (use_fd < 0 || use_fd >= fd)
+ continue;
+ use_fd = stdio_count;
+ for (fd2 = 0; fd2 < stdio_count; fd2++) {
+ /* If we were not setting POSIX_SPAWN_CLOEXEC_DEFAULT, we would need to
+ * also consider whether fcntl(fd, F_GETFD) returned without the
+ * FD_CLOEXEC flag set. */
+ if (pipes[fd2][1] == use_fd) {
+ use_fd++;
+ fd2 = 0;
+ }
+ }
+ err = posix_spawn_file_actions_adddup2(
+ actions,
+ pipes[fd][1],
+ use_fd);
+ assert(err != ENOSYS);
+ if (err != 0)
+ goto error;
+ pipes[fd][1] = use_fd;
+ }
+
+ /* Second, move the descriptors into their respective places */
+ for (fd = 0; fd < stdio_count; fd++) {
+ use_fd = pipes[fd][1];
+ if (use_fd < 0) {
+ if (fd >= 3)
+ continue;
+ else {
+ /* If ignored, redirect to (or from) /dev/null, */
+ err = posix_spawn_file_actions_addopen(
+ actions,
+ fd,
+ "/dev/null",
+ fd == 0 ? O_RDONLY : O_RDWR,
+ 0);
+ assert(err != ENOSYS);
+ if (err != 0)
+ goto error;
+ continue;
+ }
+ }
+
+ if (fd == use_fd)
+ err = posix_spawn_file_actions_addinherit_np(actions, fd);
+ else
+ err = posix_spawn_file_actions_adddup2(actions, use_fd, fd);
+ assert(err != ENOSYS);
+ if (err != 0)
+ goto error;
+
+ /* Make sure the fd is marked as non-blocking (state shared between child
+ * and parent). */
+ uv__nonblock_fcntl(use_fd, 0);
+ }
+
+ /* Finally, close all the superfluous descriptors */
+ for (fd = 0; fd < stdio_count; fd++) {
+ use_fd = pipes[fd][1];
+ if (use_fd < stdio_count)
+ continue;
+
+ /* Check if we already closed this. */
+ for (fd2 = 0; fd2 < fd; fd2++) {
+ if (pipes[fd2][1] == use_fd)
+ break;
+ }
+ if (fd2 < fd)
+ continue;
+
+ err = posix_spawn_file_actions_addclose(actions, use_fd);
+ assert(err != ENOSYS);
+ if (err != 0)
+ goto error;
+ }
+
+ return 0;
+
+error:
+ (void) posix_spawn_file_actions_destroy(actions);
+ return err;
+}
+
+char* uv__spawn_find_path_in_env(char** env) {
+ char** env_iterator;
+ const char path_var[] = "PATH=";
+
+ /* Look for an environment variable called PATH in the
+ * provided env array, and return its value if found */
+ for (env_iterator = env; *env_iterator != NULL; env_iterator++) {
+ if (strncmp(*env_iterator, path_var, sizeof(path_var) - 1) == 0) {
+ /* Found "PATH=" at the beginning of the string */
+ return *env_iterator + sizeof(path_var) - 1;
+ }
+ }
+
+ return NULL;
+}
+
+
+static int uv__spawn_resolve_and_spawn(const uv_process_options_t* options,
+ posix_spawnattr_t* attrs,
+ posix_spawn_file_actions_t* actions,
+ pid_t* pid) {
+ const char *p;
+ const char *z;
+ const char *path;
+ size_t l;
+ size_t k;
+ int err;
+ int seen_eacces;
+
+ path = NULL;
+ err = -1;
+ seen_eacces = 0;
+
+ /* Short circuit for erroneous case */
+ if (options->file == NULL)
+ return ENOENT;
+
+ /* The environment for the child process is that of the parent unless overriden
+ * by options->env */
+ char** env = environ;
+ if (options->env != NULL)
+ env = options->env;
+
+ /* If options->file contains a slash, posix_spawn/posix_spawnp behave
+ * the same, and don't involve PATH resolution at all. Otherwise, if
+ * options->file does not include a slash, but no custom environment is
+ * to be used, the environment used for path resolution as well for the
+ * child process is that of the parent process, so posix_spawnp is the
+ * way to go. */
+ if (strchr(options->file, '/') != NULL || options->env == NULL) {
+ do
+ err = posix_spawnp(pid, options->file, actions, attrs, options->args, env);
+ while (err == EINTR);
+ return err;
+ }
+
+ /* Look for the definition of PATH in the provided env */
+ path = uv__spawn_find_path_in_env(options->env);
+
+ /* The following resolution logic (execvpe emulation) is copied from
+ * https://git.musl-libc.org/cgit/musl/tree/src/process/execvp.c
+ * and adapted to work for our specific usage */
+
+ /* If no path was provided in options->env, use the default value
+ * to look for the executable */
+ if (path == NULL)
+ path = _PATH_DEFPATH;
+
+ k = strnlen(options->file, NAME_MAX + 1);
+ if (k > NAME_MAX)
+ return ENAMETOOLONG;
+
+ l = strnlen(path, PATH_MAX - 1) + 1;
+
+ for (p = path;; p = z) {
+ /* Compose the new process file from the entry in the PATH
+ * environment variable and the actual file name */
+ char b[PATH_MAX + NAME_MAX];
+ z = strchr(p, ':');
+ if (!z)
+ z = p + strlen(p);
+ if ((size_t)(z - p) >= l) {
+ if (!*z++)
+ break;
+
+ continue;
+ }
+ memcpy(b, p, z - p);
+ b[z - p] = '/';
+ memcpy(b + (z - p) + (z > p), options->file, k + 1);
+
+ /* Try to spawn the new process file. If it fails with ENOENT, the
+ * new process file is not in this PATH entry, continue with the next
+ * PATH entry. */
+ do
+ err = posix_spawn(pid, b, actions, attrs, options->args, env);
+ while (err == EINTR);
+
+ switch (err) {
+ case EACCES:
+ seen_eacces = 1;
+ break; /* continue search */
+ case ENOENT:
+ case ENOTDIR:
+ break; /* continue search */
+ default:
+ return err;
+ }
+
+ if (!*z++)
+ break;
+ }
+
+ if (seen_eacces)
+ return EACCES;
+ return err;
+}
+
+
+static int uv__spawn_and_init_child_posix_spawn(
+ const uv_process_options_t* options,
+ int stdio_count,
+ int (*pipes)[2],
+ pid_t* pid,
+ const uv__posix_spawn_fncs_t* posix_spawn_fncs) {
+ int err;
+ posix_spawnattr_t attrs;
+ posix_spawn_file_actions_t actions;
+
+ err = uv__spawn_set_posix_spawn_attrs(&attrs, posix_spawn_fncs, options);
+ if (err != 0)
+ goto error;
+
+ /* This may mutate pipes. */
+ err = uv__spawn_set_posix_spawn_file_actions(&actions,
+ posix_spawn_fncs,
+ options,
+ stdio_count,
+ pipes);
+ if (err != 0) {
+ (void) posix_spawnattr_destroy(&attrs);
+ goto error;
+ }
+
+ /* Try to spawn options->file resolving in the provided environment
+ * if any */
+ err = uv__spawn_resolve_and_spawn(options, &attrs, &actions, pid);
+ assert(err != ENOSYS);
+
+ /* Destroy the actions/attributes */
+ (void) posix_spawn_file_actions_destroy(&actions);
+ (void) posix_spawnattr_destroy(&attrs);
+
+error:
+ /* In an error situation, the attributes and file actions are
+ * already destroyed, only the happy path requires cleanup */
+ return UV__ERR(err);
+}
+#endif
+
+static 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) {
+ sigset_t signewset;
+ sigset_t sigoldset;
+
+ /* Start the child with most signals blocked, to avoid any issues before we
+ * can reset them, but allow program failures to exit (and not hang). */
+ sigfillset(&signewset);
+ sigdelset(&signewset, SIGKILL);
+ sigdelset(&signewset, SIGSTOP);
+ sigdelset(&signewset, SIGTRAP);
+ sigdelset(&signewset, SIGSEGV);
+ sigdelset(&signewset, SIGBUS);
+ sigdelset(&signewset, SIGILL);
+ sigdelset(&signewset, SIGSYS);
+ sigdelset(&signewset, SIGABRT);
+ if (pthread_sigmask(SIG_BLOCK, &signewset, &sigoldset) != 0)
+ abort();
+
+ *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();
+ }
+
+ if (pthread_sigmask(SIG_SETMASK, &sigoldset, NULL) != 0)
+ abort();
+
+ /* Fork succeeded, in the parent process */
+ return 0;
+}
+
+static int uv__spawn_and_init_child(
+ uv_loop_t* loop,
+ const uv_process_options_t* options,
+ int stdio_count,
+ int (*pipes)[2],
+ pid_t* pid) {
+ int signal_pipe[2] = { -1, -1 };
+ int status;
+ int err;
+ int exec_errorno;
+ ssize_t r;
+
+#if defined(__APPLE__)
+ uv_once(&posix_spawn_init_once, uv__spawn_init_posix_spawn);
+
+ /* Special 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 posix_spawn and its related primitives. It also takes advantage of
+ * the macOS extension POSIX_SPAWN_CLOEXEC_DEFAULT that makes impossible to
+ * leak descriptors to the child process. */
+ err = uv__spawn_and_init_child_posix_spawn(options,
+ stdio_count,
+ pipes,
+ pid,
+ &posix_spawn_fncs);
+
+ /* The posix_spawn flow will return UV_ENOSYS if any of the posix_spawn_x_np
+ * non-standard functions is both _needed_ and _undefined_. In those cases,
+ * default back to the fork/execve strategy. For all other errors, just fail. */
+ if (err != UV_ENOSYS)
+ return err;
+
+#endif
+
+ /* This pipe is used by the parent to wait until
+ * the child has called `execve()`. We need this
+ * to avoid the following race condition:
+ *
+ * if ((pid = fork()) > 0) {
+ * kill(pid, SIGTERM);
+ * }
+ * else if (pid == 0) {
+ * execve("/bin/cat", argp, envp);
+ * }
+ *
+ * The parent sends a signal immediately after forking.
+ * Since the child may not have called `execve()` yet,
+ * there is no telling what process receives the signal,
+ * our fork or /bin/cat.
+ *
+ * To avoid ambiguity, we create a pipe with both ends
+ * marked close-on-exec. Then, after the call to `fork()`,
+ * the parent polls the read end until it EOFs or errors with EPIPE.
+ */
+ err = uv__make_pipe(signal_pipe, 0);
+ if (err)
+ return err;
+
+ /* Acquire write lock to prevent opening new fds in worker threads */
+ uv_rwlock_wrlock(&loop->cloexec_lock);
+
+ err = uv__spawn_and_init_child_fork(options, stdio_count, pipes, signal_pipe[1], pid);
+
+ /* Release lock in parent process */
+ uv_rwlock_wrunlock(&loop->cloexec_lock);
+
+ uv__close(signal_pipe[1]);
+
+ if (err == 0) {
+ do
+ r = read(signal_pipe[0], &exec_errorno, sizeof(exec_errorno));
+ while (r == -1 && errno == EINTR);
+
+ if (r == 0)
+ ; /* okay, EOF */
+ else if (r == sizeof(exec_errorno)) {
+ do
+ err = waitpid(*pid, &status, 0); /* okay, read errorno */
+ while (err == -1 && errno == EINTR);
+ assert(err == *pid);
+ err = exec_errorno;
+ } else if (r == -1 && errno == EPIPE) {
+ /* Something unknown happened to our child before spawn */
+ do
+ err = waitpid(*pid, &status, 0); /* okay, got EPIPE */
+ while (err == -1 && errno == EINTR);
+ assert(err == *pid);
+ err = UV_EPIPE;
+ } else
+ abort();
+ }
+
+ uv__close_nocheckstdio(signal_pipe[0]);
+
+ return err;
+}
+
int uv_spawn(uv_loop_t* loop,
uv_process_t* process,
const uv_process_options_t* options) {
@@ -355,18 +920,13 @@ int uv_spawn(uv_loop_t* loop,
/* fork is marked __WATCHOS_PROHIBITED __TVOS_PROHIBITED. */
return UV_ENOSYS;
#else
- sigset_t signewset;
- sigset_t sigoldset;
- int signal_pipe[2] = { -1, -1 };
int pipes_storage[8][2];
int (*pipes)[2];
int stdio_count;
- ssize_t r;
pid_t pid;
int err;
int exec_errorno;
int i;
- int status;
assert(options->file != NULL);
assert(!(options->flags & ~(UV_PROCESS_DETACHED |
@@ -379,6 +939,7 @@ int uv_spawn(uv_loop_t* loop,
uv__handle_init(loop, (uv_handle_t*)process, UV_PROCESS);
QUEUE_INIT(&process->queue);
+ process->status = 0;
stdio_count = options->stdio_count;
if (stdio_count < 3)
@@ -403,106 +964,24 @@ int uv_spawn(uv_loop_t* loop,
goto error;
}
- /* This pipe is used by the parent to wait until
- * the child has called `execve()`. We need this
- * to avoid the following race condition:
- *
- * if ((pid = fork()) > 0) {
- * kill(pid, SIGTERM);
- * }
- * else if (pid == 0) {
- * execve("/bin/cat", argp, envp);
- * }
- *
- * The parent sends a signal immediately after forking.
- * Since the child may not have called `execve()` yet,
- * there is no telling what process receives the signal,
- * our fork or /bin/cat.
- *
- * To avoid ambiguity, we create a pipe with both ends
- * marked close-on-exec. Then, after the call to `fork()`,
- * the parent polls the read end until it EOFs or errors with EPIPE.
- */
- err = uv__make_pipe(signal_pipe, 0);
- if (err)
- goto error;
-
#if !(defined(__APPLE__) || defined(__DragonFly__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__))
uv_signal_start(&loop->child_watcher, uv__chld, SIGCHLD);
#endif
- /* Acquire write lock to prevent opening new fds in worker threads */
- uv_rwlock_wrlock(&loop->cloexec_lock);
-
- /* Start the child with most signals blocked, to avoid any issues before we
- * can reset them, but allow program failures to exit (and not hang). */
- sigfillset(&signewset);
- sigdelset(&signewset, SIGKILL);
- sigdelset(&signewset, SIGSTOP);
- sigdelset(&signewset, SIGTRAP);
- sigdelset(&signewset, SIGSEGV);
- sigdelset(&signewset, SIGBUS);
- sigdelset(&signewset, SIGILL);
- sigdelset(&signewset, SIGSYS);
- sigdelset(&signewset, SIGABRT);
- if (pthread_sigmask(SIG_BLOCK, &signewset, &sigoldset) != 0)
- abort();
-
- pid = fork();
- if (pid == -1)
- err = UV__ERR(errno);
-
- if (pid == 0)
- uv__process_child_init(options, stdio_count, pipes, signal_pipe[1]);
+ /* Spawn the child */
+ exec_errorno = uv__spawn_and_init_child(loop, options, stdio_count, pipes, &pid);
- if (pthread_sigmask(SIG_SETMASK, &sigoldset, NULL) != 0)
- abort();
-
- /* Release lock in parent process */
- uv_rwlock_wrunlock(&loop->cloexec_lock);
-
- uv__close(signal_pipe[1]);
-
- if (pid == -1) {
- uv__close(signal_pipe[0]);
- goto error;
- }
-
- process->status = 0;
- exec_errorno = 0;
- do
- r = read(signal_pipe[0], &exec_errorno, sizeof(exec_errorno));
- while (r == -1 && errno == EINTR);
-
- if (r == 0)
- ; /* okay, EOF */
- else if (r == sizeof(exec_errorno)) {
- do
- err = waitpid(pid, &status, 0); /* okay, read errorno */
- while (err == -1 && errno == EINTR);
- assert(err == pid);
- } else if (r == -1 && errno == EPIPE) {
- do
- err = waitpid(pid, &status, 0); /* okay, got EPIPE */
- while (err == -1 && errno == EINTR);
- assert(err == pid);
- } else
- abort();
-
- uv__close_nocheckstdio(signal_pipe[0]);
-
- for (i = 0; i < options->stdio_count; i++) {
- err = uv__process_open_stream(options->stdio + i, pipes[i]);
- if (err == 0)
- continue;
-
- while (i--)
- uv__process_close_stream(options->stdio + i);
-
- goto error;
- }
+#if 0
+ /* This runs into a nodejs issue (it expects initialized streams, even if the
+ * exec failed).
+ * See https://github.com/libuv/libuv/pull/3107#issuecomment-782482608 */
+ if (exec_errorno != 0)
+ goto error;
+#endif
- /* Only activate this handle if exec() happened successfully */
+ /* Activate this handle if exec() happened successfully, even if we later
+ * fail to open a stdio handle. This ensures we can eventually reap the child
+ * with waitpid. */
if (exec_errorno == 0) {
#if defined(__APPLE__) || defined(__DragonFly__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__)
struct kevent event;
@@ -515,12 +994,22 @@ int uv_spawn(uv_loop_t* loop,
}
#endif
+ process->pid = pid;
+ process->exit_cb = options->exit_cb;
QUEUE_INSERT_TAIL(&loop->process_handles, &process->queue);
uv__handle_start(process);
}
- process->pid = pid;
- process->exit_cb = options->exit_cb;
+ for (i = 0; i < options->stdio_count; i++) {
+ err = uv__process_open_stream(options->stdio + i, pipes[i]);
+ if (err == 0)
+ continue;
+
+ while (i--)
+ uv__process_close_stream(options->stdio + i);
+
+ goto error;
+ }
if (pipes != pipes_storage)
uv__free(pipes);

View File

@@ -0,0 +1,175 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Jameson Nash <vtjnash@gmail.com>
Date: Thu, 29 Jul 2021 12:09:51 -0400
Subject: unix: protect fork in uv_spawn from signals
Years ago, we found that various kernels (linux, macOS) were known to
fail if they try to deliver a signal during this syscall, so we prevent
that from happening. They may have fixed those issues, but it is
generally just a bad time for signals to arrive (glibc blocks them here,
for example, including some more internal ones that it won't let us
touch here).
We try to be a bit conservative, and leave many signals unblocked which
could happen during normal execution and should terminate the process if
they do. There is a small race window after the child starts before we
clear the old handlers, if the user was to send an fake signal from
elsewhere, but that should be quite unlikely.
PR-URL: https://github.com/libuv/libuv/pull/3251
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
diff --git a/deps/uv/src/unix/process.c b/deps/uv/src/unix/process.c
index 8f94c53b249978c8ce41dc2447facbbcf585120a..a72660ec1c4e7a2734cc73da3766aa620099512f 100644
--- a/deps/uv/src/unix/process.c
+++ b/deps/uv/src/unix/process.c
@@ -26,6 +26,7 @@
#include <stdlib.h>
#include <assert.h>
#include <errno.h>
+#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
@@ -206,13 +207,33 @@ static void uv__process_child_init(const uv_process_options_t* options,
int stdio_count,
int (*pipes)[2],
int error_fd) {
- sigset_t set;
+ sigset_t signewset;
int close_fd;
int use_fd;
- int err;
int fd;
int n;
+ /* Reset signal disposition first. Use a hard-coded limit because NSIG is not
+ * fixed on Linux: it's either 32, 34 or 64, depending on whether RT signals
+ * are enabled. We are not allowed to touch RT signal handlers, glibc uses
+ * them internally.
+ */
+ for (n = 1; n < 32; n += 1) {
+ if (n == SIGKILL || n == SIGSTOP)
+ continue; /* Can't be changed. */
+
+#if defined(__HAIKU__)
+ if (n == SIGKILLTHR)
+ continue; /* Can't be changed. */
+#endif
+
+ if (SIG_ERR != signal(n, SIG_DFL))
+ continue;
+
+ uv__write_int(error_fd, UV__ERR(errno));
+ _exit(127);
+ }
+
if (options->flags & UV_PROCESS_DETACHED)
setsid();
@@ -306,39 +327,16 @@ static void uv__process_child_init(const uv_process_options_t* options,
environ = options->env;
}
- /* Reset signal disposition. Use a hard-coded limit because NSIG
- * is not fixed on Linux: it's either 32, 34 or 64, depending on
- * whether RT signals are enabled. We are not allowed to touch
- * RT signal handlers, glibc uses them internally.
- */
- for (n = 1; n < 32; n += 1) {
- if (n == SIGKILL || n == SIGSTOP)
- continue; /* Can't be changed. */
-
-#if defined(__HAIKU__)
- if (n == SIGKILLTHR)
- continue; /* Can't be changed. */
-#endif
-
- if (SIG_ERR != signal(n, SIG_DFL))
- continue;
-
- uv__write_int(error_fd, UV__ERR(errno));
- _exit(127);
- }
-
- /* Reset signal mask. */
- sigemptyset(&set);
- err = pthread_sigmask(SIG_SETMASK, &set, NULL);
-
- if (err != 0) {
- uv__write_int(error_fd, UV__ERR(err));
- _exit(127);
- }
+ /* Reset signal mask just before exec. */
+ sigemptyset(&signewset);
+ if (sigprocmask(SIG_SETMASK, &signewset, NULL) != 0)
+ abort();
execvp(options->file, options->args);
+
uv__write_int(error_fd, UV__ERR(errno));
_exit(127);
+ abort();
}
#endif
@@ -350,6 +348,8 @@ int uv_spawn(uv_loop_t* loop,
/* fork is marked __WATCHOS_PROHIBITED __TVOS_PROHIBITED. */
return UV_ENOSYS;
#else
+ sigset_t signewset;
+ sigset_t sigoldset;
int signal_pipe[2] = { -1, -1 };
int pipes_storage[8][2];
int (*pipes)[2];
@@ -424,25 +424,41 @@ 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) {
+ /* Start the child with most signals blocked, to avoid any issues before we
+ * can reset them, but allow program failures to exit (and not hang). */
+ sigfillset(&signewset);
+ sigdelset(&signewset, SIGKILL);
+ sigdelset(&signewset, SIGSTOP);
+ sigdelset(&signewset, SIGTRAP);
+ sigdelset(&signewset, SIGSEGV);
+ sigdelset(&signewset, SIGBUS);
+ sigdelset(&signewset, SIGILL);
+ sigdelset(&signewset, SIGSYS);
+ sigdelset(&signewset, SIGABRT);
+ if (pthread_sigmask(SIG_BLOCK, &signewset, &sigoldset) != 0)
+ abort();
+
+ pid = fork();
+ if (pid == -1)
err = UV__ERR(errno);
- uv_rwlock_wrunlock(&loop->cloexec_lock);
- uv__close(signal_pipe[0]);
- uv__close(signal_pipe[1]);
- goto error;
- }
- if (pid == 0) {
+ if (pid == 0)
uv__process_child_init(options, stdio_count, pipes, signal_pipe[1]);
+
+ if (pthread_sigmask(SIG_SETMASK, &sigoldset, NULL) != 0)
abort();
- }
/* Release lock in parent process */
uv_rwlock_wrunlock(&loop->cloexec_lock);
+
uv__close(signal_pipe[1]);
+ if (pid == -1) {
+ uv__close(signal_pipe[0]);
+ goto error;
+ }
+
process->status = 0;
exec_errorno = 0;
do

View File

@@ -0,0 +1,92 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Jameson Nash <vtjnash@gmail.com>
Date: Sun, 6 Mar 2022 15:01:33 -0500
Subject: unix: remove uv__cloexec_ioctl() (#3515)
Now that uv__cloexec_fcntl() is simplified
(https://github.com/libuv/libuv/pull/3492), there is no benefit to
maintaining duplicate code paths for the same thing.
diff --git a/deps/uv/src/unix/core.c b/deps/uv/src/unix/core.c
index afa18856700a987a9b6cee0273572ae1bb2451fc..18e5cc19593bbf40ef6a2d4ced363de48da43992 100644
--- a/deps/uv/src/unix/core.c
+++ b/deps/uv/src/unix/core.c
@@ -589,22 +589,6 @@ int uv__nonblock_ioctl(int fd, int set) {
}
-#if !defined(__CYGWIN__) && !defined(__MSYS__) && !defined(__HAIKU__)
-int uv__cloexec_ioctl(int fd, int set) {
- int r;
-
- do
- r = ioctl(fd, set ? FIOCLEX : FIONCLEX);
- while (r == -1 && errno == EINTR);
-
- if (r)
- return UV__ERR(errno);
-
- return 0;
-}
-#endif
-
-
int uv__nonblock_fcntl(int fd, int set) {
int flags;
int r;
@@ -636,7 +620,7 @@ int uv__nonblock_fcntl(int fd, int set) {
}
-int uv__cloexec_fcntl(int fd, int set) {
+int uv__cloexec(int fd, int set) {
int flags;
int r;
diff --git a/deps/uv/src/unix/internal.h b/deps/uv/src/unix/internal.h
index 5bc9f36037594006e204dee31bc7d969bbf650d4..31c6d03bfff05559e23612865ea4ec74334a90ab 100644
--- a/deps/uv/src/unix/internal.h
+++ b/deps/uv/src/unix/internal.h
@@ -164,10 +164,8 @@ struct uv__stream_queued_fds_s {
defined(__linux__) || \
defined(__OpenBSD__) || \
defined(__NetBSD__)
-#define uv__cloexec uv__cloexec_ioctl
#define uv__nonblock uv__nonblock_ioctl
#else
-#define uv__cloexec uv__cloexec_fcntl
#define uv__nonblock uv__nonblock_fcntl
#endif
@@ -184,8 +182,7 @@ struct uv__stream_queued_fds_s {
#endif
/* core */
-int uv__cloexec_ioctl(int fd, int set);
-int uv__cloexec_fcntl(int fd, int set);
+int uv__cloexec(int fd, int set);
int uv__nonblock_ioctl(int fd, int set);
int uv__nonblock_fcntl(int fd, int set);
int uv__close(int fd); /* preserves errno */
diff --git a/deps/uv/src/unix/process.c b/deps/uv/src/unix/process.c
index 44d9af9a17626a2f85e12f4983b0540b5993e290..ef9db6cdb8bdd9867cfdc3e9a29573ea7b9a560a 100644
--- a/deps/uv/src/unix/process.c
+++ b/deps/uv/src/unix/process.c
@@ -276,7 +276,7 @@ static void uv__process_child_init(const uv_process_options_t* options,
_exit(127);
}
#ifndef F_DUPFD_CLOEXEC /* POSIX 2008 */
- n = uv__cloexec_fcntl(pipes[fd][1], 1);
+ n = uv__cloexec(pipes[fd][1], 1);
if (n) {
uv__write_int(error_fd, n);
_exit(127);
@@ -307,7 +307,7 @@ static void uv__process_child_init(const uv_process_options_t* options,
if (fd == use_fd) {
if (close_fd == -1) {
- n = uv__cloexec_fcntl(use_fd, 0);
+ n = uv__cloexec(use_fd, 0);
if (n) {
uv__write_int(error_fd, n);
_exit(127);

View File

@@ -0,0 +1,36 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Ben Noordhuis <info@bnoordhuis.nl>
Date: Sat, 5 Mar 2022 18:55:49 +0100
Subject: unix: simplify uv__cloexec_fcntl() (#3492)
FD_CLOEXEC is the only defined flag for fcntl(F_SETFD) so don't bother
getting the status of that flag first with fcntl(F_GETFD), just set it.
diff --git a/deps/uv/src/unix/core.c b/deps/uv/src/unix/core.c
index 2811f063eef7329f87fbe43b28729c6b25f0a75b..afa18856700a987a9b6cee0273572ae1bb2451fc 100644
--- a/deps/uv/src/unix/core.c
+++ b/deps/uv/src/unix/core.c
@@ -640,21 +640,9 @@ int uv__cloexec_fcntl(int fd, int set) {
int flags;
int r;
- do
- r = fcntl(fd, F_GETFD);
- while (r == -1 && errno == EINTR);
-
- if (r == -1)
- return UV__ERR(errno);
-
- /* Bail out now if already set/clear. */
- if (!!(r & FD_CLOEXEC) == !!set)
- return 0;
-
+ flags = 0;
if (set)
- flags = r | FD_CLOEXEC;
- else
- flags = r & ~FD_CLOEXEC;
+ flags = FD_CLOEXEC;
do
r = fcntl(fd, F_SETFD, flags);