summaryrefslogtreecommitdiffstats
path: root/contrib/tools/python3/src/Modules/_posixsubprocess.c
diff options
context:
space:
mode:
authorshadchin <[email protected]>2022-04-18 12:39:32 +0300
committershadchin <[email protected]>2022-04-18 12:39:32 +0300
commitd4be68e361f4258cf0848fc70018dfe37a2acc24 (patch)
tree153e294cd97ac8b5d7a989612704a0c1f58e8ad4 /contrib/tools/python3/src/Modules/_posixsubprocess.c
parent260c02f5ccf242d9d9b8a873afaf6588c00237d6 (diff)
IGNIETFERRO-1816 Update Python 3 from 3.9.12 to 3.10.4
ref:9f96be6d02ee8044fdd6f124b799b270c20ce641
Diffstat (limited to 'contrib/tools/python3/src/Modules/_posixsubprocess.c')
-rw-r--r--contrib/tools/python3/src/Modules/_posixsubprocess.c408
1 files changed, 232 insertions, 176 deletions
diff --git a/contrib/tools/python3/src/Modules/_posixsubprocess.c b/contrib/tools/python3/src/Modules/_posixsubprocess.c
index d64e0a1cfa0..a58159a277b 100644
--- a/contrib/tools/python3/src/Modules/_posixsubprocess.c
+++ b/contrib/tools/python3/src/Modules/_posixsubprocess.c
@@ -1,5 +1,6 @@
/* Authors: Gregory P. Smith & Jeffrey Yasskin */
#include "Python.h"
+#include "pycore_fileutils.h"
#if defined(HAVE_PIPE2) && !defined(_GNU_SOURCE)
# define _GNU_SOURCE
#endif
@@ -35,6 +36,14 @@
# define SYS_getdents64 __NR_getdents64
#endif
+#if defined(__linux__) && defined(HAVE_VFORK) && defined(HAVE_SIGNAL_H) && \
+ defined(HAVE_PTHREAD_SIGMASK) && !defined(HAVE_BROKEN_PTHREAD_SIGMASK)
+/* If this is ever expanded to non-Linux platforms, verify what calls are
+ * allowed after vfork(). Ex: setsid() may be disallowed on macOS? */
+# include <signal.h>
+# define VFORK_USABLE 1
+#endif
+
#if defined(__sun) && defined(__SVR4)
/* readdir64 is used to work around Solaris 9 bug 6395699. */
# define readdir readdir64
@@ -60,47 +69,8 @@
#define POSIX_CALL(call) do { if ((call) == -1) goto error; } while (0)
-typedef struct {
- PyObject* disable;
- PyObject* enable;
- PyObject* isenabled;
-} _posixsubprocessstate;
-
static struct PyModuleDef _posixsubprocessmodule;
-static inline _posixsubprocessstate*
-get_posixsubprocess_state(PyObject *module)
-{
- void *state = PyModule_GetState(module);
- assert(state != NULL);
- return (_posixsubprocessstate *)state;
-}
-
-#define _posixsubprocessstate_global get_posixsubprocess_state(PyState_FindModule(&_posixsubprocessmodule))
-
-/* If gc was disabled, call gc.enable(). Return 0 on success. */
-static int
-_enable_gc(int need_to_reenable_gc, PyObject *gc_module)
-{
- PyObject *result;
- PyObject *exctype, *val, *tb;
-
- if (need_to_reenable_gc) {
- PyErr_Fetch(&exctype, &val, &tb);
- result = PyObject_CallMethodNoArgs(
- gc_module, _posixsubprocessstate_global->enable);
- if (exctype != NULL) {
- PyErr_Restore(exctype, val, tb);
- }
- if (result == NULL) {
- return 1;
- }
- Py_DECREF(result);
- }
- return 0;
-}
-
-
/* Convert ASCII to a positive int, no libc call. no overflow. -1 on error. */
static int
_pos_int_from_ascii(const char *name)
@@ -250,7 +220,6 @@ _close_fds_by_brute_force(long start_fd, PyObject *py_fds_to_keep)
long end_fd = safe_get_max_fd();
Py_ssize_t num_fds_to_keep = PyTuple_GET_SIZE(py_fds_to_keep);
Py_ssize_t keep_seq_idx;
- int fd_num;
/* As py_fds_to_keep is sorted we can loop through the list closing
* fds in between any in the keep list falling within our range. */
for (keep_seq_idx = 0; keep_seq_idx < num_fds_to_keep; ++keep_seq_idx) {
@@ -258,21 +227,11 @@ _close_fds_by_brute_force(long start_fd, PyObject *py_fds_to_keep)
int keep_fd = PyLong_AsLong(py_keep_fd);
if (keep_fd < start_fd)
continue;
- for (fd_num = start_fd; fd_num < keep_fd; ++fd_num) {
- close(fd_num);
- }
+ _Py_closerange(start_fd, keep_fd - 1);
start_fd = keep_fd + 1;
}
if (start_fd <= end_fd) {
-#if defined(__FreeBSD__)
- /* Any errors encountered while closing file descriptors are ignored */
- closefrom(start_fd);
-#else
- for (fd_num = start_fd; fd_num < end_fd; ++fd_num) {
- /* Ignore errors */
- (void)close(fd_num);
- }
-#endif
+ _Py_closerange(start_fd, end_fd);
}
}
@@ -417,9 +376,53 @@ _close_open_fds_maybe_unsafe(long start_fd, PyObject* py_fds_to_keep)
#endif /* else NOT (defined(__linux__) && defined(HAVE_SYS_SYSCALL_H)) */
+#ifdef VFORK_USABLE
+/* Reset dispositions for all signals to SIG_DFL except for ignored
+ * signals. This way we ensure that no signal handlers can run
+ * after we unblock signals in a child created by vfork().
+ */
+static void
+reset_signal_handlers(const sigset_t *child_sigmask)
+{
+ struct sigaction sa_dfl = {.sa_handler = SIG_DFL};
+ for (int sig = 1; sig < _NSIG; sig++) {
+ /* Dispositions for SIGKILL and SIGSTOP can't be changed. */
+ if (sig == SIGKILL || sig == SIGSTOP) {
+ continue;
+ }
+
+ /* There is no need to reset the disposition of signals that will
+ * remain blocked across execve() since the kernel will do it. */
+ if (sigismember(child_sigmask, sig) == 1) {
+ continue;
+ }
+
+ struct sigaction sa;
+ /* C libraries usually return EINVAL for signals used
+ * internally (e.g. for thread cancellation), so simply
+ * skip errors here. */
+ if (sigaction(sig, NULL, &sa) == -1) {
+ continue;
+ }
+
+ /* void *h works as these fields are both pointer types already. */
+ void *h = (sa.sa_flags & SA_SIGINFO ? (void *)sa.sa_sigaction :
+ (void *)sa.sa_handler);
+ if (h == SIG_IGN || h == SIG_DFL) {
+ continue;
+ }
+
+ /* This call can't reasonably fail, but if it does, terminating
+ * the child seems to be too harsh, so ignore errors. */
+ (void) sigaction(sig, &sa_dfl, NULL);
+ }
+}
+#endif /* VFORK_USABLE */
+
+
/*
- * This function is code executed in the child process immediately after fork
- * to set things up and call exec().
+ * This function is code executed in the child process immediately after
+ * (v)fork to set things up and call exec().
*
* All of the code in this function must only use async-signal-safe functions,
* listed at `man 7 signal` or
@@ -427,8 +430,28 @@ _close_open_fds_maybe_unsafe(long start_fd, PyObject* py_fds_to_keep)
*
* This restriction is documented at
* http://www.opengroup.org/onlinepubs/009695399/functions/fork.html.
+ *
+ * If this function is called after vfork(), even more care must be taken.
+ * The lack of preparations that C libraries normally take on fork(),
+ * as well as sharing the address space with the parent, might make even
+ * async-signal-safe functions vfork-unsafe. In particular, on Linux,
+ * set*id() and setgroups() library functions must not be called, since
+ * they have to interact with the library-level thread list and send
+ * library-internal signals to implement per-process credentials semantics
+ * required by POSIX but not supported natively on Linux. Another reason to
+ * avoid this family of functions is that sharing an address space between
+ * processes running with different privileges is inherently insecure.
+ * See bpo-35823 for further discussion and references.
+ *
+ * In some C libraries, setrlimit() has the same thread list/signalling
+ * behavior since resource limits were per-thread attributes before
+ * Linux 2.6.10. Musl, as of 1.2.1, is known to have this issue
+ * (https://www.openwall.com/lists/musl/2020/10/15/6).
+ *
+ * If vfork-unsafe functionality is desired after vfork(), consider using
+ * syscall() to obtain it.
*/
-static void
+_Py_NO_INLINE static void
child_exec(char *const exec_array[],
char *const argv[],
char *const envp[],
@@ -442,6 +465,7 @@ child_exec(char *const exec_array[],
int call_setgid, gid_t gid,
int call_setgroups, size_t groups_size, const gid_t *groups,
int call_setuid, uid_t uid, int child_umask,
+ const void *child_sigmask,
PyObject *py_fds_to_keep,
PyObject *preexec_fn,
PyObject *preexec_fn_args_tuple)
@@ -517,6 +541,15 @@ child_exec(char *const exec_array[],
if (restore_signals)
_Py_RestoreSignals();
+#ifdef VFORK_USABLE
+ if (child_sigmask) {
+ reset_signal_handlers(child_sigmask);
+ if ((errno = pthread_sigmask(SIG_SETMASK, child_sigmask, NULL))) {
+ goto error;
+ }
+ }
+#endif
+
#ifdef HAVE_SETSID
if (call_setsid)
POSIX_CALL(setsid());
@@ -609,8 +642,82 @@ error:
}
+/* The main purpose of this wrapper function is to isolate vfork() from both
+ * subprocess_fork_exec() and child_exec(). A child process created via
+ * vfork() executes on the same stack as the parent process while the latter is
+ * suspended, so this function should not be inlined to avoid compiler bugs
+ * that might clobber data needed by the parent later. Additionally,
+ * child_exec() should not be inlined to avoid spurious -Wclobber warnings from
+ * GCC (see bpo-35823).
+ */
+_Py_NO_INLINE static pid_t
+do_fork_exec(char *const exec_array[],
+ char *const argv[],
+ char *const envp[],
+ const char *cwd,
+ int p2cread, int p2cwrite,
+ int c2pread, int c2pwrite,
+ int errread, int errwrite,
+ int errpipe_read, int errpipe_write,
+ int close_fds, int restore_signals,
+ int call_setsid,
+ int call_setgid, gid_t gid,
+ int call_setgroups, size_t groups_size, const gid_t *groups,
+ int call_setuid, uid_t uid, int child_umask,
+ const void *child_sigmask,
+ PyObject *py_fds_to_keep,
+ PyObject *preexec_fn,
+ PyObject *preexec_fn_args_tuple)
+{
+
+ pid_t pid;
+
+#ifdef VFORK_USABLE
+ if (child_sigmask) {
+ /* These are checked by our caller; verify them in debug builds. */
+ assert(!call_setuid);
+ assert(!call_setgid);
+ assert(!call_setgroups);
+ assert(preexec_fn == Py_None);
+
+ pid = vfork();
+ } else
+#endif
+ {
+ pid = fork();
+ }
+
+ if (pid != 0) {
+ return pid;
+ }
+
+ /* Child process.
+ * See the comment above child_exec() for restrictions imposed on
+ * the code below.
+ */
+
+ if (preexec_fn != Py_None) {
+ /* We'll be calling back into Python later so we need to do this.
+ * This call may not be async-signal-safe but neither is calling
+ * back into Python. The user asked us to use hope as a strategy
+ * to avoid deadlock... */
+ PyOS_AfterFork_Child();
+ }
+
+ child_exec(exec_array, argv, envp, cwd,
+ p2cread, p2cwrite, c2pread, c2pwrite,
+ errread, errwrite, errpipe_read, errpipe_write,
+ close_fds, restore_signals, call_setsid,
+ call_setgid, gid, call_setgroups, groups_size, groups,
+ call_setuid, uid, child_umask, child_sigmask,
+ py_fds_to_keep, preexec_fn, preexec_fn_args_tuple);
+ _exit(255);
+ return 0; /* Dead code to avoid a potential compiler warning. */
+}
+
+
static PyObject *
-subprocess_fork_exec(PyObject* self, PyObject *args)
+subprocess_fork_exec(PyObject *module, PyObject *args)
{
PyObject *gc_module = NULL;
PyObject *executable_list, *py_fds_to_keep;
@@ -628,7 +735,7 @@ subprocess_fork_exec(PyObject* self, PyObject *args)
int child_umask;
PyObject *cwd_obj, *cwd_obj2 = NULL;
const char *cwd;
- pid_t pid;
+ pid_t pid = -1;
int need_to_reenable_gc = 0;
char *const *exec_array, *const *argv = NULL, *const *envp = NULL;
Py_ssize_t arg_num, num_groups = 0;
@@ -673,30 +780,7 @@ subprocess_fork_exec(PyObject* self, PyObject *args)
/* We need to call gc.disable() when we'll be calling preexec_fn */
if (preexec_fn != Py_None) {
- PyObject *result;
-
- gc_module = PyImport_ImportModule("gc");
- if (gc_module == NULL)
- return NULL;
- result = PyObject_CallMethodNoArgs(
- gc_module, _posixsubprocessstate_global->isenabled);
- if (result == NULL) {
- Py_DECREF(gc_module);
- return NULL;
- }
- need_to_reenable_gc = PyObject_IsTrue(result);
- Py_DECREF(result);
- if (need_to_reenable_gc == -1) {
- Py_DECREF(gc_module);
- return NULL;
- }
- result = PyObject_CallMethodNoArgs(
- gc_module, _posixsubprocessstate_global->disable);
- if (result == NULL) {
- Py_DECREF(gc_module);
- return NULL;
- }
- Py_DECREF(result);
+ need_to_reenable_gc = PyGC_Disable();
}
exec_array = _PySequence_BytesToCharpArray(executable_list);
@@ -841,83 +925,92 @@ subprocess_fork_exec(PyObject* self, PyObject *args)
need_after_fork = 1;
}
- pid = fork();
- if (pid == 0) {
- /* Child process */
- /*
- * Code from here to _exit() must only use async-signal-safe functions,
- * listed at `man 7 signal` or
- * http://www.opengroup.org/onlinepubs/009695399/functions/xsh_chap02_04.html.
+ /* NOTE: When old_sigmask is non-NULL, do_fork_exec() may use vfork(). */
+ const void *old_sigmask = NULL;
+#ifdef VFORK_USABLE
+ /* Use vfork() only if it's safe. See the comment above child_exec(). */
+ sigset_t old_sigs;
+ if (preexec_fn == Py_None &&
+ !call_setuid && !call_setgid && !call_setgroups) {
+ /* Block all signals to ensure that no signal handlers are run in the
+ * child process while it shares memory with us. Note that signals
+ * used internally by C libraries won't be blocked by
+ * pthread_sigmask(), but signal handlers installed by C libraries
+ * normally service only signals originating from *within the process*,
+ * so it should be sufficient to consider any library function that
+ * might send such a signal to be vfork-unsafe and do not call it in
+ * the child.
*/
-
- if (preexec_fn != Py_None) {
- /* We'll be calling back into Python later so we need to do this.
- * This call may not be async-signal-safe but neither is calling
- * back into Python. The user asked us to use hope as a strategy
- * to avoid deadlock... */
- PyOS_AfterFork_Child();
+ sigset_t all_sigs;
+ sigfillset(&all_sigs);
+ if ((saved_errno = pthread_sigmask(SIG_BLOCK, &all_sigs, &old_sigs))) {
+ goto cleanup;
}
-
- child_exec(exec_array, argv, envp, cwd,
- p2cread, p2cwrite, c2pread, c2pwrite,
- errread, errwrite, errpipe_read, errpipe_write,
- close_fds, restore_signals, call_setsid,
- call_setgid, gid, call_setgroups, num_groups, groups,
- call_setuid, uid, child_umask,
- py_fds_to_keep, preexec_fn, preexec_fn_args_tuple);
- _exit(255);
- return NULL; /* Dead code to avoid a potential compiler warning. */
+ old_sigmask = &old_sigs;
}
+#endif
+
+ pid = do_fork_exec(exec_array, argv, envp, cwd,
+ p2cread, p2cwrite, c2pread, c2pwrite,
+ errread, errwrite, errpipe_read, errpipe_write,
+ close_fds, restore_signals, call_setsid,
+ call_setgid, gid, call_setgroups, num_groups, groups,
+ call_setuid, uid, child_umask, old_sigmask,
+ py_fds_to_keep, preexec_fn, preexec_fn_args_tuple);
+
/* Parent (original) process */
if (pid == -1) {
/* Capture errno for the exception. */
saved_errno = errno;
}
- Py_XDECREF(cwd_obj2);
+#ifdef VFORK_USABLE
+ if (old_sigmask) {
+ /* vfork() semantics guarantees that the parent is blocked
+ * until the child performs _exit() or execve(), so it is safe
+ * to unblock signals once we're here.
+ * Note that in environments where vfork() is implemented as fork(),
+ * such as QEMU user-mode emulation, the parent won't be blocked,
+ * but it won't share the address space with the child,
+ * so it's still safe to unblock the signals.
+ *
+ * We don't handle errors here because this call can't fail
+ * if valid arguments are given, and because there is no good
+ * way for the caller to deal with a failure to restore
+ * the thread signal mask. */
+ (void) pthread_sigmask(SIG_SETMASK, old_sigmask, NULL);
+ }
+#endif
if (need_after_fork)
PyOS_AfterFork_Parent();
- if (envp)
- _Py_FreeCharPArray(envp);
- if (argv)
- _Py_FreeCharPArray(argv);
- _Py_FreeCharPArray(exec_array);
-
- /* Reenable gc in the parent process (or if fork failed). */
- if (_enable_gc(need_to_reenable_gc, gc_module)) {
- pid = -1;
- }
- PyMem_RawFree(groups);
- Py_XDECREF(preexec_fn_args_tuple);
- Py_XDECREF(gc_module);
- if (pid == -1) {
+cleanup:
+ if (saved_errno != 0) {
errno = saved_errno;
/* We can't call this above as PyOS_AfterFork_Parent() calls back
* into Python code which would see the unreturned error. */
PyErr_SetFromErrno(PyExc_OSError);
- return NULL; /* fork() failed. */
}
- return PyLong_FromPid(pid);
-
-cleanup:
+ Py_XDECREF(preexec_fn_args_tuple);
+ PyMem_RawFree(groups);
Py_XDECREF(cwd_obj2);
if (envp)
_Py_FreeCharPArray(envp);
+ Py_XDECREF(converted_args);
+ Py_XDECREF(fast_args);
if (argv)
_Py_FreeCharPArray(argv);
if (exec_array)
_Py_FreeCharPArray(exec_array);
- PyMem_RawFree(groups);
- Py_XDECREF(converted_args);
- Py_XDECREF(fast_args);
- Py_XDECREF(preexec_fn_args_tuple);
- _enable_gc(need_to_reenable_gc, gc_module);
+ if (need_to_reenable_gc) {
+ PyGC_Enable();
+ }
Py_XDECREF(gc_module);
- return NULL;
+
+ return pid == -1 ? NULL : PyLong_FromPid(pid);
}
@@ -954,63 +1047,26 @@ Raises: Only on an error in the parent process.\n\
PyDoc_STRVAR(module_doc,
"A POSIX helper for the subprocess module.");
-
static PyMethodDef module_methods[] = {
{"fork_exec", subprocess_fork_exec, METH_VARARGS, subprocess_fork_exec_doc},
{NULL, NULL} /* sentinel */
};
-
-static int _posixsubprocess_traverse(PyObject *m, visitproc visit, void *arg) {
- Py_VISIT(get_posixsubprocess_state(m)->disable);
- Py_VISIT(get_posixsubprocess_state(m)->enable);
- Py_VISIT(get_posixsubprocess_state(m)->isenabled);
- return 0;
-}
-
-static int _posixsubprocess_clear(PyObject *m) {
- Py_CLEAR(get_posixsubprocess_state(m)->disable);
- Py_CLEAR(get_posixsubprocess_state(m)->enable);
- Py_CLEAR(get_posixsubprocess_state(m)->isenabled);
- return 0;
-}
-
-static void _posixsubprocess_free(void *m) {
- _posixsubprocess_clear((PyObject *)m);
-}
+static PyModuleDef_Slot _posixsubprocess_slots[] = {
+ {0, NULL}
+};
static struct PyModuleDef _posixsubprocessmodule = {
PyModuleDef_HEAD_INIT,
- "_posixsubprocess",
- module_doc,
- sizeof(_posixsubprocessstate),
- module_methods,
- NULL,
- _posixsubprocess_traverse,
- _posixsubprocess_clear,
- _posixsubprocess_free,
+ .m_name = "_posixsubprocess",
+ .m_doc = module_doc,
+ .m_size = 0,
+ .m_methods = module_methods,
+ .m_slots = _posixsubprocess_slots,
};
PyMODINIT_FUNC
PyInit__posixsubprocess(void)
{
- PyObject* m;
-
- m = PyState_FindModule(&_posixsubprocessmodule);
- if (m != NULL) {
- Py_INCREF(m);
- return m;
- }
-
- m = PyModule_Create(&_posixsubprocessmodule);
- if (m == NULL) {
- return NULL;
- }
-
- get_posixsubprocess_state(m)->disable = PyUnicode_InternFromString("disable");
- get_posixsubprocess_state(m)->enable = PyUnicode_InternFromString("enable");
- get_posixsubprocess_state(m)->isenabled = PyUnicode_InternFromString("isenabled");
-
- PyState_AddModule(m, &_posixsubprocessmodule);
- return m;
+ return PyModuleDef_Init(&_posixsubprocessmodule);
}