diff --git a/Lib/test/test_pyexpat.py b/Lib/test/test_pyexpat.py index c67bfc67479985..193d7f482e7fd4 100644 --- a/Lib/test/test_pyexpat.py +++ b/Lib/test/test_pyexpat.py @@ -276,6 +276,30 @@ def test_parse_again(self): self.assertEqual(expat.ErrorString(cm.exception.code), expat.errors.XML_ERROR_FINISHED) + def test_reentrant_parse_crash(self): + p = expat.ParserCreate(encoding="utf-16") + + def start(name, attrs): + def handler(data): + p.Parse(data, 0) + p.CharacterDataHandler = handler + + p.StartElementHandler = start + data = b"\xff\xfe<\x00a\x00>\x00x\x00" + with self.assertRaises(RuntimeError) as cm: + for i in range(len(data)): + p.Parse(data[i:i+1], i == len(data) - 1) + self.assertEqual(str(cm.exception), + "cannot call Parse() from within a handler") + + def test_parse_normal(self): + p = expat.ParserCreate() + data = "".encode('utf-8') + try: + p.Parse(data, 1) + except RuntimeError: + self.fail("Parse() raised RuntimeError during normal operation") + class NamespaceSeparatorTest(unittest.TestCase): def test_legal(self): # Tests that make sure we get errors when the namespace_separator value diff --git a/Misc/NEWS.d/next/Library/2026-03-19-11-15-00.gh-issue-146169.manual.rst b/Misc/NEWS.d/next/Library/2026-03-19-11-15-00.gh-issue-146169.manual.rst new file mode 100644 index 00000000000000..4ac7a6b5985aa3 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-03-19-11-15-00.gh-issue-146169.manual.rst @@ -0,0 +1,3 @@ +Prevent re-entrant calls to ``Parse()`` from within expat handlers, +which could cause a crash. Now raises :exc:`RuntimeError` when such a +call is attempted. diff --git a/Modules/pyexpat.c b/Modules/pyexpat.c index 782e552f342b17..2e4e17e148dddc 100644 --- a/Modules/pyexpat.c +++ b/Modules/pyexpat.c @@ -857,6 +857,13 @@ pyexpat_xmlparser_Parse_impl(xmlparseobject *self, PyTypeObject *cls, PyObject *data, int isfinal) /*[clinic end generated code: output=8faffe07fe1f862a input=053e0f047e55c05a]*/ { + + if (self->in_callback) { + PyErr_SetString(PyExc_RuntimeError, + "cannot call Parse() from within a handler"); + return NULL; + } + const char *s; Py_ssize_t slen; Py_buffer view;