]> granicus.if.org Git - python/commitdiff
Fixes issue #16140: The subprocess module no longer double closes its
authorGregory P. Smith <greg@krypto.org>
Sun, 11 Nov 2012 09:37:02 +0000 (01:37 -0800)
committerGregory P. Smith <greg@krypto.org>
Sun, 11 Nov 2012 09:37:02 +0000 (01:37 -0800)
child subprocess.PIPE parent file descriptors on child error prior to
exec().

This would lead to race conditions in multithreaded programs where
another thread opened a file reusing the fd which was then closed out
from beneath it by the errant second close.

Lib/subprocess.py
Lib/test/test_subprocess.py
Misc/NEWS

index fcc3e6e3baaa524d1d3d6888575613719e7431f9..a3228294693bcc181bf0f6213e753422f1970b64 100644 (file)
@@ -1378,9 +1378,6 @@ class Popen(object):
                 child_exception_type = getattr(
                         builtins, exception_name.decode('ascii'),
                         RuntimeError)
-                for fd in (p2cwrite, c2pread, errread):
-                    if fd != -1:
-                        os.close(fd)
                 err_msg = err_msg.decode(errors="surrogatepass")
                 if issubclass(child_exception_type, OSError) and hex_errno:
                     errno_num = int(hex_errno, 16)
index 5c20975a5dbf14829958e7fec65a66c6e42702fd..c7c6ce846cbe8eef6ba6190b25652cb2f75aab26 100644 (file)
@@ -1035,6 +1035,44 @@ class POSIXProcessTestCase(BaseTestCase):
             self.fail("Exception raised by preexec_fn did not make it "
                       "to the parent process.")
 
+    @unittest.skipIf(not os.path.exists("/dev/zero"), "/dev/zero required.")
+    def test_preexec_errpipe_does_not_double_close_pipes(self):
+        """Issue16140: Don't double close pipes on preexec error."""
+        class SafeConstructorPopen(subprocess.Popen):
+            def __init__(self):
+                pass  # Do nothing so we can modify the instance for testing.
+            def RealPopen(self, *args, **kwargs):
+                subprocess.Popen.__init__(self, *args, **kwargs)
+        def raise_it():
+            raise RuntimeError("force the _execute_child() errpipe_data path.")
+
+        p = SafeConstructorPopen()
+
+        def _test_fds_execute_child_wrapper(*args, **kwargs):
+            try:
+                subprocess.Popen._execute_child(p, *args, **kwargs)
+            finally:
+                # Open a bunch of file descriptors and verify that
+                # none of them are the same as the ones the Popen
+                # instance is using for stdin/stdout/stderr.
+                devzero_fds = [os.open("/dev/zero", os.O_RDONLY)
+                               for _ in range(8)]
+                try:
+                    for fd in devzero_fds:
+                        self.assertNotIn(fd, (
+                                p.stdin.fileno(), p.stdout.fileno(),
+                                p.stderr.fileno()),
+                                msg="At least one fd was closed early.")
+                finally:
+                    map(os.close, devzero_fds)
+
+        p._execute_child = _test_fds_execute_child_wrapper
+
+        with self.assertRaises(RuntimeError):
+            p.RealPopen([sys.executable, "-c", "pass"],
+                        stdin=subprocess.PIPE, stdout=subprocess.PIPE,
+                        stderr=subprocess.PIPE, preexec_fn=raise_it)
+
     def test_preexec_gc_module_failure(self):
         # This tests the code that disables garbage collection if the child
         # process will execute any Python.
index 49d6a1a386666ef23d9f39f4660bd264ecaf545a..cdc96b8276a58ba1a614a9732627467f4293d127 100644 (file)
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -162,6 +162,9 @@ Core and Builtins
 Library
 -------
 
+- Issue #16140: The subprocess module no longer double closes its child
+  subprocess.PIPE parent file descriptors on child error prior to exec().
+
 - Remove a bare print to stdout from the subprocess module that could have
   happened if the child process wrote garbage to its pre-exec error pipe.