Skip to content

Commit 312cbed

Browse files
BHUVANSH855miss-islington
authored andcommitted
gh-156204: Guard recursion in PyErr_GivenExceptionMatches (GH-156205)
(cherry picked from commit 20d5e67) Co-authored-by: Bhuvansh <bhuvanshkataria@gmail.com>
1 parent 03bb26f commit 312cbed

5 files changed

Lines changed: 102 additions & 10 deletions

File tree

‎Lib/test/test_exceptions.py‎

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2630,6 +2630,32 @@ def test_except_star_invalid_exception_type(self):
26302630
except (ValueError, 42):
26312631
pass
26322632

2633+
@cpython_only
2634+
@unittest.skipIf(_testcapi is None, "requires _testcapi")
2635+
def test_given_exception_matches_nested_tuple(self):
2636+
# Nested tuples are searched recursively.
2637+
self.assertTrue(
2638+
_testcapi.err_givenexceptionmatches(ValueError(), ((ValueError,),)))
2639+
self.assertFalse(
2640+
_testcapi.err_givenexceptionmatches(TypeError(), ((ValueError,),)))
2641+
2642+
@cpython_only
2643+
@unittest.skipIf(_testcapi is None, "requires _testcapi")
2644+
@support.skip_emscripten_stack_overflow()
2645+
@support.skip_wasi_stack_overflow()
2646+
@support.run_with_limited_c_stack(depth=500_000)
2647+
def test_given_exception_matches_deeply_nested_tuple(self):
2648+
# gh-156204: PyErr_GivenExceptionMatches() used to exhaust the C stack
2649+
# and crash the interpreter on deeply nested tuples of exception types.
2650+
tup = (ValueError,)
2651+
for _ in range(500_000):
2652+
tup = (tup,)
2653+
2654+
with support.catch_unraisable_exception() as cm:
2655+
self.assertFalse(
2656+
_testcapi.err_givenexceptionmatches(ValueError(), tup))
2657+
self.assertIsInstance(cm.unraisable.exc_value, RecursionError)
2658+
26332659

26342660
class PEP626Tests(unittest.TestCase):
26352661

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fix a crash in :c:func:`PyErr_GivenExceptionMatches` when evaluating deeply
2+
nested exception tuples. The recursion is now bounded, and exceeding the
3+
limit is reported as an unraisable exception.

‎Modules/_testcapi/clinic/exceptions.c.h‎

Lines changed: 32 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎Modules/_testcapi/exceptions.c‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,26 @@ err_restore(PyObject *self, PyObject *args) {
5454
return NULL;
5555
}
5656

57+
/*[clinic input]
58+
_testcapi.err_givenexceptionmatches
59+
err: object
60+
exc: object
61+
/
62+
63+
Test PyErr_GivenExceptionMatches().
64+
[clinic start generated code]*/
65+
66+
static PyObject *
67+
_testcapi_err_givenexceptionmatches_impl(PyObject *module, PyObject *err,
68+
PyObject *exc)
69+
/*[clinic end generated code: output=e40994ab6dd75001 input=7b8ef542df07575b]*/
70+
{
71+
assert(!PyErr_Occurred());
72+
int res = PyErr_GivenExceptionMatches(err, exc);
73+
assert(!PyErr_Occurred());
74+
return PyBool_FromLong(res);
75+
}
76+
5777
/*[clinic input]
5878
_testcapi.exception_print
5979
exception as exc: object
@@ -552,6 +572,7 @@ static PyMethodDef test_methods[] = {
552572
_TESTCAPI_MAKE_EXCEPTION_WITH_DOC_METHODDEF
553573
_TESTCAPI_EXC_SET_OBJECT_METHODDEF
554574
_TESTCAPI_EXC_SET_OBJECT_FETCH_METHODDEF
575+
_TESTCAPI_ERR_GIVENEXCEPTIONMATCHES_METHODDEF
555576
_TESTCAPI_ERR_SETSTRING_METHODDEF
556577
_TESTCAPI_ERR_SETFROMERRNOWITHFILENAME_METHODDEF
557578
_TESTCAPI_RAISE_EXCEPTION_METHODDEF

‎Python/errors.c‎

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#include "Python.h"
55
#include "pycore_audit.h" // _PySys_Audit()
66
#include "pycore_call.h" // _PyObject_CallNoArgs()
7+
#include "pycore_ceval.h" // _Py_ReachedRecursionLimitWithMargin()
78
#include "pycore_fileutils.h" // _PyFile_Flush
89
#include "pycore_initconfig.h" // _PyStatus_ERR()
910
#include "pycore_pyerrors.h" // _PyErr_Format()
@@ -335,17 +336,27 @@ PyErr_GivenExceptionMatches(PyObject *err, PyObject *exc)
335336
return 0;
336337
}
337338
if (PyTuple_Check(exc)) {
338-
Py_ssize_t i, n;
339-
n = PyTuple_Size(exc);
340-
for (i = 0; i < n; i++) {
339+
PyThreadState *tstate = _PyThreadState_GET();
340+
if (_Py_ReachedRecursionLimitWithMargin(tstate, 2)) {
341+
PyObject *exc_value = _PyErr_GetRaisedException(tstate);
342+
_PyErr_SetString(tstate, PyExc_RecursionError,
343+
"maximum recursion depth exceeded while "
344+
"checking exception tuple");
345+
PyErr_FormatUnraisable("Exception ignored while "
346+
"checking exception tuple");
347+
_PyErr_SetRaisedException(tstate, exc_value);
348+
return 0;
349+
}
350+
int res = 0;
351+
Py_ssize_t n = PyTuple_GET_SIZE(exc);
352+
for (Py_ssize_t i = 0; i < n; i++) {
341353
/* Test recursively */
342-
if (PyErr_GivenExceptionMatches(
343-
err, PyTuple_GET_ITEM(exc, i)))
344-
{
345-
return 1;
346-
}
354+
if (PyErr_GivenExceptionMatches(err, PyTuple_GET_ITEM(exc, i))) {
355+
res = 1;
356+
break;
357+
}
347358
}
348-
return 0;
359+
return res;
349360
}
350361
/* err might be an instance, so check its class. */
351362
if (PyExceptionInstance_Check(err))

0 commit comments

Comments
 (0)