@@ -15,6 +15,10 @@ class PySysGetAttrTest(unittest.TestCase):
1515 import sys
1616 from faulthandler import dump_traceback, enable, dump_traceback_later
1717
18+ # The faulthandler_get_fileno calls stderr functions twice - the first
19+ # call is a fileno and the second one is a flush. So, if the stderr
20+ # changed while fileno is called, then we get a segfault when we
21+ # call flush after that.
1822 class FakeIO:
1923 def __init__(self, what):
2024 self.what = what
@@ -50,6 +54,12 @@ def main():
5054 import sys
5155 import warnings
5256
57+ # First of all, we have to delete _showwarnmsg to get into show_warning
58+ # function. The show_warning do series of calls of PyFile_WriteObject
59+ # and PyFile_WriteString functions. And one of the call is for __repr__
60+ # of warning's message. So if we change stderr while calling __repr__
61+ # (or concurently) we can get segfault from one of the consequence call
62+ # to write functions.
5363 class Foo:
5464 def __init__(self):
5565 self.x = sys.stderr
@@ -78,45 +88,28 @@ def write(self, str):
7888 pass
7989 def flush(self):
8090 pass
81- def fileno(self):
82- return 0
83-
84- class CrashStdin:
85- def __init__(self):
86- self.stdin = sys.stdin
87- setattr(sys, "stdin", FakeIO())
88-
89- def __repr__(self):
90- stdin = sys.stdin
91- setattr(sys, "stdin", self.stdin)
92- del stdin
93- return "CrashStdin"
94-
95- class CrashStdout:
96- def __init__(self):
97- self.stdout = sys.stdout
98- setattr(sys, "stdout", FakeIO())
9991
100- def __repr__(self):
101- stdout = sys.stdout
102- setattr(sys, "stdout", self.stdout)
103- del stdout
104- return "CrashStdout"
105-
106- class CrashStderr:
107- def __init__(self):
108- self.stderr = sys.stderr
109- setattr(sys, "stderr", FakeIO())
92+ # The input function gets borrowed refs for stdin, stdout and stderr.
93+ # As we use FakeIO without fileno the input functions thinks that we
94+ # are not tty and following happens:
95+ # audit is called, stderr is flushed, prompt's __repr__ is printed to
96+ # stdout and line is read from stdin. For stdin and stdout we can just
97+ # replace stdin and stdout from prompt's __repr__ and get segfault. But
98+ # for stderr we should do this from audit function.
99+ class CrashWhat:
100+ def __init__(self, what):
101+ self.what = what
102+ self.std = getattr(sys, what)
103+ setattr(sys, what, FakeIO())
110104
111105 def __repr__(self):
112- stderr = sys.stderr
113- setattr(sys, "stderr" , self.stderr )
114- del stderr
115- return "CrashStderr "
106+ std = getattr( sys, self.what)
107+ setattr(sys, self.what , self.std )
108+ del std
109+ return "Crash "
116110
117111 def audit(event, args):
118- if event == 'builtins.input':
119- repr(args)
112+ repr(args)
120113
121114 def main():
122115 {0}
@@ -134,6 +127,10 @@ class UnraisableHookInitiator:
134127 def __del__(self):
135128 raise Exception('1')
136129
130+ # To get into unraisablehook we need to raise an exception from __del__
131+ # function. So, format_unraisable_v gets hook, calls audit
132+ # function and calls hook. If we revert back unraisablehook from audit
133+ # function we will get segfault when calling hook.
137134 class UnraisableHook:
138135 def __call__(self, *args, **kwds):
139136 print('X', *args)
@@ -161,15 +158,16 @@ def main():
161158 flush_std_files_common_code = textwrap .dedent ('''
162159 import sys
163160
161+ # The flush_std_files function gets stdout and stderr. And then checks
162+ # if both of them are closed. And if so calls flush for them.
163+ # If we restore stdfile from FakeIO.closed property we can get segfault.
164164 class FakeIO:
165165 def __init__(self, what):
166166 self.what = what
167167 def write(self, str):
168168 pass
169169 def flush(self):
170170 pass
171- def fileno(self):
172- return 0
173171
174172 @property
175173 def closed(self):
@@ -188,6 +186,10 @@ def main():
188186 pyerr_printex_code = textwrap .dedent ('''
189187 import sys
190188
189+ # To get into excepthook we should run invalid statement.
190+ # Then _PyErr_PrintEx gets excepthook, calls audit function and tries
191+ # to call hook. If we replace hook from audit (or concurently) we get
192+ # segfault.
191193 class Hook:
192194 def __call__(self, *args, **kwds):
193195 pass
@@ -214,6 +216,9 @@ def main():
214216 from io import StringIO
215217 import sys
216218
219+ # The print function gets stdout and does a series of calls write
220+ # functions. One of the function calls __repr__ and if we replace
221+ # stdout from __repr__ (or concurently) we get segfault.
217222 class Bar:
218223 def __init__(self):
219224 self.x = sys.stdout
@@ -279,21 +284,21 @@ def test_warnings_warn_explicit(self):
279284 def test_input_stdin (self ):
280285 code = self .common_input_code .format (
281286 "" ,
282- "CrashStdin( )"
287+ "CrashWhat('stdin' )"
283288 )
284289 self ._check (code )
285290
286291 def test_input_stdout (self ):
287292 code = self .common_input_code .format (
288293 "" ,
289- "CrashStdout( )"
294+ "CrashWhat('stdout' )"
290295 )
291296 self ._check (code )
292297
293298 def test_input_stderr (self ):
294299 code = self .common_input_code .format (
295300 "sys.addaudithook(audit)" ,
296- "CrashStderr( )"
301+ "CrashWhat('stderr' )"
297302 )
298303 self ._check (code )
299304
0 commit comments