From c924f5760b7325fd3180b86608911e51e864a612 Mon Sep 17 00:00:00 2001 From: Okiemute Date: Thu, 19 Mar 2026 09:50:33 -0700 Subject: [PATCH 1/3] gh-146174: Prevent re-entrant Parse() calls in pyexpat Add a check in Parse() to prevent calls when in_callback is true, as this violates expat's requirements and can cause crashes. Now raises RuntimeError with a clear message. Add tests to verify the fix and ensure normal parsing still works. --- Lib/test/test_pyexpat.py | 26 +++++++++++++++++++ ...-03-19-10-30-20.gh-issue-146169.NXUmTH.rst | 9 +++++++ Modules/pyexpat.c | 7 +++++ 3 files changed, 42 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2026-03-19-10-30-20.gh-issue-146169.NXUmTH.rst diff --git a/Lib/test/test_pyexpat.py b/Lib/test/test_pyexpat.py index c67bfc67479985..f422e38ce70bc6 100644 --- a/Lib/test/test_pyexpat.py +++ b/Lib/test/test_pyexpat.py @@ -276,6 +276,32 @@ 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)): + 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): + 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-10-30-20.gh-issue-146169.NXUmTH.rst b/Misc/NEWS.d/next/Library/2026-03-19-10-30-20.gh-issue-146169.NXUmTH.rst new file mode 100644 index 00000000000000..195fb1eb82d28a --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-03-19-10-30-20.gh-issue-146169.NXUmTH.rst @@ -0,0 +1,9 @@ +.. gh-issue: 146169 + +.. section: Library + +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; From 0e6705285e6b7538a15b8bc0116a27216496293d Mon Sep 17 00:00:00 2001 From: Okiemute Date: Thu, 19 Mar 2026 10:43:40 -0700 Subject: [PATCH 2/3] Add NEWS entry for pyexpat re-entrant fix --- .../2026-03-19-10-30-20.gh-issue-146169.NXUmTH.rst | 9 --------- .../2026-03-19-11-15-00.gh-issue-146169.manual.rst | 3 +++ 2 files changed, 3 insertions(+), 9 deletions(-) delete mode 100644 Misc/NEWS.d/next/Library/2026-03-19-10-30-20.gh-issue-146169.NXUmTH.rst create mode 100644 Misc/NEWS.d/next/Library/2026-03-19-11-15-00.gh-issue-146169.manual.rst diff --git a/Misc/NEWS.d/next/Library/2026-03-19-10-30-20.gh-issue-146169.NXUmTH.rst b/Misc/NEWS.d/next/Library/2026-03-19-10-30-20.gh-issue-146169.NXUmTH.rst deleted file mode 100644 index 195fb1eb82d28a..00000000000000 --- a/Misc/NEWS.d/next/Library/2026-03-19-10-30-20.gh-issue-146169.NXUmTH.rst +++ /dev/null @@ -1,9 +0,0 @@ -.. gh-issue: 146169 - -.. section: Library - -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/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. From 029e408ece9727fcd29a18983fc6409ed86419b8 Mon Sep 17 00:00:00 2001 From: Okiemute Date: Thu, 19 Mar 2026 10:43:40 -0700 Subject: [PATCH 3/3] Add NEWS entry for pyexpat re-entrant fix --- Lib/test/test_pyexpat.py | 2 -- .../2026-03-19-10-30-20.gh-issue-146169.NXUmTH.rst | 9 --------- .../2026-03-19-11-15-00.gh-issue-146169.manual.rst | 3 +++ 3 files changed, 3 insertions(+), 11 deletions(-) delete mode 100644 Misc/NEWS.d/next/Library/2026-03-19-10-30-20.gh-issue-146169.NXUmTH.rst create mode 100644 Misc/NEWS.d/next/Library/2026-03-19-11-15-00.gh-issue-146169.manual.rst diff --git a/Lib/test/test_pyexpat.py b/Lib/test/test_pyexpat.py index f422e38ce70bc6..193d7f482e7fd4 100644 --- a/Lib/test/test_pyexpat.py +++ b/Lib/test/test_pyexpat.py @@ -277,7 +277,6 @@ def test_parse_again(self): 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): @@ -294,7 +293,6 @@ def handler(data): "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: diff --git a/Misc/NEWS.d/next/Library/2026-03-19-10-30-20.gh-issue-146169.NXUmTH.rst b/Misc/NEWS.d/next/Library/2026-03-19-10-30-20.gh-issue-146169.NXUmTH.rst deleted file mode 100644 index 195fb1eb82d28a..00000000000000 --- a/Misc/NEWS.d/next/Library/2026-03-19-10-30-20.gh-issue-146169.NXUmTH.rst +++ /dev/null @@ -1,9 +0,0 @@ -.. gh-issue: 146169 - -.. section: Library - -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/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.