Skip to content

Commit fcbfaf4

Browse files
Fix unbound C recursion in Element.__deepcopy__()
1 parent a8c9aa9 commit fcbfaf4

File tree

3 files changed

+29
-2
lines changed

3 files changed

+29
-2
lines changed

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(50_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: 14 additions & 2 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
@@ -818,18 +819,25 @@ _elementtree_Element___deepcopy___impl(ElementObject *self, PyObject *memo)
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)
831+
if (!tag) {
832+
_Py_LeaveRecursiveCall();
827833
return NULL;
834+
}
828835

829836
if (self->extra && self->extra->attrib) {
830837
attrib = deepcopy(st, self->extra->attrib, memo);
831838
if (!attrib) {
832839
Py_DECREF(tag);
840+
_Py_LeaveRecursiveCall();
833841
return NULL;
834842
}
835843
} else {
@@ -841,8 +849,10 @@ _elementtree_Element___deepcopy___impl(ElementObject *self, PyObject *memo)
841849
Py_DECREF(tag);
842850
Py_XDECREF(attrib);
843851

844-
if (!element)
852+
if (!element) {
853+
_Py_LeaveRecursiveCall();
845854
return NULL;
855+
}
846856

847857
text = deepcopy(st, JOIN_OBJ(self->text), memo);
848858
if (!text)
@@ -904,9 +914,11 @@ _elementtree_Element___deepcopy___impl(ElementObject *self, PyObject *memo)
904914
if (i < 0)
905915
goto error;
906916

917+
_Py_LeaveRecursiveCall();
907918
return (PyObject*) element;
908919

909920
error:
921+
_Py_LeaveRecursiveCall();
910922
Py_DECREF(element);
911923
return NULL;
912924
}

0 commit comments

Comments
 (0)