Skip to content

Commit 65f7787

Browse files
[3.13] gh-148801: Fix unbound C recursion in Element.__deepcopy__() (GH-148802)
(cherry picked from commit 33e82be) Co-authored-by: Stan Ulbrych <stan@python.org>
1 parent 95633d2 commit 65f7787

3 files changed

Lines changed: 31 additions & 7 deletions

File tree

Lib/test/test_xml_etree.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3099,6 +3099,19 @@ def __deepcopy__(self, memo):
30993099
self.assertEqual([c.tag for c in children[3:]],
31003100
[a.tag, b.tag, a.tag, b.tag])
31013101

3102+
@support.skip_if_unlimited_stack_size
3103+
@support.skip_emscripten_stack_overflow()
3104+
@support.skip_wasi_stack_overflow()
3105+
def test_deeply_nested_deepcopy(self):
3106+
# This should raise a RecursionError and not crash.
3107+
# See https://github.com/python/cpython/issues/148801.
3108+
root = cur = ET.Element('s')
3109+
for _ in range(150_000):
3110+
cur = ET.SubElement(cur, 'u')
3111+
with support.infinite_recursion():
3112+
with self.assertRaises(RecursionError):
3113+
copy.deepcopy(root)
3114+
31023115

31033116
class MutationDeleteElementPath(str):
31043117
def __new__(cls, elem, *args):
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
:mod:`xml.etree.ElementTree`: Fix a crash in :meth:`Element.__deepcopy__
2+
<object.__deepcopy__>` on deeply nested trees.

Modules/_elementtree.c

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#endif
1717

1818
#include "Python.h"
19+
#include "pycore_ceval.h" // _Py_EnterRecursiveCall()
1920
#include "pycore_import.h" // _PyImport_GetModuleAttrString()
2021
#include "pycore_pyhash.h" // _Py_HashSecret
2122
#include "pycore_weakref.h" // FT_CLEAR_WEAKREFS()
@@ -801,26 +802,31 @@ _elementtree_Element___deepcopy___impl(ElementObject *self, PyObject *memo)
801802
/*[clinic end generated code: output=eefc3df50465b642 input=a2d40348c0aade10]*/
802803
{
803804
Py_ssize_t i;
804-
ElementObject* element;
805+
ElementObject* element = NULL;
805806
PyObject* tag;
806807
PyObject* attrib;
807808
PyObject* text;
808809
PyObject* tail;
809810
PyObject* id;
810811

812+
if (_Py_EnterRecursiveCall(" in Element.__deepcopy__")) {
813+
return NULL;
814+
}
815+
811816
PyTypeObject *tp = Py_TYPE(self);
812817
elementtreestate *st = get_elementtree_state_by_type(tp);
813818
// The deepcopy() helper takes care of incrementing the refcount
814819
// of the object to copy so to avoid use-after-frees.
815820
tag = deepcopy(st, self->tag, memo);
816-
if (!tag)
817-
return NULL;
821+
if (!tag) {
822+
goto error;
823+
}
818824

819825
if (self->extra && self->extra->attrib) {
820826
attrib = deepcopy(st, self->extra->attrib, memo);
821827
if (!attrib) {
822828
Py_DECREF(tag);
823-
return NULL;
829+
goto error;
824830
}
825831
} else {
826832
attrib = NULL;
@@ -831,8 +837,9 @@ _elementtree_Element___deepcopy___impl(ElementObject *self, PyObject *memo)
831837
Py_DECREF(tag);
832838
Py_XDECREF(attrib);
833839

834-
if (!element)
835-
return NULL;
840+
if (!element) {
841+
goto error;
842+
}
836843

837844
text = deepcopy(st, JOIN_OBJ(self->text), memo);
838845
if (!text)
@@ -894,10 +901,12 @@ _elementtree_Element___deepcopy___impl(ElementObject *self, PyObject *memo)
894901
if (i < 0)
895902
goto error;
896903

904+
_Py_LeaveRecursiveCall();
897905
return (PyObject*) element;
898906

899907
error:
900-
Py_DECREF(element);
908+
_Py_LeaveRecursiveCall();
909+
Py_XDECREF(element);
901910
return NULL;
902911
}
903912

0 commit comments

Comments
 (0)