Skip to content

Commit 1a0c970

Browse files
committed
gh-143200: fix UAFs in Element.__{set,get}item__
1 parent 3a728e5 commit 1a0c970

File tree

3 files changed

+101
-25
lines changed

3 files changed

+101
-25
lines changed

Lib/test/test_xml_etree.py

Lines changed: 56 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3010,32 +3010,72 @@ def __del__(self):
30103010
elem = b.close()
30113011
self.assertEqual(elem[0].tail, 'ABCDEFGHIJKL')
30123012

3013-
def test_subscr(self):
3014-
# Issue #27863
3013+
def test_subscr_with_clear(self):
3014+
# See https://github.com/python/cpython/issues/143200.
3015+
self.do_test_subscr_with_mutating_slice(use_clear_method=True)
3016+
3017+
def test_subscr_with_delete(self):
3018+
# See https://github.com/python/cpython/issues/72050.
3019+
self.do_test_subscr_with_mutating_slice(use_clear_method=False)
3020+
3021+
def do_test_subscr_with_mutating_slice(self, *, use_clear_method):
30153022
class X:
3023+
def __init__(self, i=0):
3024+
self.i = i
30163025
def __index__(self):
3017-
del e[:]
3018-
return 1
3026+
if use_clear_method:
3027+
e.clear()
3028+
else:
3029+
del e[:]
3030+
return self.i
30193031

3020-
e = ET.Element('elem')
3021-
e.append(ET.Element('child'))
3022-
e[:X()] # shouldn't crash
3032+
for s in self.get_mutating_slices(X, 10):
3033+
with self.subTest(s):
3034+
e = ET.Element('elem')
3035+
e.extend([ET.Element(f'c{i}') for i in range(10)])
3036+
e[s] # shouldn't crash
30233037

3024-
e.append(ET.Element('child'))
3025-
e[0:10:X()] # shouldn't crash
3038+
def test_ass_subscr_with_mutating_slice(self):
3039+
# See https://github.com/python/cpython/issues/72050
3040+
# and https://github.com/python/cpython/issues/143200.
30263041

3027-
def test_ass_subscr(self):
3028-
# Issue #27863
30293042
class X:
3043+
def __init__(self, i=0):
3044+
self.i = i
30303045
def __index__(self):
30313046
e[:] = []
3032-
return 1
3047+
return self.i
3048+
3049+
for s in self.get_mutating_slices(X, 10):
3050+
with self.subTest(s):
3051+
e = ET.Element('elem')
3052+
e.extend([ET.Element(f'c{i}') for i in range(10)])
3053+
e[s] = [] # shouldn't crash
3054+
3055+
def get_mutating_slices(self, index_class, n_children):
3056+
self.assertGreaterEqual(n_children, 10)
3057+
return [
3058+
slice(index_class(), None, None),
3059+
slice(index_class(2), None, None),
3060+
slice(None, index_class(), None),
3061+
slice(None, index_class(2), None),
3062+
slice(0, 2, index_class(1)),
3063+
slice(0, 2, index_class(2)),
3064+
slice(0, n_children, index_class(1)),
3065+
slice(0, n_children, index_class(2)),
3066+
slice(0, 2 * n_children, index_class(1)),
3067+
slice(0, 2 * n_children, index_class(2)),
3068+
]
30333069

3034-
e = ET.Element('elem')
3035-
for _ in range(10):
3036-
e.insert(0, ET.Element('child'))
3070+
def test_ass_subscr_with_mutating_iterable_value(self):
3071+
class V:
3072+
def __iter__(self):
3073+
e.clear()
3074+
return iter([ET.Element('a'), ET.Element('b')])
30373075

3038-
e[0:10:X()] = [] # shouldn't crash
3076+
e = ET.Element('elem')
3077+
e.extend([ET.Element(f'c{i}') for i in range(10)])
3078+
e[:] = V()
30393079

30403080
def test_treebuilder_start(self):
30413081
# Issue #27863
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
:mod:`xml.etree.ElementTree`: fix use-after-free crashes in
2+
:meth:`~object.__getitem__` and :meth:`~object.__setitem__` methods of
3+
:class:`~xml.etree.ElementTree.Element` when the element is concurrently
4+
mutated. Patch by Bénédikt Tran.

Modules/_elementtree.c

