Skip to content

Commit 5f1b710

Browse files
[3.14] gh-148801: Fix unbound C recursion in Element.__deepcopy__() (GH-148802) (#148842)
(cherry picked from commit 33e82be)
1 parent e5d5541 commit 5f1b710

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
@@ -3098,6 +3098,19 @@ def __deepcopy__(self, memo):
30983098
self.assertEqual([c.tag for c in children[3:]],
30993099
[a.tag, b.tag, a.tag, b.tag])
31003100

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

31023115
class MutationDeleteElementPath(str):
31033116
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_pyhash.h" // _Py_HashSecret
2021
#include "pycore_weakref.h" // FT_CLEAR_WEAKREFS()
2122

@@ -802,26 +803,31 @@ _elementtree_Element___deepcopy___impl(ElementObject *self, PyObject *memo)
802803
/*[clinic end generated code: output=eefc3df50465b642 input=a2d40348c0aade10]*/
803804
{
804805
Py_ssize_t i;
805-
ElementObject* element;
806+
ElementObject* element = NULL;
806807
PyObject* tag;
807808
PyObject* attrib;
808809
PyObject* text;
809810
PyObject* tail;
810811
PyObject* id;
811812

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

820826
if (self->extra && self->extra->attrib) {
821827
attrib = deepcopy(st, self->extra->attrib, memo);
822828
if (!attrib) {
823829
Py_DECREF(tag);
824-
return NULL;
830+
goto error;
825831
}
826832
} else {
827833
attrib = NULL;
@@ -832,8 +838,9 @@ _elementtree_Element___deepcopy___impl(ElementObject *self, PyObject *memo)
832838
Py_DECREF(tag);
833839
Py_XDECREF(attrib);
834840

835-
if (!element)
836-
return NULL;
841+
if (!element) {
842+
goto error;
843+
}
837844

838845
text = deepcopy(st, JOIN_OBJ(self->text), memo);
839846
if (!text)
@@ -895,10 +902,12 @@ _elementtree_Element___deepcopy___impl(ElementObject *self, PyObject *memo)
895902
if (i < 0)
896903
goto error;
897904

905+
_Py_LeaveRecursiveCall();
898906
return (PyObject*) element;
899907

900908
error:
901-
Py_DECREF(element);
909+
_Py_LeaveRecursiveCall();
910+
Py_XDECREF(element);
902911
return NULL;
903912
}
904913

0 commit comments

Comments
 (0)