mirror of
https://github.com/python/cpython.git
synced 2025-08-31 14:07:50 +00:00
gh-126037: fix UAF in xml.etree.ElementTree.Element.find*
when concurrent mutations happen (#127964)
We fix a use-after-free in the `find`, `findtext` and `findall` methods of `xml.etree.ElementTree.Element` objects that can be triggered when the tag to find implements an `__eq__` method that mutates the element being queried.
This commit is contained in:
parent
6aa88a2cb3
commit
c57623c221
3 changed files with 69 additions and 48 deletions
|
@ -2793,20 +2793,38 @@ class BadElementTest(ElementTestCase, unittest.TestCase):
|
|||
gc_collect()
|
||||
|
||||
|
||||
class MutatingElementPath(str):
|
||||
class MutationDeleteElementPath(str):
|
||||
def __new__(cls, elem, *args):
|
||||
self = str.__new__(cls, *args)
|
||||
self.elem = elem
|
||||
return self
|
||||
|
||||
def __eq__(self, o):
|
||||
del self.elem[:]
|
||||
return True
|
||||
MutatingElementPath.__hash__ = str.__hash__
|
||||
|
||||
__hash__ = str.__hash__
|
||||
|
||||
|
||||
class MutationClearElementPath(str):
|
||||
def __new__(cls, elem, *args):
|
||||
self = str.__new__(cls, *args)
|
||||
self.elem = elem
|
||||
return self
|
||||
|
||||
def __eq__(self, o):
|
||||
self.elem.clear()
|
||||
return True
|
||||
|
||||
__hash__ = str.__hash__
|
||||
|
||||
|
||||
class BadElementPath(str):
|
||||
def __eq__(self, o):
|
||||
raise 1/0
|
||||
BadElementPath.__hash__ = str.__hash__
|
||||
|
||||
__hash__ = str.__hash__
|
||||
|
||||
|
||||
class BadElementPathTest(ElementTestCase, unittest.TestCase):
|
||||
def setUp(self):
|
||||
|
@ -2821,9 +2839,11 @@ class BadElementPathTest(ElementTestCase, unittest.TestCase):
|
|||
super().tearDown()
|
||||
|
||||
def test_find_with_mutating(self):
|
||||
e = ET.Element('foo')
|
||||
e.extend([ET.Element('bar')])
|
||||
e.find(MutatingElementPath(e, 'x'))
|
||||
for cls in [MutationDeleteElementPath, MutationClearElementPath]:
|
||||
with self.subTest(cls):
|
||||
e = ET.Element('foo')
|
||||
e.extend([ET.Element('bar')])
|
||||
e.find(cls(e, 'x'))
|
||||
|
||||
def test_find_with_error(self):
|
||||
e = ET.Element('foo')
|
||||
|
@ -2834,9 +2854,11 @@ class BadElementPathTest(ElementTestCase, unittest.TestCase):
|
|||
pass
|
||||
|
||||
def test_findtext_with_mutating(self):
|
||||
e = ET.Element('foo')
|
||||
e.extend([ET.Element('bar')])
|
||||
e.findtext(MutatingElementPath(e, 'x'))
|
||||
for cls in [MutationDeleteElementPath, MutationClearElementPath]:
|
||||
with self.subTest(cls):
|
||||
e = ET.Element('foo')
|
||||
e.extend([ET.Element('bar')])
|
||||
e.findtext(cls(e, 'x'))
|
||||
|
||||
def test_findtext_with_error(self):
|
||||
e = ET.Element('foo')
|
||||
|
@ -2861,9 +2883,11 @@ class BadElementPathTest(ElementTestCase, unittest.TestCase):
|
|||
self.assertEqual(root_elem.findtext('./bar'), '')
|
||||
|
||||
def test_findall_with_mutating(self):
|
||||
e = ET.Element('foo')
|
||||
e.extend([ET.Element('bar')])
|
||||
e.findall(MutatingElementPath(e, 'x'))
|
||||
for cls in [MutationDeleteElementPath, MutationClearElementPath]:
|
||||
with self.subTest(cls):
|
||||
e = ET.Element('foo')
|
||||
e.extend([ET.Element('bar')])
|
||||
e.findall(cls(e, 'x'))
|
||||
|
||||
def test_findall_with_error(self):
|
||||
e = ET.Element('foo')
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
:mod:`xml.etree.ElementTree`: Fix a crash in :meth:`Element.find <xml.etree.ElementTree.Element.find>`,
|
||||
:meth:`Element.findtext <xml.etree.ElementTree.Element.findtext>` and
|
||||
:meth:`Element.findall <xml.etree.ElementTree.Element.findall>` when the tag
|
||||
to find implements an :meth:`~object.__eq__` method mutating the element
|
||||
being queried. Patch by Bénédikt Tran.
|
|
@ -1252,29 +1252,28 @@ _elementtree_Element_find_impl(ElementObject *self, PyTypeObject *cls,
|
|||
PyObject *path, PyObject *namespaces)
|
||||
/*[clinic end generated code: output=18f77d393c9fef1b input=94df8a83f956acc6]*/
|
||||
{
|
||||
Py_ssize_t i;
|
||||
elementtreestate *st = get_elementtree_state_by_cls(cls);
|
||||
|
||||
if (checkpath(path) || namespaces != Py_None) {
|
||||
return PyObject_CallMethodObjArgs(
|
||||
st->elementpath_obj, st->str_find, self, path, namespaces, NULL
|
||||
);
|
||||
);
|
||||
}
|
||||
|
||||
if (!self->extra)
|
||||
Py_RETURN_NONE;
|
||||
|
||||
for (i = 0; i < self->extra->length; i++) {
|
||||
PyObject* item = self->extra->children[i];
|
||||
int rc;
|
||||
for (Py_ssize_t i = 0; self->extra && i < self->extra->length; i++) {
|
||||
PyObject *item = self->extra->children[i];
|
||||
assert(Element_Check(st, item));
|
||||
Py_INCREF(item);
|
||||
rc = PyObject_RichCompareBool(((ElementObject*)item)->tag, path, Py_EQ);
|
||||
if (rc > 0)
|
||||
PyObject *tag = Py_NewRef(((ElementObject *)item)->tag);
|
||||
int rc = PyObject_RichCompareBool(tag, path, Py_EQ);
|
||||
Py_DECREF(tag);
|
||||
if (rc > 0) {
|
||||
return item;
|
||||
}
|
||||
Py_DECREF(item);
|
||||
if (rc < 0)
|
||||
if (rc < 0) {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
Py_RETURN_NONE;
|
||||
|
@ -1297,38 +1296,34 @@ _elementtree_Element_findtext_impl(ElementObject *self, PyTypeObject *cls,
|
|||
PyObject *namespaces)
|
||||
/*[clinic end generated code: output=6af7a2d96aac32cb input=32f252099f62a3d2]*/
|
||||
{
|
||||
Py_ssize_t i;
|
||||
elementtreestate *st = get_elementtree_state_by_cls(cls);
|
||||
|
||||
if (checkpath(path) || namespaces != Py_None)
|
||||
return PyObject_CallMethodObjArgs(
|
||||
st->elementpath_obj, st->str_findtext,
|
||||
self, path, default_value, namespaces, NULL
|
||||
);
|
||||
);
|
||||
|
||||
if (!self->extra) {
|
||||
return Py_NewRef(default_value);
|
||||
}
|
||||
|
||||
for (i = 0; i < self->extra->length; i++) {
|
||||
for (Py_ssize_t i = 0; self->extra && i < self->extra->length; i++) {
|
||||
PyObject *item = self->extra->children[i];
|
||||
int rc;
|
||||
assert(Element_Check(st, item));
|
||||
Py_INCREF(item);
|
||||
rc = PyObject_RichCompareBool(((ElementObject*)item)->tag, path, Py_EQ);
|
||||
PyObject *tag = Py_NewRef(((ElementObject *)item)->tag);
|
||||
int rc = PyObject_RichCompareBool(tag, path, Py_EQ);
|
||||
Py_DECREF(tag);
|
||||
if (rc > 0) {
|
||||
PyObject* text = element_get_text((ElementObject*)item);
|
||||
PyObject *text = element_get_text((ElementObject *)item);
|
||||
Py_DECREF(item);
|
||||
if (text == Py_None) {
|
||||
Py_DECREF(item);
|
||||
return Py_GetConstant(Py_CONSTANT_EMPTY_STR);
|
||||
}
|
||||
Py_XINCREF(text);
|
||||
Py_DECREF(item);
|
||||
return text;
|
||||
}
|
||||
Py_DECREF(item);
|
||||
if (rc < 0)
|
||||
if (rc < 0) {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
return Py_NewRef(default_value);
|
||||
|
@ -1349,29 +1344,26 @@ _elementtree_Element_findall_impl(ElementObject *self, PyTypeObject *cls,
|
|||
PyObject *path, PyObject *namespaces)
|
||||
/*[clinic end generated code: output=65e39a1208f3b59e input=7aa0db45673fc9a5]*/
|
||||
{
|
||||
Py_ssize_t i;
|
||||
PyObject* out;
|
||||
elementtreestate *st = get_elementtree_state_by_cls(cls);
|
||||
|
||||
if (checkpath(path) || namespaces != Py_None) {
|
||||
return PyObject_CallMethodObjArgs(
|
||||
st->elementpath_obj, st->str_findall, self, path, namespaces, NULL
|
||||
);
|
||||
);
|
||||
}
|
||||
|
||||
out = PyList_New(0);
|
||||
if (!out)
|
||||
PyObject *out = PyList_New(0);
|
||||
if (out == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!self->extra)
|
||||
return out;
|
||||
|
||||
for (i = 0; i < self->extra->length; i++) {
|
||||
PyObject* item = self->extra->children[i];
|
||||
int rc;
|
||||
for (Py_ssize_t i = 0; self->extra && i < self->extra->length; i++) {
|
||||
PyObject *item = self->extra->children[i];
|
||||
assert(Element_Check(st, item));
|
||||
Py_INCREF(item);
|
||||
rc = PyObject_RichCompareBool(((ElementObject*)item)->tag, path, Py_EQ);
|
||||
PyObject *tag = Py_NewRef(((ElementObject *)item)->tag);
|
||||
int rc = PyObject_RichCompareBool(tag, path, Py_EQ);
|
||||
Py_DECREF(tag);
|
||||
if (rc != 0 && (rc < 0 || PyList_Append(out, item) < 0)) {
|
||||
Py_DECREF(item);
|
||||
Py_DECREF(out);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue