Skip to content

Commit 1e534b5

Browse files
committed
Fix a crasher where Python code managed to infinitely recurse in C code without
ever going back out to Python code in PyObject_Call(). Required introducing a static RuntimeError instance so that normalizing an exception there is no reliance on a recursive call that would put the exception system over the recursion check itself.
1 parent 68a6da9 commit 1e534b5

File tree

11 files changed

+66
-49
lines changed

11 files changed

+66
-49
lines changed

Include/pyerrors.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,7 @@ PyAPI_DATA(PyObject *) PyExc_VMSError;
161161
#endif
162162

163163
PyAPI_DATA(PyObject *) PyExc_MemoryErrorInst;
164+
PyAPI_DATA(PyObject *) PyExc_RecursionErrorInst;
164165

165166
/* Predefined warning categories */
166167
PyAPI_DATA(PyObject *) PyExc_Warning;

Lib/test/crashers/infinite_rec_1.py

Lines changed: 0 additions & 11 deletions
This file was deleted.

Lib/test/crashers/infinite_rec_2.py

Lines changed: 0 additions & 10 deletions
This file was deleted.

Lib/test/crashers/infinite_rec_4.py

Lines changed: 0 additions & 7 deletions
This file was deleted.

Lib/test/crashers/infinite_rec_5.py

Lines changed: 0 additions & 10 deletions
This file was deleted.

Lib/test/test_descr.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from copy import deepcopy
55
import warnings
66
import types
7+
import new
78

