From afa76bfcbed706926c8bab6607ed3eb55726b1fd Mon Sep 17 00:00:00 2001 From: "Gregory P. Smith" Date: Sun, 17 Oct 2021 18:50:43 -0700 Subject: [PATCH] bpo-30570: issubclass could crash due to infinite recursion on the values code emits for `__bases__`. --- Lib/test/test_isinstance.py | 8 ++++++++ Objects/abstract.c | 37 +++++++++++++++++++++++++++++++++++-- 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_isinstance.py b/Lib/test/test_isinstance.py index 109c3f84a5c426..f3238270258859 100644 --- a/Lib/test/test_isinstance.py +++ b/Lib/test/test_isinstance.py @@ -313,6 +313,14 @@ def __bases__(self): self.assertRaises(RecursionError, issubclass, int, X()) self.assertRaises(RecursionError, isinstance, 1, X()) + def test_infinite_recursion_via_bases_tuple(self): + """Regression test for bpo-30570.""" + class Failure(object): + def __getattr__(self, attr): + return (self, None) + + self.assertFalse(issubclass(Failure(), int)) + def blowstack(fxn, arg, compare_to): # Make sure that calling isinstance with a deeply nested tuple for its diff --git a/Objects/abstract.c b/Objects/abstract.c index 6f7b94600e278a..4dc602b79e45d5 100644 --- a/Objects/abstract.c +++ b/Objects/abstract.c @@ -2525,7 +2525,8 @@ abstract_get_bases(PyObject *cls) static int -abstract_issubclass(PyObject *derived, PyObject *cls) +_abstract_issubclass_inner(PyObject *derived, PyObject *cls, + PyObject* visited_set) { PyObject *bases = NULL; Py_ssize_t i, n; @@ -2558,7 +2559,24 @@ abstract_issubclass(PyObject *derived, PyObject *cls) continue; } for (i = 0; i < n; i++) { - r = abstract_issubclass(PyTuple_GET_ITEM(bases, i), cls); + PyObject *borrowed_base = PyTuple_GET_ITEM(bases, i); + PyObject *visitor = PyLong_FromVoidPtr(borrowed_base); + if (visitor == NULL) { + Py_DECREF(bases); + return -1; + } + if (PySet_Contains(visited_set, visitor)) { + Py_DECREF(visitor); + continue; + } + if (PySet_Add(visited_set, visitor) < 0) { + Py_DECREF(visitor); + Py_DECREF(bases); + return -1; + } + Py_DECREF(visitor); // PySet_Add does its own INCREF. + r = _abstract_issubclass_inner( + borrowed_base, cls, visited_set); if (r != 0) break; } @@ -2567,6 +2585,21 @@ abstract_issubclass(PyObject *derived, PyObject *cls) } } +static int +abstract_issubclass(PyObject *derived, PyObject *cls) +{ + int result; + /* I would much rather use a C++ hash_map but we're stuck in C so + * we'll use our own high overhead for the purpose data structure. */ + PyObject *visited_set = PySet_New(NULL); + if (visited_set == NULL) { + return -1; + } + result = _abstract_issubclass_inner(derived, cls, visited_set); + Py_DECREF(visited_set); + return result; +} + static int check_class(PyObject *cls, const char *error) {