Skip to content

Commit d585b09

Browse files
committed
handle more evil mutations
1 parent f56a368 commit d585b09

File tree

2 files changed

+53
-17
lines changed

2 files changed

+53
-17
lines changed

Lib/test/test_xml_etree.py

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2960,8 +2960,8 @@ 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.
2963+
def test_deepcopy_clear(self):
2964+
# Prevent crashes when __deepcopy__() clears the children list.
29652965
# See https://github.com/python/cpython/issues/133009.
29662966
class X(ET.Element):
29672967
def __deepcopy__(self, memo):
@@ -2972,12 +2972,38 @@ def __deepcopy__(self, memo):
29722972
evil = X('x')
29732973
root.extend([evil, ET.Element('y')])
29742974
if is_python_implementation():
2975+
# Mutating a list over which we iterate raises an error.
29752976
self.assertRaises(RuntimeError, copy.deepcopy, root)
29762977
else:
29772978
c = copy.deepcopy(root)
29782979
# In the C implementation, we can still copy the evil element.
29792980
self.assertListEqual(list(c), [evil])
29802981

2982+
def test_deepcopy_grow(self):
2983+
# Prevent crashes when __deepcopy__() mutates the children list.
2984+
# See https://github.com/python/cpython/issues/133009.
2985+
a = ET.Element('a')
2986+
b = ET.Element('b')
2987+
c = ET.Element('c')
2988+
2989+
class X(ET.Element):
2990+
def __deepcopy__(self, memo):
2991+
root.append(a)
2992+
root.append(b)
2993+
return self
2994+
2995+
root = ET.Element('top')
2996+
evil1, evil2 = X('1'), X('2')
2997+
root.extend([evil1, c, evil2])
2998+
children = list(copy.deepcopy(root))
2999+
# mock deep copies
3000+
self.assertIs(children[0], evil1)
3001+
self.assertIs(children[2], evil2)
3002+
# true deep copies
3003+
self.assertEqual(children[1].tag, c.tag)
3004+
self.assertEqual([c.tag for c in children[3:]],
3005+
[a.tag, b.tag, a.tag, b.tag])
3006+
29813007

29823008
class MutationDeleteElementPath(str):
29833009
def __new__(cls, elem, *args):

Modules/_elementtree.c

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -847,8 +847,11 @@ _elementtree_Element___deepcopy___impl(ElementObject *self, PyObject *memo)
847847

848848
assert(!element->extra || !element->extra->length);
849849
if (self->extra) {
850-
if (element_resize(element, self->extra->length) < 0)
850+
Py_ssize_t expected_count = self->extra->length;
851+
if (element_resize(element, expected_count) < 0) {
852+
assert(!element->extra->length);
851853
goto error;
854+
}
852855

853856
for (i = 0; self->extra && i < self->extra->length; i++) {
854857
PyObject* child = deepcopy(st, self->extra->children[i], memo);
@@ -860,15 +863,23 @@ _elementtree_Element___deepcopy___impl(ElementObject *self, PyObject *memo)
860863
element->extra->length = i;
861864
goto error;
862865
}
866+
if (self->extra && expected_count != self->extra->length) {
867+
// 'self->extra' got mutated and 'element' may not have
868+
// sufficient space to hold the next iteration's item.
869+
expected_count = self->extra->length;
870+
if (element_resize(element, expected_count) < 0) {
871+
Py_DECREF(child);
872+
element->extra->length = i;
873+
goto error;
874+
}
875+
}
863876
element->extra->children[i] = child;
864877
}
865878

866879
assert(!element->extra->length);
867-
/*
868-
* The original 'self->extra' may be gone at this point if deepcopy()
869-
* has side-effects. However, 'i' is the number of copied items that
870-
* we were able to successfully copy.
871-
*/
880+
// The original 'self->extra' may be gone at this point if deepcopy()
881+
// has side-effects. However, 'i' is the number of copied items that
882+
// we were able to successfully copy.
872883
element->extra->length = i;
873884
}
874885

@@ -903,8 +914,6 @@ deepcopy(elementtreestate *st, PyObject *object, PyObject *memo)
903914

904915
if (Py_REFCNT(object) == 1) {
905916
if (PyDict_CheckExact(object)) {
906-
// Exact dictionaries do not execute arbitrary code as it's
907-
// impossible to override their __iter__() method.
908917
PyObject *key, *value;
909918
Py_ssize_t pos = 0;
910919
int simple = 1;
@@ -915,18 +924,18 @@ deepcopy(elementtreestate *st, PyObject *object, PyObject *memo)
915924
}
916925
}
917926
if (simple) {
918-
// This does not call object.__copy__(), so it's still safe.
919927
return PyDict_Copy(object);
920928
}
921929
/* Fall through to general case */
922930
}
923931
else if (Element_CheckExact(st, object)) {
924932
// The __deepcopy__() call may call arbitrary code even if the
925933
// object to copy is a built-in XML element (one of its children
926-
// may mutate 'object' in its own __deepcopy__() implementation).
927-
ElementObject *tmp = (ElementObject *)Py_NewRef(object);
928-
PyObject *res = _elementtree_Element___deepcopy___impl(tmp, memo);
929-
Py_DECREF(tmp);
934+
// any of its parents in its own __deepcopy__() implementation).
935+
Py_INCREF(object);
936+
PyObject *res = _elementtree_Element___deepcopy___impl(
937+
(ElementObject *)object, memo);
938+
Py_DECREF(object);
930939
return res;
931940
}
932941
}
@@ -938,9 +947,10 @@ deepcopy(elementtreestate *st, PyObject *object, PyObject *memo)
938947
return NULL;
939948
}
940949

941-
PyObject *args[2] = {Py_NewRef(object), memo};
950+
Py_INCREF(object);
951+
PyObject *args[2] = {object, memo};
942952
PyObject *res = PyObject_Vectorcall(st->deepcopy_obj, args, 2, NULL);
943-
Py_DECREF(args[0]);
953+
Py_DECREF(object);
944954
return res;
945955
}
946956

0 commit comments

Comments
 (0)