From d3b4e068077dd26927ae7485bd0303e09d962c02 Mon Sep 17 00:00:00 2001
From: Alexey Izbyshev <izbyshev@ispras.ru>
Date: Sun, 1 Nov 2020 08:33:08 +0300
Subject: [PATCH] bpo-42146: Unify cleanup in subprocess_fork_exec() (GH-22970)

* bpo-42146: Unify cleanup in subprocess_fork_exec()

Also ignore errors from _enable_gc():
* They are always suppressed by the current code due to a bug.
* _enable_gc() is only used if `preexec_fn != None`, which is unsafe.
* We don't have a good way to handle errors in case we successfully
  created a child process.

Co-authored-by: Gregory P. Smith <greg@krypto.org>
---
 Modules/_posixsubprocess.c | 53 ++++++++++++++++------------------------------
 1 file changed, 18 insertions(+), 35 deletions(-)

diff --git a/Modules/_posixsubprocess.c b/Modules/_posixsubprocess.c
index 5e5fbb2..a00e137 100644
--- a/Modules/_posixsubprocess.c
+++ b/Modules/_posixsubprocess.c
@@ -87,8 +87,8 @@
 
 #define _posixsubprocessstate_global get_posixsubprocess_state(PyState_FindModule(&_posixsubprocessmodule))
 
-/* If gc was disabled, call gc.enable().  Return 0 on success. */
-static int
+/* If gc was disabled, call gc.enable(). Ignore errors. */
+static void
 _enable_gc(int need_to_reenable_gc, PyObject *gc_module)
 {
     PyObject *result;
@@ -98,15 +98,17 @@
         PyErr_Fetch(&exctype, &val, &tb);
         result = PyObject_CallMethodNoArgs(
             gc_module, _posixsubprocessstate_global->enable);
+        if (result == NULL) {
+            /* We might have created a child process at this point, we
+             * we have no good way to handle a failure to reenable GC
+             * and return information about the child process. */
+            PyErr_Print();
+        }
+        Py_XDECREF(result);
         if (exctype != NULL) {
             PyErr_Restore(exctype, val, tb);
         }
-        if (result == NULL) {
-            return 1;
-        }
-        Py_DECREF(result);
     }
-    return 0;
 }
 
 
@@ -774,7 +776,7 @@ struct linux_dirent64 {
     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;
@@ -1010,8 +1012,6 @@ struct linux_dirent64 {
         sigset_t all_sigs;
         sigfillset(&all_sigs);
         if ((saved_errno = pthread_sigmask(SIG_BLOCK, &all_sigs, &old_sigs))) {
-            errno = saved_errno;
-            PyErr_SetFromErrno(PyExc_OSError);
             goto cleanup;
         }
         old_sigmask = &old_sigs;
@@ -1050,50 +1050,33 @@ struct linux_dirent64 {
     }
 #endif
 
-    Py_XDECREF(cwd_obj2);
-
     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);
     Py_XDECREF(gc_module);
-    return NULL;
+
+    return pid == -1 ? NULL : PyLong_FromPid(pid);
 }
 
 
-- 
1.8.3.1

