From 65f778799c530da60c7f72c77ba68b01bc320214 Mon Sep 17 00:00:00 2001 From: Stan Ulbrych Date: Tue, 21 Apr 2026 17:20:18 +0100 Subject: [PATCH 1/2] [3.13] gh-148801: Fix unbound C recursion in `Element.__deepcopy__()` (GH-148802) (cherry picked from commit 33e82be1746a964b595b2bba64f38a5787681eb3) Co-authored-by: Stan Ulbrych --- Lib/test/test_xml_etree.py | 13 +++++++++++ ...-04-20-18-29-21.gh-issue-148801.ROeNqs.rst | 2 ++ Modules/_elementtree.c | 23 +++++++++++++------ 3 files changed, 31 insertions(+), 7 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2026-04-20-18-29-21.gh-issue-148801.ROeNqs.rst diff --git a/Lib/test/test_xml_etree.py b/Lib/test/test_xml_etree.py index 597ec8306120e2..c136de9d387791 100644 --- a/Lib/test/test_xml_etree.py +++ b/Lib/test/test_xml_etree.py @@ -3099,6 +3099,19 @@ def __deepcopy__(self, memo): self.assertEqual([c.tag for c in children[3:]], [a.tag, b.tag, a.tag, b.tag]) + @support.skip_if_unlimited_stack_size + @support.skip_emscripten_stack_overflow() + @support.skip_wasi_stack_overflow() + def test_deeply_nested_deepcopy(self): + # This should raise a RecursionError and not crash. + # See https://github.com/python/cpython/issues/148801. + root = cur = ET.Element('s') + for _ in range(150_000): + cur = ET.SubElement(cur, 'u') + with support.infinite_recursion(): + with self.assertRaises(RecursionError): + copy.deepcopy(root) + class MutationDeleteElementPath(str): def __new__(cls, elem, *args): diff --git a/Misc/NEWS.d/next/Library/2026-04-20-18-29-21.gh-issue-148801.ROeNqs.rst b/Misc/NEWS.d/next/Library/2026-04-20-18-29-21.gh-issue-148801.ROeNqs.rst new file mode 100644 index 00000000000000..6fcd30e8f057b9 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-04-20-18-29-21.gh-issue-148801.ROeNqs.rst @@ -0,0 +1,2 @@ +:mod:`xml.etree.ElementTree`: Fix a crash in :meth:`Element.__deepcopy__ +` on deeply nested trees. diff --git a/Modules/_elementtree.c b/Modules/_elementtree.c index dfa03663c38c65..a9b24535267ce8 100644 --- a/Modules/_elementtree.c +++ b/Modules/_elementtree.c @@ -16,6 +16,7 @@ #endif #include "Python.h" +#include "pycore_ceval.h" // _Py_EnterRecursiveCall() #include "pycore_import.h" // _PyImport_GetModuleAttrString() #include "pycore_pyhash.h" // _Py_HashSecret #include "pycore_weakref.h" // FT_CLEAR_WEAKREFS() @@ -801,26 +802,31 @@ _elementtree_Element___deepcopy___impl(ElementObject *self, PyObject *memo) /*[clinic end generated code: output=eefc3df50465b642 input=a2d40348c0aade10]*/ { Py_ssize_t i; - ElementObject* element; + ElementObject* element = NULL; PyObject* tag; PyObject* attrib; PyObject* text; PyObject* tail; PyObject* id; + if (_Py_EnterRecursiveCall(" in Element.__deepcopy__")) { + return NULL; + } + PyTypeObject *tp = Py_TYPE(self); elementtreestate *st = get_elementtree_state_by_type(tp); // The deepcopy() helper takes care of incrementing the refcount // of the object to copy so to avoid use-after-frees. tag = deepcopy(st, self->tag, memo); - if (!tag) - return NULL; + if (!tag) { + goto error; + } if (self->extra && self->extra->attrib) { attrib = deepcopy(st, self->extra->attrib, memo); if (!attrib) { Py_DECREF(tag); - return NULL; + goto error; } } else { attrib = NULL; @@ -831,8 +837,9 @@ _elementtree_Element___deepcopy___impl(ElementObject *self, PyObject *memo) Py_DECREF(tag); Py_XDECREF(attrib); - if (!element) - return NULL; + if (!element) { + goto error; + } text = deepcopy(st, JOIN_OBJ(self->text), memo); if (!text) @@ -894,10 +901,12 @@ _elementtree_Element___deepcopy___impl(ElementObject *self, PyObject *memo) if (i < 0) goto error; + _Py_LeaveRecursiveCall(); return (PyObject*) element; error: - Py_DECREF(element); + _Py_LeaveRecursiveCall(); + Py_XDECREF(element); return NULL; } From c9a54ecb4ed9cf4df9c60e579bdd2b204e1e828e Mon Sep 17 00:00:00 2001 From: Stan Ulbrych Date: Wed, 22 Apr 2026 22:57:34 +0100 Subject: [PATCH 2/2] Remove unsupported decorators --- Lib/test/test_xml_etree.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/Lib/test/test_xml_etree.py b/Lib/test/test_xml_etree.py index c136de9d387791..cc1f9df1ba2f27 100644 --- a/Lib/test/test_xml_etree.py +++ b/Lib/test/test_xml_etree.py @@ -3099,9 +3099,6 @@ def __deepcopy__(self, memo): self.assertEqual([c.tag for c in children[3:]], [a.tag, b.tag, a.tag, b.tag]) - @support.skip_if_unlimited_stack_size - @support.skip_emscripten_stack_overflow() - @support.skip_wasi_stack_overflow() def test_deeply_nested_deepcopy(self): # This should raise a RecursionError and not crash. # See https://github.com/python/cpython/issues/148801.