89
warnings.filterwarnings("ignore",
910
r'complex divmod\(\), // and % are deprecated$',
@@ -1981,6 +1982,10 @@ def unsafecmp(a, b):
19811982
unsafecmp(1, 1L)
19821983
unsafecmp(1L, 1)
19831984

1985+
def recursions():
1986+
if verbose:
1987+
print "Testing recursion checks ..."
1988+
19841989
class Letter(str):
19851990
def __new__(cls, letter):
19861991
if letter == 'EPS':
@@ -1990,7 +1995,6 @@ def __str__(self):
19901995
if not self:
19911996
return 'EPS'
19921997
return self
1993-
19941998
# sys.stdout needs to be the original to trigger the recursion bug
19951999
import sys
19962000
test_stdout = sys.stdout
@@ -2004,6 +2008,17 @@ def __str__(self):
20042008
raise TestFailed, "expected a RuntimeError for print recursion"
20052009
sys.stdout = test_stdout
20062010

2011+
# Bug #1202533.
2012+
class A(object):
2013+
pass
2014+
A.__mul__ = new.instancemethod(lambda self, x: self * x, None, A)
2015+
try:
2016+
A()*2
2017+
except RuntimeError:
2018+
pass
2019+
else:
2020+
raise TestFailed("expected a RuntimeError")
2021+
20072022
def weakrefs():
20082023
if verbose: print "Testing weak references..."
20092024
import weakref
@@ -4395,6 +4410,7 @@ def test_main():
43954410
overloading()
43964411
methods()
43974412
specials()
4413+
recursions()
43984414
weakrefs()
43994415
properties()
44004416
supers()

Misc/NEWS

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,12 @@ What's New in Python 2.6 alpha 1?
1212
Core and builtins
1313
-----------------
1414

15+
- Issue #1202533: Fix infinite recursion calls triggered by calls to
16+
PyObject_Call() never calling back out to Python code to trigger recursion
17+
depth updates/checks. Required the creation of a static RuntimeError
18+
instance in case normalizing an exception put the recursion check value past
19+
its limit. Fixes crashers infinite_rec_(1|2|4|5).py.
20+
1521
- Patch #1031213: Decode source line in SyntaxErrors back to its original source
1622
encoding.
1723

Objects/abstract.c

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1857,7 +1857,11 @@ PyObject_Call(PyObject *func, PyObject *arg, PyObject *kw)
18571857
ternaryfunc call;
18581858

18591859
if ((call = func->ob_type->tp_call) != NULL) {
1860-
PyObject *result = (*call)(func, arg, kw);
1860+
PyObject *result;
1861+
if (Py_EnterRecursiveCall(" while calling a Python object"))
1862+
return NULL;
1863+
result = (*call)(func, arg, kw);
1864+
Py_LeaveRecursiveCall();
18611865
if (result == NULL && !PyErr_Occurred())
18621866
PyErr_SetString(
18631867
PyExc_SystemError,

Objects/exceptions.c

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1912,6 +1912,12 @@ SimpleExtendsException(PyExc_Warning, UnicodeWarning,
19121912
*/
19131913
PyObject *PyExc_MemoryErrorInst=NULL;
19141914

1915+
/* Pre-computed RuntimeError instance for when recursion depth is reached.
1916+
Meant to be used when normalizing the exception for exceeding the recursion
1917+
depth will cause its own infinite recursion.
1918+
*/
1919+
PyObject *PyExc_RecursionErrorInst = NULL;
1920+
19151921
/* module global functions */
19161922
static PyMethodDef functions[] = {
19171923
/* Sentinel */
@@ -2079,6 +2085,29 @@ _PyExc_Init(void)
20792085
if (!PyExc_MemoryErrorInst)
20802086
Py_FatalError("Cannot pre-allocate MemoryError instance\n");
20812087

2088+
PyExc_RecursionErrorInst = BaseException_new(&_PyExc_RuntimeError, NULL, NULL);
2089+
if (!PyExc_RecursionErrorInst)
2090+
Py_FatalError("Cannot pre-allocate RuntimeError instance for "
2091+
"recursion errors");
2092+
else {
2093+
PyBaseExceptionObject *err_inst =
2094+
(PyBaseExceptionObject *)PyExc_RecursionErrorInst;
2095+
PyObject *args_tuple;
2096+
PyObject *exc_message;
2097+
exc_message = PyString_FromString("maximum recursion depth exceeded");
2098+
if (!exc_message)
2099+
Py_FatalError("cannot allocate argument for RuntimeError "
2100+
"pre-allocation");
2101+
args_tuple = PyTuple_Pack(1, exc_message);
2102+
if (!args_tuple)
2103+
Py_FatalError("cannot allocate tuple for RuntimeError "
2104+
"pre-allocation");
2105+
Py_DECREF(exc_message);
2106+
if (BaseException_init(err_inst, args_tuple, NULL))
2107+
Py_FatalError("init of pre-allocated RuntimeError failed");
2108+
Py_DECREF(args_tuple);
2109+
}
2110+
20822111
Py_DECREF(bltinmod);
20832112

20842113
#if defined _MSC_VER && _MSC_VER >= 1400 && defined(__STDC_SECURE_LIB__)

Objects/typeobject.c

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4854,16 +4854,7 @@ slot_tp_call(PyObject *self, PyObject *args, PyObject *kwds)
48544854
if (meth == NULL)
48554855
return NULL;
48564856

4857-
/* PyObject_Call() will end up calling slot_tp_call() again if
4858-
the object returned for __call__ has __call__ itself defined
4859-
upon it. This can be an infinite recursion if you set
4860-
__call__ in a class to an instance of it. */
4861-
if (Py_EnterRecursiveCall(" in __call__")) {
4862-
Py_DECREF(meth);
4863-
return NULL;
4864-
}
48654857
res = PyObject_Call(meth, args, kwds);
4866-
Py_LeaveRecursiveCall();
48674858

48684859
Py_DECREF(meth);
48694860
return res;

Python/errors.c

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ PyErr_NormalizeException(PyObject **exc, PyObject **val, PyObject **tb)
132132
PyObject *value = *val;
133133
PyObject *inclass = NULL;
134134
PyObject *initial_tb = NULL;
135+
PyThreadState *tstate = NULL;
135136

136137
if (type == NULL) {
137138
/* There was no exception, so nothing to do. */
@@ -207,7 +208,14 @@ PyErr_NormalizeException(PyObject **exc, PyObject **val, PyObject **tb)
207208
Py_DECREF(initial_tb);
208209
}
209210
/* normalize recursively */
211+
tstate = PyThreadState_GET();
212+
if (++tstate->recursion_depth > Py_GetRecursionLimit()) {
213+
--tstate->recursion_depth;
214+
PyErr_SetObject(PyExc_RuntimeError, PyExc_RecursionErrorInst);
215+
return;
216+
}
210217
PyErr_NormalizeException(exc, val, tb);
218+
--tstate->recursion_depth;
211219
}
212220

213221

0 commit comments

Comments
 (0)