diff options
| author | shadchin <[email protected]> | 2022-04-18 12:39:32 +0300 |
|---|---|---|
| committer | shadchin <[email protected]> | 2022-04-18 12:39:32 +0300 |
| commit | d4be68e361f4258cf0848fc70018dfe37a2acc24 (patch) | |
| tree | 153e294cd97ac8b5d7a989612704a0c1f58e8ad4 /contrib/tools/python3/src/Modules/_posixsubprocess.c | |
| parent | 260c02f5ccf242d9d9b8a873afaf6588c00237d6 (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.c | 408 |
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); } |
