From ae55e9c053a79d90e619733f36b5806d7f9306c7 Mon Sep 17 00:00:00 2001 From: Stan Ulbrych Date: Sat, 18 Apr 2026 21:39:56 +0100 Subject: [PATCH 1/2] Fix a UAF in `Element.findtext()` --- Lib/test/test_xml_etree.py | 10 ++++++++++ .../2026-04-18-21-39-15.gh-issue-148735.siw6DG.rst | 4 ++++ Modules/_elementtree.c | 4 ++-- 3 files changed, 16 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2026-04-18-21-39-15.gh-issue-148735.siw6DG.rst diff --git a/Lib/test/test_xml_etree.py b/Lib/test/test_xml_etree.py index b380d0276b0169..d1e04d43f4eb30 100644 --- a/Lib/test/test_xml_etree.py +++ b/Lib/test/test_xml_etree.py @@ -3258,6 +3258,16 @@ def test_findtext_with_mutating(self): e.extend([ET.Element('bar')]) e.findtext(cls(e, 'x')) + def test_findtext_with_mutating_non_none_text(self): + for cls in [MutationDeleteElementPath, MutationClearElementPath]: + with self.subTest(cls): + e = ET.Element('foo') + child = ET.Element('bar') + child.text = str(object()) + e.append(child) + del child + repr(e.findtext(cls(e, 'x'))) + def test_findtext_with_error(self): e = ET.Element('foo') e.extend([ET.Element('bar')]) diff --git a/Misc/NEWS.d/next/Library/2026-04-18-21-39-15.gh-issue-148735.siw6DG.rst b/Misc/NEWS.d/next/Library/2026-04-18-21-39-15.gh-issue-148735.siw6DG.rst new file mode 100644 index 00000000000000..a7a5ecd01a99bf --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-04-18-21-39-15.gh-issue-148735.siw6DG.rst @@ -0,0 +1,4 @@ +:mod:`xml.etree.ElementTree`: Fix a use-after-free in +:meth:`Element.findtext ` when the +tag to find implements an :meth:`~object.__eq__` method that drops every +other reference to a matching element. diff --git a/Modules/_elementtree.c b/Modules/_elementtree.c index e2185c4bd03aad..99be8c4c045bb8 100644 --- a/Modules/_elementtree.c +++ b/Modules/_elementtree.c @@ -1347,12 +1347,12 @@ _elementtree_Element_findtext_impl(ElementObject *self, PyTypeObject *cls, int rc = PyObject_RichCompareBool(tag, path, Py_EQ); Py_DECREF(tag); if (rc > 0) { - PyObject *text = element_get_text((ElementObject *)item); + PyObject *text = Py_XNewRef(element_get_text((ElementObject *)item)); Py_DECREF(item); if (text == Py_None) { + Py_DECREF(text); return Py_GetConstant(Py_CONSTANT_EMPTY_STR); } - Py_XINCREF(text); return text; } Py_DECREF(item); From a8d4139aae72114416d69fc506438d7c0ec2a671 Mon Sep 17 00:00:00 2001 From: Stan Ulbrych Date: Sun, 19 Apr 2026 17:19:57 +0100 Subject: [PATCH 2/2] Return a new ref in `element_get_{tail,text}` instead --- Modules/_elementtree.c | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/Modules/_elementtree.c b/Modules/_elementtree.c index 99be8c4c045bb8..52f53f253f8fbd 100644 --- a/Modules/_elementtree.c +++ b/Modules/_elementtree.c @@ -572,7 +572,7 @@ element_get_attrib(ElementObject* self) LOCAL(PyObject*) element_get_text(ElementObject* self) { - /* return borrowed reference to text attribute */ + /* return new reference to text attribute */ PyObject *res = self->text; @@ -587,13 +587,13 @@ element_get_text(ElementObject* self) } } - return res; + return Py_NewRef(res); } LOCAL(PyObject*) element_get_tail(ElementObject* self) { - /* return borrowed reference to text attribute */ + /* return new reference to tail attribute */ PyObject *res = self->tail; @@ -608,7 +608,7 @@ element_get_tail(ElementObject* self) } } - return res; + return Py_NewRef(res); } static PyObject* @@ -1347,7 +1347,7 @@ _elementtree_Element_findtext_impl(ElementObject *self, PyTypeObject *cls, int rc = PyObject_RichCompareBool(tag, path, Py_EQ); Py_DECREF(tag); if (rc > 0) { - PyObject *text = Py_XNewRef(element_get_text((ElementObject *)item)); + PyObject *text = element_get_text((ElementObject *)item); Py_DECREF(item); if (text == Py_None) { Py_DECREF(text); @@ -2055,16 +2055,14 @@ static PyObject* element_text_getter(PyObject *op, void *closure) { ElementObject *self = _Element_CAST(op); - PyObject *res = element_get_text(self); - return Py_XNewRef(res); + return element_get_text(self); } static PyObject* element_tail_getter(PyObject *op, void *closure) { ElementObject *self = _Element_CAST(op); - PyObject *res = element_get_tail(self); - return Py_XNewRef(res); + return element_get_tail(self); } static PyObject* @@ -2307,16 +2305,14 @@ elementiter_next(PyObject *op) continue; gettext: + Py_DECREF(elem); if (!text) { - Py_DECREF(elem); return NULL; } if (text == Py_None) { - Py_DECREF(elem); + Py_DECREF(text); } else { - Py_INCREF(text); - Py_DECREF(elem); rc = PyObject_IsTrue(text); if (rc > 0) return text;