From 50e16e33af69aca6e908bace793178c8d6ab8272 Mon Sep 17 00:00:00 2001 From: "Gregory P. Smith" Date: Sun, 22 Jan 2017 17:28:38 -0800 Subject: Issue #29335: Fix subprocess.Popen.wait() when the child process has exited to a stopped instead of terminated state (ex: when under ptrace). --- Lib/subprocess.py | 5 ++++- Lib/test/test_subprocess.py | 41 +++++++++++++++++++++++++++++++++++++++++ Misc/NEWS | 3 +++ 3 files changed, 48 insertions(+), 1 deletion(-) diff --git a/Lib/subprocess.py b/Lib/subprocess.py index 502e26b..2165524 100644 --- a/Lib/subprocess.py +++ b/Lib/subprocess.py @@ -1285,7 +1285,8 @@ class Popen(object): def _handle_exitstatus(self, sts, _WIFSIGNALED=os.WIFSIGNALED, _WTERMSIG=os.WTERMSIG, _WIFEXITED=os.WIFEXITED, - _WEXITSTATUS=os.WEXITSTATUS): + _WEXITSTATUS=os.WEXITSTATUS, _WIFSTOPPED=os.WIFSTOPPED, + _WSTOPSIG=os.WSTOPSIG): """All callers to this function MUST hold self._waitpid_lock.""" # This method is called (indirectly) by __del__, so it cannot # refer to anything outside of its local scope. @@ -1293,6 +1294,8 @@ class Popen(object): self.returncode = -_WTERMSIG(sts) elif _WIFEXITED(sts): self.returncode = _WEXITSTATUS(sts) + elif _WIFSTOPPED(sts): + self.returncode = -_WSTOPSIG(sts) else: # Should never happen raise SubprocessError("Unknown child exit status!") diff --git a/Lib/test/test_subprocess.py b/Lib/test/test_subprocess.py index 758e094..27406d4 100644 --- a/Lib/test/test_subprocess.py +++ b/Lib/test/test_subprocess.py @@ -4,6 +4,8 @@ from test.support import script_helper from test import support import subprocess import sys +import platform +import ctypes import signal import io import locale @@ -2448,6 +2450,45 @@ class POSIXProcessTestCase(BaseTestCase): proc.communicate(timeout=999) mock_proc_stdin.close.assert_called_once_with() + _libc_file_extensions = { + 'Linux': 'so.6', + 'Darwin': '.dylib', + } + @unittest.skipIf(platform.uname()[0] not in _libc_file_extensions, + 'Test requires a libc this code can load with ctypes.') + @unittest.skipIf(not sys.executable, 'Test requires sys.executable.') + def test_child_terminated_in_stopped_state(self): + """Test wait() behavior when waitpid returns WIFSTOPPED; issue29335.""" + PTRACE_TRACEME = 0 # From glibc and MacOS (PT_TRACE_ME). + libc_name = 'libc.' + self._libc_file_extensions[platform.uname()[0]] + libc = ctypes.CDLL(libc_name) + if not hasattr(libc, 'ptrace'): + raise unittest.SkipTest('ptrace() required.') + test_ptrace = subprocess.Popen( + [sys.executable, '-c', """if True: + import ctypes + libc = ctypes.CDLL({libc_name!r}) + libc.ptrace({PTRACE_TRACEME}, 0, 0) + """.format(libc_name=libc_name, PTRACE_TRACEME=PTRACE_TRACEME) + ]) + if test_ptrace.wait() != 0: + raise unittest.SkipTest('ptrace() failed - unable to test.') + child = subprocess.Popen( + [sys.executable, '-c', """if True: + import ctypes + libc = ctypes.CDLL({libc_name!r}) + libc.ptrace({PTRACE_TRACEME}, 0, 0) + libc.printf(ctypes.c_char_p(0xdeadbeef)) # Crash the process. + """.format(libc_name=libc_name, PTRACE_TRACEME=PTRACE_TRACEME) + ]) + try: + returncode = child.wait() + except Exception as e: + child.kill() # Clean up the hung stopped process. + raise e + self.assertNotEqual(0, returncode) + self.assertLess(returncode, 0) # signal death, likely SIGSEGV. + @unittest.skipUnless(mswindows, "Windows specific tests") class Win32ProcessTestCase(BaseTestCase): diff --git a/Misc/NEWS b/Misc/NEWS index d2a8390..dd68006 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -13,6 +13,9 @@ Core and Builtins Library ------- +- Issue #29335: Fix subprocess.Popen.wait() when the child process has + exited to a stopped instead of terminated state (ex: when under ptrace). + - Issue #29290: Fix a regression in argparse that help messages would wrap at non-breaking spaces. -- cgit v0.12