diff --git a/Doc/whatsnew/3.15.rst b/Doc/whatsnew/3.15.rst index 5689ecbffc4b30..69283f67487b18 100644 --- a/Doc/whatsnew/3.15.rst +++ b/Doc/whatsnew/3.15.rst @@ -1358,7 +1358,7 @@ The JIT avoids :term:`reference count`\ s where possible. This generally reduces the cost of most operations in Python. (Contributed by Ken Jin, Donghee Na, Zheao Li, Hai Zhu, Savannah Ostrowski, -Reiden Ong, Noam Cohen, Tomas Roun, PuQing, and Cajetan Rodrigues in :gh:`134584`.) +Reiden Ong, Noam Cohen, Tomas Roun, PuQing, Cajetan Rodrigues, and Sacul in :gh:`134584`.) .. rubric:: Better machine code generation diff --git a/Lib/test/test_pyexpat.py b/Lib/test/test_pyexpat.py index c67bfc67479985..5588bc5431317d 100644 --- a/Lib/test/test_pyexpat.py +++ b/Lib/test/test_pyexpat.py @@ -276,6 +276,42 @@ def test_parse_again(self): self.assertEqual(expat.ErrorString(cm.exception.code), expat.errors.XML_ERROR_FINISHED) + def test_reentrant_parse_crash(self): + from xml.parsers import expat + + 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)): + try: + p.Parse(data[i:i+1], i == len(data) - 1) + except Exception as e: + raise + + self.assertEqual(str(cm.exception), + "cannot call Parse() from within a handler") + + + def test_parse_normal(self): + from xml.parsers import expat + + 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-08-34-32.gh-issue-146174.Fhubb3.rst b/Misc/NEWS.d/next/Library/2026-03-19-08-34-32.gh-issue-146174.Fhubb3.rst new file mode 100644 index 00000000000000..f74184f01367ec --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-03-19-08-34-32.gh-issue-146174.Fhubb3.rst @@ -0,0 +1,8 @@ +.. gh-issue: 146174 + +.. section: Library + +Prevent re-entrant calls to +:meth:`xml.parsers.expat.xmlparser.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..ca07dfa504d0cc 100644 --- a/Modules/pyexpat.c +++ b/Modules/pyexpat.c @@ -857,6 +857,11 @@ 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;