From 6270ccd51b9a6c517018edaadbfd508360b6f20d Mon Sep 17 00:00:00 2001 From: cptpcrd <31829097+cptpcrd@users.noreply.github.com> Date: Sat, 27 Aug 2022 21:41:48 -0400 Subject: [PATCH 1/4] Fix file descriptor leaks in subprocess.Popen --- Lib/subprocess.py | 311 ++++++++++-------- ...2-08-27-21-41-41.gh-issue-87474.9X-kxt.rst | 1 + 2 files changed, 182 insertions(+), 130 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2022-08-27-21-41-41.gh-issue-87474.9X-kxt.rst diff --git a/Lib/subprocess.py b/Lib/subprocess.py index 1f203bd00d3500..3085e9ef5aa435 100644 --- a/Lib/subprocess.py +++ b/Lib/subprocess.py @@ -872,37 +872,6 @@ def __init__(self, args, bufsize=-1, executable=None, 'and universal_newlines are supplied but ' 'different. Pass one or the other.') - # Input and output objects. The general principle is like - # this: - # - # Parent Child - # ------ ----- - # p2cwrite ---stdin---> p2cread - # c2pread <--stdout--- c2pwrite - # errread <--stderr--- errwrite - # - # On POSIX, the child objects are file descriptors. On - # Windows, these are Windows file handles. The parent objects - # are file descriptors on both platforms. The parent objects - # are -1 when not using PIPEs. The child objects are -1 - # when not redirecting. - - (p2cread, p2cwrite, - c2pread, c2pwrite, - errread, errwrite) = self._get_handles(stdin, stdout, stderr) - - # We wrap OS handles *before* launching the child, otherwise a - # quickly terminating child could make our fds unwrappable - # (see #8458). - - if _mswindows: - if p2cwrite != -1: - p2cwrite = msvcrt.open_osfhandle(p2cwrite.Detach(), 0) - if c2pread != -1: - c2pread = msvcrt.open_osfhandle(c2pread.Detach(), 0) - if errread != -1: - errread = msvcrt.open_osfhandle(errread.Detach(), 0) - self.text_mode = encoding or errors or text or universal_newlines if self.text_mode and encoding is None: self.encoding = encoding = _text_encoding() @@ -1003,6 +972,39 @@ def __init__(self, args, bufsize=-1, executable=None, if uid < 0: raise ValueError(f"User ID cannot be negative, got {uid}") + # Input and output objects. The general principle is like + # this: + # + # Parent Child + # ------ ----- + # p2cwrite ---stdin---> p2cread + # c2pread <--stdout--- c2pwrite + # errread <--stderr--- errwrite + # + # On POSIX, the child objects are file descriptors. On + # Windows, these are Windows file handles. The parent objects + # are file descriptors on both platforms. The parent objects + # are -1 when not using PIPEs. The child objects are -1 + # when not redirecting. + + (p2cread, p2cwrite, + c2pread, c2pwrite, + errread, errwrite) = self._get_handles(stdin, stdout, stderr) + + # From here on, raising exceptions may cause file descriptor leakage + + # We wrap OS handles *before* launching the child, otherwise a + # quickly terminating child could make our fds unwrappable + # (see #8458). + + if _mswindows: + if p2cwrite != -1: + p2cwrite = msvcrt.open_osfhandle(p2cwrite.Detach(), 0) + if c2pread != -1: + c2pread = msvcrt.open_osfhandle(c2pread.Detach(), 0) + if errread != -1: + errread = msvcrt.open_osfhandle(errread.Detach(), 0) + try: if p2cwrite != -1: self.stdin = io.open(p2cwrite, 'wb', bufsize) @@ -1321,61 +1323,93 @@ def _get_handles(self, stdin, stdout, stderr): c2pread, c2pwrite = -1, -1 errread, errwrite = -1, -1 - if stdin is None: - p2cread = _winapi.GetStdHandle(_winapi.STD_INPUT_HANDLE) - if p2cread is None: - p2cread, _ = _winapi.CreatePipe(None, 0) - p2cread = Handle(p2cread) - _winapi.CloseHandle(_) - elif stdin == PIPE: - p2cread, p2cwrite = _winapi.CreatePipe(None, 0) - p2cread, p2cwrite = Handle(p2cread), Handle(p2cwrite) - elif stdin == DEVNULL: - p2cread = msvcrt.get_osfhandle(self._get_devnull()) - elif isinstance(stdin, int): - p2cread = msvcrt.get_osfhandle(stdin) - else: - # Assuming file-like object - p2cread = msvcrt.get_osfhandle(stdin.fileno()) - p2cread = self._make_inheritable(p2cread) - - if stdout is None: - c2pwrite = _winapi.GetStdHandle(_winapi.STD_OUTPUT_HANDLE) - if c2pwrite is None: - _, c2pwrite = _winapi.CreatePipe(None, 0) - c2pwrite = Handle(c2pwrite) - _winapi.CloseHandle(_) - elif stdout == PIPE: - c2pread, c2pwrite = _winapi.CreatePipe(None, 0) - c2pread, c2pwrite = Handle(c2pread), Handle(c2pwrite) - elif stdout == DEVNULL: - c2pwrite = msvcrt.get_osfhandle(self._get_devnull()) - elif isinstance(stdout, int): - c2pwrite = msvcrt.get_osfhandle(stdout) - else: - # Assuming file-like object - c2pwrite = msvcrt.get_osfhandle(stdout.fileno()) - c2pwrite = self._make_inheritable(c2pwrite) - - if stderr is None: - errwrite = _winapi.GetStdHandle(_winapi.STD_ERROR_HANDLE) - if errwrite is None: - _, errwrite = _winapi.CreatePipe(None, 0) - errwrite = Handle(errwrite) - _winapi.CloseHandle(_) - elif stderr == PIPE: - errread, errwrite = _winapi.CreatePipe(None, 0) - errread, errwrite = Handle(errread), Handle(errwrite) - elif stderr == STDOUT: - errwrite = c2pwrite - elif stderr == DEVNULL: - errwrite = msvcrt.get_osfhandle(self._get_devnull()) - elif isinstance(stderr, int): - errwrite = msvcrt.get_osfhandle(stderr) - else: - # Assuming file-like object - errwrite = msvcrt.get_osfhandle(stderr.fileno()) - errwrite = self._make_inheritable(errwrite) + stdin_needsclose = False + stdout_needsclose = False + stderr_needsclose = False + + try: + if stdin is None: + p2cread = _winapi.GetStdHandle(_winapi.STD_INPUT_HANDLE) + if p2cread is None: + stdin_needsclose = True + p2cread, _ = _winapi.CreatePipe(None, 0) + p2cread = Handle(p2cread) + _winapi.CloseHandle(_) + elif stdin == PIPE: + stdin_needsclose = True + p2cread, p2cwrite = _winapi.CreatePipe(None, 0) + p2cread, p2cwrite = Handle(p2cread), Handle(p2cwrite) + elif stdin == DEVNULL: + p2cread = msvcrt.get_osfhandle(self._get_devnull()) + elif isinstance(stdin, int): + p2cread = msvcrt.get_osfhandle(stdin) + else: + # Assuming file-like object + p2cread = msvcrt.get_osfhandle(stdin.fileno()) + p2cread = self._make_inheritable(p2cread) + + if stdout is None: + c2pwrite = _winapi.GetStdHandle(_winapi.STD_OUTPUT_HANDLE) + if c2pwrite is None: + stdout_needsclose = True + _, c2pwrite = _winapi.CreatePipe(None, 0) + c2pwrite = Handle(c2pwrite) + _winapi.CloseHandle(_) + elif stdout == PIPE: + stdout_needsclose = True + c2pread, c2pwrite = _winapi.CreatePipe(None, 0) + c2pread, c2pwrite = Handle(c2pread), Handle(c2pwrite) + elif stdout == DEVNULL: + c2pwrite = msvcrt.get_osfhandle(self._get_devnull()) + elif isinstance(stdout, int): + c2pwrite = msvcrt.get_osfhandle(stdout) + else: + # Assuming file-like object + c2pwrite = msvcrt.get_osfhandle(stdout.fileno()) + c2pwrite = self._make_inheritable(c2pwrite) + + if stderr is None: + errwrite = _winapi.GetStdHandle(_winapi.STD_ERROR_HANDLE) + if errwrite is None: + stderr_needsclose = True + _, errwrite = _winapi.CreatePipe(None, 0) + errwrite = Handle(errwrite) + _winapi.CloseHandle(_) + elif stderr == PIPE: + stderr_needsclose = True + errread, errwrite = _winapi.CreatePipe(None, 0) + errread, errwrite = Handle(errread), Handle(errwrite) + elif stderr == STDOUT: + errwrite = c2pwrite + elif stderr == DEVNULL: + errwrite = msvcrt.get_osfhandle(self._get_devnull()) + elif isinstance(stderr, int): + errwrite = msvcrt.get_osfhandle(stderr) + else: + # Assuming file-like object + errwrite = msvcrt.get_osfhandle(stderr.fileno()) + errwrite = self._make_inheritable(errwrite) + + except BaseException: + to_close = [] + if stdin_needsclose and p2cwrite != -1: + to_close.append(p2cread) + to_close.append(p2cwrite) + if stdout_needsclose and p2cwrite != -1: + to_close.append(c2pread) + to_close.append(c2pwrite) + if stderr_needsclose and errwrite != -1: + to_close.append(errread) + to_close.append(errwrite) + for file in to_close: + if isinstance(file, Handle): + file.Close() + else: + os.close(file) + if hasattr(self, "_devnull"): + os.close(self._devnull) + del self._devnull + raise return (p2cread, p2cwrite, c2pread, c2pwrite, @@ -1662,52 +1696,69 @@ def _get_handles(self, stdin, stdout, stderr): c2pread, c2pwrite = -1, -1 errread, errwrite = -1, -1 - if stdin is None: - pass - elif stdin == PIPE: - p2cread, p2cwrite = os.pipe() - if self.pipesize > 0 and hasattr(fcntl, "F_SETPIPE_SZ"): - fcntl.fcntl(p2cwrite, fcntl.F_SETPIPE_SZ, self.pipesize) - elif stdin == DEVNULL: - p2cread = self._get_devnull() - elif isinstance(stdin, int): - p2cread = stdin - else: - # Assuming file-like object - p2cread = stdin.fileno() + try: + if stdin is None: + pass + elif stdin == PIPE: + p2cread, p2cwrite = os.pipe() + if self.pipesize > 0 and hasattr(fcntl, "F_SETPIPE_SZ"): + fcntl.fcntl(p2cwrite, fcntl.F_SETPIPE_SZ, self.pipesize) + elif stdin == DEVNULL: + p2cread = self._get_devnull() + elif isinstance(stdin, int): + p2cread = stdin + else: + # Assuming file-like object + p2cread = stdin.fileno() - if stdout is None: - pass - elif stdout == PIPE: - c2pread, c2pwrite = os.pipe() - if self.pipesize > 0 and hasattr(fcntl, "F_SETPIPE_SZ"): - fcntl.fcntl(c2pwrite, fcntl.F_SETPIPE_SZ, self.pipesize) - elif stdout == DEVNULL: - c2pwrite = self._get_devnull() - elif isinstance(stdout, int): - c2pwrite = stdout - else: - # Assuming file-like object - c2pwrite = stdout.fileno() + if stdout is None: + pass + elif stdout == PIPE: + c2pread, c2pwrite = os.pipe() + if self.pipesize > 0 and hasattr(fcntl, "F_SETPIPE_SZ"): + fcntl.fcntl(c2pwrite, fcntl.F_SETPIPE_SZ, self.pipesize) + elif stdout == DEVNULL: + c2pwrite = self._get_devnull() + elif isinstance(stdout, int): + c2pwrite = stdout + else: + # Assuming file-like object + c2pwrite = stdout.fileno() - if stderr is None: - pass - elif stderr == PIPE: - errread, errwrite = os.pipe() - if self.pipesize > 0 and hasattr(fcntl, "F_SETPIPE_SZ"): - fcntl.fcntl(errwrite, fcntl.F_SETPIPE_SZ, self.pipesize) - elif stderr == STDOUT: - if c2pwrite != -1: - errwrite = c2pwrite - else: # child's stdout is not set, use parent's stdout - errwrite = sys.__stdout__.fileno() - elif stderr == DEVNULL: - errwrite = self._get_devnull() - elif isinstance(stderr, int): - errwrite = stderr - else: - # Assuming file-like object - errwrite = stderr.fileno() + if stderr is None: + pass + elif stderr == PIPE: + errread, errwrite = os.pipe() + if self.pipesize > 0 and hasattr(fcntl, "F_SETPIPE_SZ"): + fcntl.fcntl(errwrite, fcntl.F_SETPIPE_SZ, self.pipesize) + elif stderr == STDOUT: + if c2pwrite != -1: + errwrite = c2pwrite + else: # child's stdout is not set, use parent's stdout + errwrite = sys.__stdout__.fileno() + elif stderr == DEVNULL: + errwrite = self._get_devnull() + elif isinstance(stderr, int): + errwrite = stderr + else: + # Assuming file-like object + errwrite = stderr.fileno() + + except BaseException: + # Close the file descriptors we opened to avoid leakage + if stdin == PIPE and p2cwrite != -1: + os.close(p2cread) + os.close(p2cwrite) + if stdout == PIPE and c2pwrite != -1: + os.close(c2pread) + os.close(c2pwrite) + if stderr == PIPE and errwrite != -1: + os.close(errread) + os.close(errwrite) + if hasattr(self, "_devnull"): + os.close(self._devnull) + del self._devnull + raise return (p2cread, p2cwrite, c2pread, c2pwrite, diff --git a/Misc/NEWS.d/next/Library/2022-08-27-21-41-41.gh-issue-87474.9X-kxt.rst b/Misc/NEWS.d/next/Library/2022-08-27-21-41-41.gh-issue-87474.9X-kxt.rst new file mode 100644 index 00000000000000..d11c58a12fe5f2 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2022-08-27-21-41-41.gh-issue-87474.9X-kxt.rst @@ -0,0 +1 @@ +Fix file descriptor leaks in subprocess.Popen From 783e6f8a654f7d9c45ca3feb3ba44c1c4c602ac7 Mon Sep 17 00:00:00 2001 From: cptpcrd <31829097+cptpcrd@users.noreply.github.com> Date: Sun, 2 Oct 2022 19:35:16 -0400 Subject: [PATCH 2/4] Refactor to use context manager Simplifies the logic and should help avoid mistakes in the future. Co-authored-by: Gregory P. Smith --- Lib/subprocess.py | 80 ++++++++++++++++++----------------------------- 1 file changed, 31 insertions(+), 49 deletions(-) diff --git a/Lib/subprocess.py b/Lib/subprocess.py index 3085e9ef5aa435..7257c808e197d1 100644 --- a/Lib/subprocess.py +++ b/Lib/subprocess.py @@ -1308,6 +1308,26 @@ def _close_pipe_fds(self, # Prevent a double close of these handles/fds from __init__ on error. self._closed_child_pipe_fds = True + @contextlib.contextmanager + def _on_error_fd_closer(self): + """Helper to ensure file descriptors opened in _get_handles are closed""" + to_close = [] + try: + yield to_close + except: + if hasattr(self, '_devnull'): + to_close.append(self._devnull) + del self._devnull + for fd in to_close: + try: + if _mswindows and isinstance(fd, Handle): + fd.Close() + else: + os.close(fd) + except OSError: + pass + raise + if _mswindows: # # Windows methods @@ -1323,22 +1343,18 @@ def _get_handles(self, stdin, stdout, stderr): c2pread, c2pwrite = -1, -1 errread, errwrite = -1, -1 - stdin_needsclose = False - stdout_needsclose = False - stderr_needsclose = False - - try: + with self._on_error_fd_closer() as err_close_fds: if stdin is None: p2cread = _winapi.GetStdHandle(_winapi.STD_INPUT_HANDLE) if p2cread is None: - stdin_needsclose = True p2cread, _ = _winapi.CreatePipe(None, 0) p2cread = Handle(p2cread) _winapi.CloseHandle(_) + err_close_fds.append(p2cread) elif stdin == PIPE: - stdin_needsclose = True p2cread, p2cwrite = _winapi.CreatePipe(None, 0) p2cread, p2cwrite = Handle(p2cread), Handle(p2cwrite) + err_close_fds.extend((p2cread, p2cwrite)) elif stdin == DEVNULL: p2cread = msvcrt.get_osfhandle(self._get_devnull()) elif isinstance(stdin, int): @@ -1351,14 +1367,14 @@ def _get_handles(self, stdin, stdout, stderr): if stdout is None: c2pwrite = _winapi.GetStdHandle(_winapi.STD_OUTPUT_HANDLE) if c2pwrite is None: - stdout_needsclose = True _, c2pwrite = _winapi.CreatePipe(None, 0) c2pwrite = Handle(c2pwrite) _winapi.CloseHandle(_) + err_close_fds.append(c2pwrite) elif stdout == PIPE: - stdout_needsclose = True c2pread, c2pwrite = _winapi.CreatePipe(None, 0) c2pread, c2pwrite = Handle(c2pread), Handle(c2pwrite) + err_close_fds.extend((c2pread, c2pwrite)) elif stdout == DEVNULL: c2pwrite = msvcrt.get_osfhandle(self._get_devnull()) elif isinstance(stdout, int): @@ -1371,14 +1387,14 @@ def _get_handles(self, stdin, stdout, stderr): if stderr is None: errwrite = _winapi.GetStdHandle(_winapi.STD_ERROR_HANDLE) if errwrite is None: - stderr_needsclose = True _, errwrite = _winapi.CreatePipe(None, 0) errwrite = Handle(errwrite) _winapi.CloseHandle(_) + err_close_fds.append(errwrite) elif stderr == PIPE: - stderr_needsclose = True errread, errwrite = _winapi.CreatePipe(None, 0) errread, errwrite = Handle(errread), Handle(errwrite) + err_close_fds.extend((errread, errwrite)) elif stderr == STDOUT: errwrite = c2pwrite elif stderr == DEVNULL: @@ -1390,27 +1406,6 @@ def _get_handles(self, stdin, stdout, stderr): errwrite = msvcrt.get_osfhandle(stderr.fileno()) errwrite = self._make_inheritable(errwrite) - except BaseException: - to_close = [] - if stdin_needsclose and p2cwrite != -1: - to_close.append(p2cread) - to_close.append(p2cwrite) - if stdout_needsclose and p2cwrite != -1: - to_close.append(c2pread) - to_close.append(c2pwrite) - if stderr_needsclose and errwrite != -1: - to_close.append(errread) - to_close.append(errwrite) - for file in to_close: - if isinstance(file, Handle): - file.Close() - else: - os.close(file) - if hasattr(self, "_devnull"): - os.close(self._devnull) - del self._devnull - raise - return (p2cread, p2cwrite, c2pread, c2pwrite, errread, errwrite) @@ -1696,13 +1691,14 @@ def _get_handles(self, stdin, stdout, stderr): c2pread, c2pwrite = -1, -1 errread, errwrite = -1, -1 - try: + with self._on_error_fd_closer() as err_close_fds: if stdin is None: pass elif stdin == PIPE: p2cread, p2cwrite = os.pipe() if self.pipesize > 0 and hasattr(fcntl, "F_SETPIPE_SZ"): fcntl.fcntl(p2cwrite, fcntl.F_SETPIPE_SZ, self.pipesize) + err_close_fds.extend((p2cread, p2cwrite)) elif stdin == DEVNULL: p2cread = self._get_devnull() elif isinstance(stdin, int): @@ -1717,6 +1713,7 @@ def _get_handles(self, stdin, stdout, stderr): c2pread, c2pwrite = os.pipe() if self.pipesize > 0 and hasattr(fcntl, "F_SETPIPE_SZ"): fcntl.fcntl(c2pwrite, fcntl.F_SETPIPE_SZ, self.pipesize) + err_close_fds.extend((c2pread, c2pwrite)) elif stdout == DEVNULL: c2pwrite = self._get_devnull() elif isinstance(stdout, int): @@ -1731,6 +1728,7 @@ def _get_handles(self, stdin, stdout, stderr): errread, errwrite = os.pipe() if self.pipesize > 0 and hasattr(fcntl, "F_SETPIPE_SZ"): fcntl.fcntl(errwrite, fcntl.F_SETPIPE_SZ, self.pipesize) + err_close_fds.extend((errread, errwrite)) elif stderr == STDOUT: if c2pwrite != -1: errwrite = c2pwrite @@ -1744,22 +1742,6 @@ def _get_handles(self, stdin, stdout, stderr): # Assuming file-like object errwrite = stderr.fileno() - except BaseException: - # Close the file descriptors we opened to avoid leakage - if stdin == PIPE and p2cwrite != -1: - os.close(p2cread) - os.close(p2cwrite) - if stdout == PIPE and c2pwrite != -1: - os.close(c2pread) - os.close(c2pwrite) - if stderr == PIPE and errwrite != -1: - os.close(errread) - os.close(errwrite) - if hasattr(self, "_devnull"): - os.close(self._devnull) - del self._devnull - raise - return (p2cread, p2cwrite, c2pread, c2pwrite, errread, errwrite) From 0d4efba1463431f667b63e34f2aa4ac01ab64b4c Mon Sep 17 00:00:00 2001 From: "Gregory P. Smith [Google LLC]" Date: Fri, 7 Oct 2022 16:59:07 -0700 Subject: [PATCH 3/4] Move err_close_fds adds next to pipe creation. --- Lib/subprocess.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Lib/subprocess.py b/Lib/subprocess.py index 7257c808e197d1..fbc76b8d0f14b2 100644 --- a/Lib/subprocess.py +++ b/Lib/subprocess.py @@ -1349,8 +1349,8 @@ def _get_handles(self, stdin, stdout, stderr): if p2cread is None: p2cread, _ = _winapi.CreatePipe(None, 0) p2cread = Handle(p2cread) - _winapi.CloseHandle(_) err_close_fds.append(p2cread) + _winapi.CloseHandle(_) elif stdin == PIPE: p2cread, p2cwrite = _winapi.CreatePipe(None, 0) p2cread, p2cwrite = Handle(p2cread), Handle(p2cwrite) @@ -1369,8 +1369,8 @@ def _get_handles(self, stdin, stdout, stderr): if c2pwrite is None: _, c2pwrite = _winapi.CreatePipe(None, 0) c2pwrite = Handle(c2pwrite) - _winapi.CloseHandle(_) err_close_fds.append(c2pwrite) + _winapi.CloseHandle(_) elif stdout == PIPE: c2pread, c2pwrite = _winapi.CreatePipe(None, 0) c2pread, c2pwrite = Handle(c2pread), Handle(c2pwrite) @@ -1389,8 +1389,8 @@ def _get_handles(self, stdin, stdout, stderr): if errwrite is None: _, errwrite = _winapi.CreatePipe(None, 0) errwrite = Handle(errwrite) - _winapi.CloseHandle(_) err_close_fds.append(errwrite) + _winapi.CloseHandle(_) elif stderr == PIPE: errread, errwrite = _winapi.CreatePipe(None, 0) errread, errwrite = Handle(errread), Handle(errwrite) @@ -1696,9 +1696,9 @@ def _get_handles(self, stdin, stdout, stderr): pass elif stdin == PIPE: p2cread, p2cwrite = os.pipe() + err_close_fds.extend((p2cread, p2cwrite)) if self.pipesize > 0 and hasattr(fcntl, "F_SETPIPE_SZ"): fcntl.fcntl(p2cwrite, fcntl.F_SETPIPE_SZ, self.pipesize) - err_close_fds.extend((p2cread, p2cwrite)) elif stdin == DEVNULL: p2cread = self._get_devnull() elif isinstance(stdin, int): @@ -1711,9 +1711,9 @@ def _get_handles(self, stdin, stdout, stderr): pass elif stdout == PIPE: c2pread, c2pwrite = os.pipe() + err_close_fds.extend((c2pread, c2pwrite)) if self.pipesize > 0 and hasattr(fcntl, "F_SETPIPE_SZ"): fcntl.fcntl(c2pwrite, fcntl.F_SETPIPE_SZ, self.pipesize) - err_close_fds.extend((c2pread, c2pwrite)) elif stdout == DEVNULL: c2pwrite = self._get_devnull() elif isinstance(stdout, int): @@ -1726,9 +1726,9 @@ def _get_handles(self, stdin, stdout, stderr): pass elif stderr == PIPE: errread, errwrite = os.pipe() + err_close_fds.extend((errread, errwrite)) if self.pipesize > 0 and hasattr(fcntl, "F_SETPIPE_SZ"): fcntl.fcntl(errwrite, fcntl.F_SETPIPE_SZ, self.pipesize) - err_close_fds.extend((errread, errwrite)) elif stderr == STDOUT: if c2pwrite != -1: errwrite = c2pwrite From 0265ce7cc309499bae71313c2c924178868df87c Mon Sep 17 00:00:00 2001 From: "Gregory P. Smith" Date: Tue, 16 May 2023 12:51:37 -0700 Subject: [PATCH 4/4] add ReST formatting to NEWS. --- .../next/Library/2022-08-27-21-41-41.gh-issue-87474.9X-kxt.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Library/2022-08-27-21-41-41.gh-issue-87474.9X-kxt.rst b/Misc/NEWS.d/next/Library/2022-08-27-21-41-41.gh-issue-87474.9X-kxt.rst index d11c58a12fe5f2..eeb5308680e8af 100644 --- a/Misc/NEWS.d/next/Library/2022-08-27-21-41-41.gh-issue-87474.9X-kxt.rst +++ b/Misc/NEWS.d/next/Library/2022-08-27-21-41-41.gh-issue-87474.9X-kxt.rst @@ -1 +1 @@ -Fix file descriptor leaks in subprocess.Popen +Fix potential file descriptor leaks in :class:`subprocess.Popen`.