Lines changed: 41 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1801,6 +1801,8 @@ element_subscr(PyObject *op, PyObject *item)
18011801
if (i == -1 && PyErr_Occurred()) {
18021802
return NULL;
18031803
}
1804+
// If PyNumber_AsSsize_t() clears 'self->extra',
1805+
// we want element_getitem() to raise IndexError.
18041806
if (i < 0 && self->extra)
18051807
i += self->extra->length;
18061808
return element_getitem(op, i);
@@ -1810,12 +1812,19 @@ element_subscr(PyObject *op, PyObject *item)
18101812
size_t cur;
18111813
PyObject* list;
18121814

1813-
if (!self->extra)
1814-
return PyList_New(0);
1815-
18161815
if (PySlice_Unpack(item, &start, &stop, &step) < 0) {
18171816
return NULL;
18181817
}
1818+
1819+
// Even if PySlice_Unpack() may clear 'self->extra',
1820+
// using a slice should not raise an IndexError, so
1821+
// we do not need to ensure 'extra' to exist.
1822+
//
1823+
// See https://github.com/python/cpython/issues/143200.
1824+
if (self->extra == NULL) {
1825+
return PyList_New(0);
1826+
}
1827+
18191828
slicelen = PySlice_AdjustIndices(self->extra->length, &start, &stop,
18201829
step);
18211830

@@ -1853,6 +1862,8 @@ element_ass_subscr(PyObject *op, PyObject *item, PyObject *value)
18531862
if (i == -1 && PyErr_Occurred()) {
18541863
return -1;
18551864
}
1865+
// If PyNumber_AsSsize_t() clears 'self->extra',
1866+
// we want element_setitem() to raise IndexError.
18561867
if (i < 0 && self->extra)
18571868
i += self->extra->length;
18581869
return element_setitem(op, i, value);
@@ -1864,16 +1875,20 @@ element_ass_subscr(PyObject *op, PyObject *item, PyObject *value)
18641875
PyObject* recycle = NULL;
18651876
PyObject* seq;
18661877

1867-
if (!self->extra) {
1868-
if (create_extra(self, NULL) < 0)
1869-
return -1;
1870-
}
1871-
18721878
if (PySlice_Unpack(item, &start, &stop, &step) < 0) {
18731879
return -1;
18741880
}
1881+
// Since PySlice_Unpack() may clear 'self->extra', we need
1882+
// to ensure that it exists now (using a slice for assignment
1883+
// should not raise IndexError).
1884+
//
1885+
// See https://github.com/python/cpython/issues/143200.
1886+
if (self->extra == NULL && create_extra(self, NULL) < 0) {
1887+
return -1;
1888+
}
1889+
18751890
slicelen = PySlice_AdjustIndices(self->extra->length, &start, &stop,
1876-
step);
1891+
step);
18771892

18781893
if (value == NULL) {
18791894
/* Delete slice */
@@ -1908,6 +1923,8 @@ element_ass_subscr(PyObject *op, PyObject *item, PyObject *value)
19081923
* Note that in the ith iteration, shifting is done i+i places down
19091924
* because i children were already removed.
19101925
*/
1926+
assert(self->extra != NULL);
1927+
assert(self->extra->children != NULL);
19111928
for (cur = start, i = 0; cur < (size_t)stop; cur += step, ++i) {
19121929
/* Compute how many children have to be moved, clipping at the
19131930
* list end.
@@ -1948,6 +1965,18 @@ element_ass_subscr(PyObject *op, PyObject *item, PyObject *value)
19481965
}
19491966
newlen = PySequence_Fast_GET_SIZE(seq);
19501967

1968+
// Re-create 'self->extra' if PySequence_Fast() cleared it.
1969+
// See https://github.com/python/cpython/issues/143200.
1970+
if (self->extra == NULL) {
1971+
if (create_extra(self, NULL) < 0) {
1972+
Py_DECREF(seq);
1973+
return -1;
1974+
}
1975+
// re-compute the slice length since self->extra->length changed
1976+
slicelen = PySlice_AdjustIndices(self->extra->length, &start, &stop,
1977+
step);
1978+
}
1979+
19511980
if (step != 1 && newlen != slicelen)
19521981
{
19531982
Py_DECREF(seq);
@@ -1967,6 +1996,9 @@ element_ass_subscr(PyObject *op, PyObject *item, PyObject *value)
19671996
}
19681997
}
19691998

1999+
assert(recycle == NULL);
2000+
assert(self->extra != NULL);
2001+
19702002
PyTypeObject *tp = Py_TYPE(self);
19712003
elementtreestate *st = get_elementtree_state_by_type(tp);
19722004
for (i = 0; i < newlen; i++) {

0 commit comments

Comments
 (0)