]> 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:41:49 +0000 (01:41 -0800)
committerGregory P. Smith <greg@krypto.org>
Sun, 11 Nov 2012 09:41:49 +0000 (01:41 -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.

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

index 0fff36fa5d67763f395ee12b983c0faf6828dcac,d3d90ca2cec8cd279a77316ea5cb17b36e79b8b0..c0151046ec5e911615cd283e4517cfeaba0c25fd
@@@ -1418,10 -1418,7 +1418,7 @@@ class Popen(object)
                                 repr(errpipe_data))
                  child_exception_type = getattr(
                          builtins, exception_name.decode('ascii'),
 -                        RuntimeError)
 +                        SubprocessError)
-                 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 8978b31fb5f36bbf74a2f8436b484be18fbeb006,da7045cb5f57aa69b4d08a868f9d3ff6ae71d107..e502618219fcfc17d745e485b90fd7865ed79c0d
@@@ -1189,6 -1189,44 +1189,45 @@@ class POSIXProcessTestCase(BaseTestCase
              self.fail("Exception raised by preexec_fn did not make it "
                        "to the parent process.")
  
 -            raise RuntimeError("force the _execute_child() errpipe_data path.")
+     @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():
 -        with self.assertRaises(RuntimeError):
++            raise subprocess.SubprocessError(
++                    "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(subprocess.SubprocessError):
+             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.
diff --cc Misc/NEWS
Simple merge