From: Gregory P. Smith Date: Mon, 23 Jan 2017 06:38:28 +0000 (-0800) Subject: Issue #29335: Fix subprocess.Popen.wait() when the child process has X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=f0739cbfe79ac48a20c612e20d5bf71162da7909;p=python Issue #29335: Fix subprocess.Popen.wait() when the child process has exited to a stopped instead of terminated state (ex: when under ptrace). --- diff --git a/Lib/subprocess.py b/Lib/subprocess.py index 407141e25d..c6ecc46186 100644 --- a/Lib/subprocess.py +++ b/Lib/subprocess.py @@ -1026,13 +1026,16 @@ 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): # This method is called (indirectly) by __del__, so it cannot # refer to anything outside of its local scope. if _WIFSIGNALED(sts): self.returncode = -_WTERMSIG(sts) elif _WIFEXITED(sts): self.returncode = _WEXITSTATUS(sts) + elif _WIFSTOPPED(sts): + self.returncode = -_WSTOPSIG(sts) else: # Should never happen raise RuntimeError("Unknown child exit status!") diff --git a/Lib/test/test_subprocess.py b/Lib/test/test_subprocess.py index 160ccfeb16..627275891f 100644 --- a/Lib/test/test_subprocess.py +++ b/Lib/test/test_subprocess.py @@ -2,6 +2,7 @@ import unittest from test import test_support import subprocess import sys +import platform import signal import os import errno @@ -10,6 +11,11 @@ import time import re import sysconfig +try: + import ctypes +except ImportError: + ctypes = None + try: import resource except ImportError: @@ -1216,6 +1222,46 @@ class POSIXProcessTestCase(BaseTestCase): self.assertEqual(p2.returncode, 0, "Unexpected error: " + repr(stderr)) + _libc_file_extensions = { + 'Linux': 'so.6', + 'Darwin': 'dylib', + } + @unittest.skipIf(not ctypes, 'ctypes module required.') + @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 155ec8e066..08256a5f7f 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -23,6 +23,9 @@ Extension Modules 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 #29219: Fixed infinite recursion in the repr of uninitialized ctypes.CDLL instances.