Skip to content

Commit 33e82be

Browse files
gh-148801: Fix unbound C recursion in Element.__deepcopy__() (#148802)
1 parent 1274766 commit 33e82be

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

3193+
@support.skip_if_unlimited_stack_size
3194+
@support.skip_emscripten_stack_overflow()
3195+
@support.skip_wasi_stack_overflow()
3196+
def test_deeply_nested_deepcopy(self):
3197+
# This should raise a RecursionError and not crash.
3198+
# See https://github.com/python/cpython/issues/148801.
3199+
root = cur = ET.Element('s')
3200+
for _ in range(150_000):
3201+
cur = ET.SubElement(cur, 'u')
3202+
with support.infinite_recursion():
3203+
with self.assertRaises(RecursionError):
3204+
copy.deepcopy(root)
3205+
31933206

31943207
class MutationDeleteElementPath(str):
31953208
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_dict.h" // _PyDict_CopyAsDict()
2021
#include "pycore_pyhash.h" // _Py_HashSecret
2122
#include "pycore_tuple.h" // _PyTuple_FromPair
@@ -811,26 +812,31 @@ _elementtree_Element___deepcopy___impl(ElementObject *self, PyObject *memo)
811812
/*[clinic end generated code: output=eefc3df50465b642 input=a2d40348c0aade10]*/
812813
{
813814
Py_ssize_t i;
814-
ElementObject* element;
815+
ElementObject* element = NULL;
815816
PyObject* tag;
816817
PyObject* attrib;
817818
PyObject* text;
818819
PyObject* tail;
819820
PyObject* id;
820821

822+
if (_Py_EnterRecursiveCall(" in Element.__deepcopy__")) {
823+
return NULL;
824+
}
825+
821826
PyTypeObject *tp = Py_TYPE(self);
822827
elementtreestate *st = get_elementtree_state_by_type(tp);
823828
// The deepcopy() helper takes care of incrementing the refcount
824829
// of the object to copy so to avoid use-after-frees.
825830
tag = deepcopy(st, self->tag, memo);
826-
if (!tag)
827-
return NULL;
831+
if (!tag) {
832+
goto error;
833+
}
828834

829835
if (self->extra && self->extra->attrib) {
830836
attrib = deepcopy(st, self->extra->attrib, memo);
831837
if (!attrib) {
832838
Py_DECREF(tag);
833-
return NULL;
839+
goto error;
834840
}
835841
} else {
836842
attrib = NULL;
@@ -841,8 +847,9 @@ _elementtree_Element___deepcopy___impl(ElementObject *self, PyObject *memo)
841847
Py_DECREF(tag);
842848
Py_XDECREF(attrib);
843849

844-
if (!element)
845-
return NULL;
850+
if (!element) {
851+
goto error;
852+
}
846853

847854
text = deepcopy(st, JOIN_OBJ(self->text), memo);
848855
if (!text)
@@ -904,10 +911,12 @@ _elementtree_Element___deepcopy___impl(ElementObject *self, PyObject *memo)
904911
if (i < 0)
905912
goto error;
906913

914+
_Py_LeaveRecursiveCall();
907915
return (PyObject*) element;
908916

909917
error:
910-
Py_DECREF(element);
918+
_Py_LeaveRecursiveCall();
919+
Py_XDECREF(element);
911920
return NULL;
912921
}
913922

0 commit comments

Comments
 (0)