Skip to content

Commit 6da136f

Browse files
raminfppicnixz
authored andcommitted
gh-144984: Fix crash in Expat's ExternalEntityParserCreate error paths (GH-144992)
(cherry picked from commit e6b9a14) Co-authored-by: Ramin Farajpour Cami <ramin.blackhat@gmail.com> Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com>
1 parent 8bc3aa9 commit 6da136f

File tree

3 files changed

+47
-9
lines changed

3 files changed

+47
-9
lines changed

Lib/test/test_pyexpat.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -831,6 +831,43 @@ def test_parent_parser_outlives_its_subparsers__chain(self):
831831
del subparser
832832

833833

834+
class ExternalEntityParserCreateErrorTest(unittest.TestCase):
835+
"""ExternalEntityParserCreate error paths should not crash or leak
836+
refcounts on the parent parser.
837+
838+
See https://github.com/python/cpython/issues/144984.
839+
"""
840+
841+
@classmethod
842+
def setUpClass(cls):
843+
cls.testcapi = import_helper.import_module('_testcapi')
844+
845+
def test_error_path_no_crash(self):
846+
# When an allocation inside ExternalEntityParserCreate fails,
847+
# the partially-initialized subparser is deallocated. This
848+
# must not dereference NULL handlers or double-decrement the
849+
# parent parser's refcount.
850+
parser = expat.ParserCreate()
851+
parser.buffer_text = True
852+
rc_before = sys.getrefcount(parser)
853+
854+
# We avoid self.assertRaises(MemoryError) here because the
855+
# context manager itself needs memory allocations that fail
856+
# while the nomemory hook is active.
857+
self.testcapi.set_nomemory(1, 10)
858+
raised = False
859+
try:
860+
parser.ExternalEntityParserCreate(None)
861+
except MemoryError:
862+
raised = True
863+
finally:
864+
self.testcapi.remove_mem_hooks()
865+
self.assertTrue(raised, "MemoryError not raised")
866+
867+
rc_after = sys.getrefcount(parser)
868+
self.assertEqual(rc_after, rc_before)
869+
870+
834871
class ReparseDeferralTest(unittest.TestCase):
835872
def test_getter_setter_round_trip(self):
836873
parser = expat.ParserCreate()
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fix crash in :meth:`xml.parsers.expat.xmlparser.ExternalEntityParserCreate`
2+
when an allocation fails. The error paths could dereference NULL ``handlers``
3+
and double-decrement the parent parser's reference count.

Modules/pyexpat.c

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1076,11 +1076,6 @@ pyexpat_xmlparser_ExternalEntityParserCreate_impl(xmlparseobject *self,
10761076
return NULL;
10771077
}
10781078

1079-
// The new subparser will make use of the parent XML_Parser inside of Expat.
1080-
// So we need to take subparsers into account with the reference counting
1081-
// of their parent parser.
1082-
Py_INCREF(self);
1083-
10841079
new_parser->buffer_size = self->buffer_size;
10851080
new_parser->buffer_used = 0;
10861081
new_parser->buffer = NULL;
@@ -1090,21 +1085,22 @@ pyexpat_xmlparser_ExternalEntityParserCreate_impl(xmlparseobject *self,
10901085
new_parser->ns_prefixes = self->ns_prefixes;
10911086
new_parser->itself = XML_ExternalEntityParserCreate(self->itself, context,
10921087
encoding);
1093-
new_parser->parent = (PyObject *)self;
1088+
// The new subparser will make use of the parent XML_Parser inside of Expat.
1089+
// So we need to take subparsers into account with the reference counting
1090+
// of their parent parser.
1091+
new_parser->parent = Py_NewRef(self);
10941092
new_parser->handlers = 0;
10951093
new_parser->intern = Py_XNewRef(self->intern);
10961094

10971095
if (self->buffer != NULL) {
10981096
new_parser->buffer = PyMem_Malloc(new_parser->buffer_size);
10991097
if (new_parser->buffer == NULL) {
11001098
Py_DECREF(new_parser);
1101-
Py_DECREF(self);
11021099
return PyErr_NoMemory();
11031100
}
11041101
}
11051102
if (!new_parser->itself) {
11061103
Py_DECREF(new_parser);
1107-
Py_DECREF(self);
11081104
return PyErr_NoMemory();
11091105
}
11101106

@@ -1118,7 +1114,6 @@ pyexpat_xmlparser_ExternalEntityParserCreate_impl(xmlparseobject *self,
11181114
new_parser->handlers = PyMem_New(PyObject *, i);
11191115
if (!new_parser->handlers) {
11201116
Py_DECREF(new_parser);
1121-
Py_DECREF(self);
11221117
return PyErr_NoMemory();
11231118
}
11241119
clear_handlers(new_parser, 1);
@@ -2397,6 +2392,9 @@ PyInit_pyexpat(void)
23972392
static void
23982393
clear_handlers(xmlparseobject *self, int initial)
23992394
{
2395+
if (self->handlers == NULL) {
2396+
return;
2397+
}
24002398
for (size_t i = 0; handler_info[i].name != NULL; i++) {
24012399
if (initial) {
24022400
self->handlers[i] = NULL;

0 commit comments

Comments
 (0)