Skip to content

Commit 2b5be5f

Browse files
committed
fix UAF in xml.etree.ElementTree.Element.__deepcopy__
1 parent 7f02ded commit 2b5be5f

File tree

3 files changed

+41
-4
lines changed

3 files changed

+41
-4
lines changed

Lib/test/test_xml_etree.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2960,6 +2960,24 @@ def element_factory(x, y):
29602960
del b
29612961
gc_collect()
29622962

2963+
def test_deepcopy(self):
2964+
# Prevent crashes when __deepcopy__() mutates the element being copied.
2965+
# See https://github.com/python/cpython/issues/133009.
2966+
class X(ET.Element):
2967+
def __deepcopy__(self, memo):
2968+
root.clear()
2969+
return self
2970+
2971+
root = ET.Element('a')
2972+
evil = X('x')
2973+
root.extend([evil, ET.Element('y')])
2974+
if is_python_implementation():
2975+
self.assertRaises(RuntimeError, copy.deepcopy, root)
2976+
else:
2977+
c = copy.deepcopy(root)
2978+
# In the C implementation, we can still copy the evil element.
2979+
self.assertListEqual(list(c), [evil])
2980+
29632981

29642982
class MutationDeleteElementPath(str):
29652983
def __new__(cls, elem, *args):
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
:mod:`xml.etree.ElementTree`: Fix a crash in :meth:`Element.__deepcopy__
2+
<object.__deepcopy__>` when the element is concurrently mutated.
3+
Patch by Bénédikt Tran.

Modules/_elementtree.c

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -813,11 +813,16 @@ _elementtree_Element___deepcopy___impl(ElementObject *self, PyObject *memo)
813813

814814
PyTypeObject *tp = Py_TYPE(self);
815815
elementtreestate *st = get_elementtree_state_by_type(tp);
816+
// Since the 'tag' attribute is undeletable, deepcopy() should be safe.
816817
tag = deepcopy(st, self->tag, memo);
818+
assert(self->tag != NULL);
817819
if (!tag)
818820
return NULL;
819821

820822
if (self->extra && self->extra->attrib) {
823+
// While deepcopy(self->extra->attrib) may cause 'self->extra' to be
824+
// NULL, we do not use it afterawrds without checking this, so no need
825+
// to temporarily incref 'self->extra->attrib'.
821826
attrib = deepcopy(st, self->extra->attrib, memo);
822827
if (!attrib) {
823828
Py_DECREF(tag);
@@ -835,12 +840,16 @@ _elementtree_Element___deepcopy___impl(ElementObject *self, PyObject *memo)
835840
if (!element)
836841
return NULL;
837842

843+
// Since the 'text' attribute is undeletable, deepcopy() should be safe.
838844
text = deepcopy(st, JOIN_OBJ(self->text), memo);
845+
assert(JOIN_OBJ(self->text) != NULL);
839846
if (!text)
840847
goto error;
841848
_set_joined_ptr(&element->text, JOIN_SET(text, JOIN_GET(self->text)));
842849

850+
// Since the 'tail' attribute is undeletable, deepcopy() should be safe.
843851
tail = deepcopy(st, JOIN_OBJ(self->tail), memo);
852+
assert(JOIN_OBJ(self->tail) != NULL);
844853
if (!tail)
845854
goto error;
846855
_set_joined_ptr(&element->tail, JOIN_SET(tail, JOIN_GET(self->tail)));
@@ -850,9 +859,11 @@ _elementtree_Element___deepcopy___impl(ElementObject *self, PyObject *memo)
850859
if (element_resize(element, self->extra->length) < 0)
851860
goto error;
852861

853-
// TODO(picnixz): check for an evil child's __deepcopy__ on 'self'
854-
for (i = 0; i < self->extra->length; i++) {
855-
PyObject* child = deepcopy(st, self->extra->children[i], memo);
862+
// TODO(picnixz): should we protect against mutations from 'memo'?
863+
for (i = 0; self->extra && i < self->extra->length; i++) {
864+
PyObject* itemi = Py_NewRef(self->extra->children[i]);
865+
PyObject* child = deepcopy(st, itemi, memo);
866+
Py_DECREF(itemi);
856867
if (!child || !Element_Check(st, child)) {
857868
if (child) {
858869
raise_type_error(child);
@@ -865,7 +876,12 @@ _elementtree_Element___deepcopy___impl(ElementObject *self, PyObject *memo)
865876
}
866877

867878
assert(!element->extra->length);
868-
element->extra->length = self->extra->length;
879+
/*
880+
* The original 'self->extra' may be gone at this point if deepcopy()
881+
* had side-effects. However, 'i' is the number of copied items that
882+
* we were able to successfully copy.
883+
*/
884+
element->extra->length = i;
869885
}
870886

871887
/* add object to memo dictionary (so deepcopy won't visit it again) */

0 commit comments

Comments
 (0)