From 9aee99be160370fc9d35566d28532f29bbaafd71 Mon Sep 17 00:00:00 2001 From: Brett Cannon Date: Sat, 20 Mar 2004 21:54:35 +0000 Subject: Raise RuntimeError if the second argument to isinstance() or issubclass() is a tuple nested to a depth beyond the interpreter's recursion limit to prevent a segfault from blowing the C stack. Fixes bug #858016 . --- Lib/test/test_isinstance.py | 19 +++++++++++++++ Misc/NEWS | 6 ++++- Objects/abstract.c | 57 ++++++++++++++++++++++++++++++++++++++------- 3 files changed, 72 insertions(+), 10 deletions(-) diff --git a/Lib/test/test_isinstance.py b/Lib/test/test_isinstance.py index 1b8c593..7e297f0 100644 --- a/Lib/test/test_isinstance.py +++ b/Lib/test/test_isinstance.py @@ -4,6 +4,7 @@ import unittest from test import test_support +import sys @@ -244,6 +245,24 @@ class TestIsInstanceIsSubclass(unittest.TestCase): self.assertEqual(True, issubclass(int, (long, (float, int)))) self.assertEqual(True, issubclass(str, (unicode, (Child, NewChild, basestring)))) + def test_subclass_recursion_limit(self): + # make sure that issubclass raises RuntimeError before the C stack is + # blown + self.assertRaises(RuntimeError, blowstack, issubclass, str, str) + + def test_isinstance_recursion_limit(self): + # make sure that issubclass raises RuntimeError before the C stack is + # blown + self.assertRaises(RuntimeError, blowstack, isinstance, '', str) + +def blowstack(fxn, arg, compare_to): + # Make sure that calling isinstance with a deeply nested tuple for its + # argument will raise RuntimeError eventually. + tuple_arg = (compare_to,) + for cnt in xrange(sys.getrecursionlimit()+5): + tuple_arg = (tuple_arg,) + fxn(arg, tuple_arg) + diff --git a/Misc/NEWS b/Misc/NEWS index 1559943..e40e66c 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -34,7 +34,11 @@ Core and builtins Library ------- -- bug 700055: .pth files can now have any type of line endings. +- Bug #858016: isinstance() and issubclass() can have their second + argument be a tuple whose nested depth is capped at the interpreter's + recursion limit. Raises RuntimeError if the limit reached. + +- Bug #700055: .pth files can now have any type of line endings. - Patch 817379: Allow absolute ftp paths in urllib2. diff --git a/Objects/abstract.c b/Objects/abstract.c index 36c1608..13185d8 100644 --- a/Objects/abstract.c +++ b/Objects/abstract.c @@ -1997,6 +1997,8 @@ abstract_issubclass(PyObject *derived, PyObject *cls) if (PyTuple_Check(cls)) { /* Not a general sequence -- that opens up the road to recursion and stack overflow. */ + /* XXX: really an issue even though no subsequences get + iterated over? */ n = PyTuple_GET_SIZE(cls); for (i = 0; i < n; i++) { if (derived == PyTuple_GET_ITEM(cls, i)) @@ -2035,8 +2037,8 @@ check_class(PyObject *cls, const char *error) return -1; } -int -PyObject_IsInstance(PyObject *inst, PyObject *cls) +static int +recursive_isinstance(PyObject *inst, PyObject *cls, int recursion_depth) { PyObject *icls; static PyObject *__class__ = NULL; @@ -2071,14 +2073,20 @@ PyObject_IsInstance(PyObject *inst, PyObject *cls) } } else if (PyTuple_Check(cls)) { - /* Not a general sequence -- that opens up the road to - recursion and stack overflow. */ int i, n; + if (!recursion_depth) { + PyErr_SetString(PyExc_RuntimeError, + "Recursion depth exceeded"); + return NULL; + } + n = PyTuple_GET_SIZE(cls); for (i = 0; i < n; i++) { - retval = PyObject_IsInstance( - inst, PyTuple_GET_ITEM(cls, i)); + retval = recursive_isinstance( + inst, + PyTuple_GET_ITEM(cls, i), + recursion_depth-1); if (retval != 0) break; } @@ -2102,8 +2110,19 @@ PyObject_IsInstance(PyObject *inst, PyObject *cls) return retval; } +/* Use recursive_isinstance to have a hard limit on the depth of the possible + tuple second argument. + + Done to prevent segfaulting by blowing the C stack. +*/ int -PyObject_IsSubclass(PyObject *derived, PyObject *cls) +PyObject_IsInstance(PyObject *inst, PyObject *cls) +{ + return recursive_isinstance(inst, cls, Py_GetRecursionLimit()); +} + +static int +recursive_issubclass(PyObject *derived, PyObject *cls, int recursion_depth) { int retval; @@ -2115,9 +2134,18 @@ PyObject_IsSubclass(PyObject *derived, PyObject *cls) if (PyTuple_Check(cls)) { int i; int n = PyTuple_GET_SIZE(cls); + + if (!recursion_depth) { + PyErr_SetString(PyExc_RuntimeError, + "Recursion depth exceeded"); + return NULL; + } + for (i = 0; i < n; ++i) { - retval = PyObject_IsSubclass( - derived, PyTuple_GET_ITEM(cls, i)); + retval = recursive_issubclass( + derived, + PyTuple_GET_ITEM(cls, i), + recursion_depth-1); if (retval != 0) { /* either found it, or got an error */ return retval; @@ -2143,6 +2171,17 @@ PyObject_IsSubclass(PyObject *derived, PyObject *cls) return retval; } +/* Use recursive_issubclass to have a hard limit on the depth of the possible + tuple second argument. + + Done to prevent segfaulting by blowing the C stack. +*/ +int +PyObject_IsSubclass(PyObject *derived, PyObject *cls) +{ + return recursive_issubclass(derived, cls, Py_GetRecursionLimit()); +} + PyObject * PyObject_GetIter(PyObject *o) { -- cgit v0.12