Issue #29335: Fix subprocess.Popen.wait() when the child process has

exited to a stopped instead of terminated state (ex: when under ptrace).
This commit is contained in:
Gregory P. Smith 2017-01-22 17:30:28 -08:00
commit 5c8706c04a
3 changed files with 48 additions and 1 deletions

View file

@ -1326,7 +1326,8 @@ class Popen(object):
def _handle_exitstatus(self, sts, _WIFSIGNALED=os.WIFSIGNALED, def _handle_exitstatus(self, sts, _WIFSIGNALED=os.WIFSIGNALED,
_WTERMSIG=os.WTERMSIG, _WIFEXITED=os.WIFEXITED, _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.""" """All callers to this function MUST hold self._waitpid_lock."""
# This method is called (indirectly) by __del__, so it cannot # This method is called (indirectly) by __del__, so it cannot
# refer to anything outside of its local scope. # refer to anything outside of its local scope.
@ -1334,6 +1335,8 @@ class Popen(object):
self.returncode = -_WTERMSIG(sts) self.returncode = -_WTERMSIG(sts)
elif _WIFEXITED(sts): elif _WIFEXITED(sts):
self.returncode = _WEXITSTATUS(sts) self.returncode = _WEXITSTATUS(sts)
elif _WIFSTOPPED(sts):
self.returncode = -_WSTOPSIG(sts)
else: else:
# Should never happen # Should never happen
raise SubprocessError("Unknown child exit status!") raise SubprocessError("Unknown child exit status!")

View file

@ -3,6 +3,8 @@ from unittest import mock
from test import support from test import support
import subprocess import subprocess
import sys import sys
import platform
import ctypes
import signal import signal
import io import io
import os import os
@ -2485,6 +2487,45 @@ class POSIXProcessTestCase(BaseTestCase):
proc.communicate(timeout=999) proc.communicate(timeout=999)
mock_proc_stdin.close.assert_called_once_with() 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") @unittest.skipUnless(mswindows, "Windows specific tests")
class Win32ProcessTestCase(BaseTestCase): class Win32ProcessTestCase(BaseTestCase):

View file

@ -215,6 +215,9 @@ Core and Builtins
Library 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 - Issue #29290: Fix a regression in argparse that help messages would wrap at
non-breaking spaces. non-breaking spaces.