From 38fbd481db1161c4e0bebdd68d0ca786a53cb24f Mon Sep 17 00:00:00 2001 From: Stefan Behnel Date: Sat, 14 Oct 2017 08:53:53 +0200 Subject: [PATCH 1/3] bpo-31544: Avoid calling "PyObject_GetAttrString()" (and potentially executing user code) with a live exception set. Backport to Py2.7 --- Lib/test/test_xml_etree.py | 33 +++++++++++++++++ .../2017-09-13-19-55-35.bpo-31544.beTh6t.rst | 2 ++ Modules/_elementtree.c | 35 +++++++++++++++++-- 3 files changed, 68 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2017-09-13-19-55-35.bpo-31544.beTh6t.rst diff --git a/Lib/test/test_xml_etree.py b/Lib/test/test_xml_etree.py index e466867b7cd158..c75d55f05c17cd 100644 --- a/Lib/test/test_xml_etree.py +++ b/Lib/test/test_xml_etree.py @@ -136,6 +136,13 @@ def wrapper(*args): return test(*args) return wrapper +def cet_only(test): + def wrapper(*args): + if ET is pyET: + raise unittest.SkipTest('only for the C version') + return test(*args) + return wrapper + # -------------------------------------------------------------------- # element tree tests @@ -2229,6 +2236,32 @@ def close(self): ('html', '-//W3C//DTD XHTML 1.0 Transitional//EN', 'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd')) + @cet_only # PyET does not look up the attributes in XMLParser().__init__() + def test_builder_lookup_errors(self): + class RaisingBuilder(object): + def __init__(self, raise_in=None, what=ValueError): + self.raise_in = raise_in + self.what = what + + def __getattr__(self, name): + if name == self.raise_in: + raise self.what(self.raise_in) + def handle(*args): + pass + return handle + + ET.XMLParser(target=RaisingBuilder()) + # cET also checks for 'close' and 'doctype', PyET does it only at need + for event in ('start', 'data', 'end', 'comment', 'pi'): + with self.assertRaises(ValueError): + ET.XMLParser(target=RaisingBuilder(event)) + + ET.XMLParser(target=RaisingBuilder(what=AttributeError)) + for event in ('start', 'data', 'end', 'comment', 'pi'): + parser = ET.XMLParser(target=RaisingBuilder(event, what=AttributeError)) + parser.feed(self.sample1) + self.assertIsNone(parser.close()) + class XMLParserTest(unittest.TestCase): sample1 = b'22' diff --git a/Misc/NEWS.d/next/Library/2017-09-13-19-55-35.bpo-31544.beTh6t.rst b/Misc/NEWS.d/next/Library/2017-09-13-19-55-35.bpo-31544.beTh6t.rst new file mode 100644 index 00000000000000..9ea3599ee0b0e9 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2017-09-13-19-55-35.bpo-31544.beTh6t.rst @@ -0,0 +1,2 @@ +The C accelerator module of ElementTree ignored exceptions raised when +looking up TreeBuilder target methods in XMLParser(). diff --git a/Modules/_elementtree.c b/Modules/_elementtree.c index 574559c6313bc7..7f0e609340033a 100644 --- a/Modules/_elementtree.c +++ b/Modules/_elementtree.c @@ -2510,6 +2510,18 @@ expat_unknown_encoding_handler(XMLParserObject *self, const XML_Char *name, /* -------------------------------------------------------------------- */ /* constructor and destructor */ +static int +ignore_attribute_error(PyObject *value) +{ + if (value == NULL) { + if (!PyErr_ExceptionMatches(PyExc_AttributeError)) { + return -1; + } + PyErr_Clear(); + } + return 0; +} + static PyObject* xmlparser(PyObject* self_, PyObject* args, PyObject* kw) { @@ -2578,14 +2590,33 @@ xmlparser(PyObject* self_, PyObject* args, PyObject* kw) self->target = target; self->handle_xml = PyObject_GetAttrString(target, "xml"); + if (ignore_attribute_error(self->handle_xml)) { + return NULL; + } self->handle_start = PyObject_GetAttrString(target, "start"); + if (ignore_attribute_error(self->handle_start)) { + return NULL; + } self->handle_data = PyObject_GetAttrString(target, "data"); + if (ignore_attribute_error(self->handle_data)) { + return NULL; + } self->handle_end = PyObject_GetAttrString(target, "end"); + if (ignore_attribute_error(self->handle_end)) { + return NULL; + } self->handle_comment = PyObject_GetAttrString(target, "comment"); + if (ignore_attribute_error(self->handle_comment)) { + return NULL; + } self->handle_pi = PyObject_GetAttrString(target, "pi"); + if (ignore_attribute_error(self->handle_pi)) { + return NULL; + } self->handle_close = PyObject_GetAttrString(target, "close"); - - PyErr_Clear(); + if (ignore_attribute_error(self->handle_close)) { + return NULL; + } /* configure parser */ EXPAT(SetUserData)(self->parser, self); From 72413a52c7fab52ab197716a42c36c10b30be3a5 Mon Sep 17 00:00:00 2001 From: Stefan Behnel Date: Sat, 31 Mar 2018 11:01:22 +0200 Subject: [PATCH 2/3] Fix bug reference in news entry --- ...-31544.beTh6t.rst => 2017-09-13-19-55-35.bpo-31455.beTh6t.rst} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename Misc/NEWS.d/next/Library/{2017-09-13-19-55-35.bpo-31544.beTh6t.rst => 2017-09-13-19-55-35.bpo-31455.beTh6t.rst} (100%) diff --git a/Misc/NEWS.d/next/Library/2017-09-13-19-55-35.bpo-31544.beTh6t.rst b/Misc/NEWS.d/next/Library/2017-09-13-19-55-35.bpo-31455.beTh6t.rst similarity index 100% rename from Misc/NEWS.d/next/Library/2017-09-13-19-55-35.bpo-31544.beTh6t.rst rename to Misc/NEWS.d/next/Library/2017-09-13-19-55-35.bpo-31455.beTh6t.rst From 1c04b93f5e9d19a9b7e7c7dad27d4cd9df4bcf81 Mon Sep 17 00:00:00 2001 From: Stefan Behnel Date: Sat, 31 Mar 2018 12:05:02 +0200 Subject: [PATCH 3/3] bpo-31544: Fix a reference leak to 'self' after the previous target error handling fixes. This change generally splits the xmlparser creation code into an unsafe part with "rollback" error handling and a safe "object initialisation done" part with normal decref cleanup. --- Modules/_elementtree.c | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/Modules/_elementtree.c b/Modules/_elementtree.c index 7f0e609340033a..1d316a1c91d24b 100644 --- a/Modules/_elementtree.c +++ b/Modules/_elementtree.c @@ -2575,14 +2575,24 @@ xmlparser(PyObject* self_, PyObject* args, PyObject* kw) return NULL; } + ALLOC(sizeof(XMLParserObject), "create expatparser"); + + /* Init to NULL to keep the error handling below manageable. */ + self->target = + self->handle_xml = + self->handle_start = + self->handle_data = + self->handle_end = + self->handle_comment = + self->handle_pi = + self->handle_close = + NULL; + /* setup target handlers */ if (!target) { target = treebuilder_new(); if (!target) { - EXPAT(ParserFree)(self->parser); - PyObject_Del(self->names); - PyObject_Del(self->entity); - PyObject_Del(self); + Py_DECREF(self); return NULL; } } else @@ -2591,30 +2601,37 @@ xmlparser(PyObject* self_, PyObject* args, PyObject* kw) self->handle_xml = PyObject_GetAttrString(target, "xml"); if (ignore_attribute_error(self->handle_xml)) { + Py_DECREF(self); return NULL; } self->handle_start = PyObject_GetAttrString(target, "start"); if (ignore_attribute_error(self->handle_start)) { + Py_DECREF(self); return NULL; } self->handle_data = PyObject_GetAttrString(target, "data"); if (ignore_attribute_error(self->handle_data)) { + Py_DECREF(self); return NULL; } self->handle_end = PyObject_GetAttrString(target, "end"); if (ignore_attribute_error(self->handle_end)) { + Py_DECREF(self); return NULL; } self->handle_comment = PyObject_GetAttrString(target, "comment"); if (ignore_attribute_error(self->handle_comment)) { + Py_DECREF(self); return NULL; } self->handle_pi = PyObject_GetAttrString(target, "pi"); if (ignore_attribute_error(self->handle_pi)) { + Py_DECREF(self); return NULL; } self->handle_close = PyObject_GetAttrString(target, "close"); if (ignore_attribute_error(self->handle_close)) { + Py_DECREF(self); return NULL; } @@ -2650,8 +2667,6 @@ xmlparser(PyObject* self_, PyObject* args, PyObject* kw) ); #endif - ALLOC(sizeof(XMLParserObject), "create expatparser"); - return (PyObject*) self; }