Skip to content

Commit e6b9a14

Browse files
raminfppicnixz
andauthored
gh-144984: Fix crash in Expat's ExternalEntityParserCreate error paths (#144992)
Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com>
1 parent 37121ef commit e6b9a14

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
@@ -843,6 +843,43 @@ def test_parent_parser_outlives_its_subparsers__chain(self):
843843
del subparser
844844

845845

846+
class ExternalEntityParserCreateErrorTest(unittest.TestCase):
847+
"""ExternalEntityParserCreate error paths should not crash or leak
848+
refcounts on the parent parser.
849+
850+
See https://github.com/python/cpython/issues/144984.
851+
"""
852+
853+
@classmethod
854+
def setUpClass(cls):
855+
cls.testcapi = import_helper.import_module('_testcapi')
856+
857+
def test_error_path_no_crash(self):
858+
# When an allocation inside ExternalEntityParserCreate fails,
859+
# the partially-initialized subparser is deallocated. This
860+
# must not dereference NULL handlers or double-decrement the
861+
# parent parser's refcount.
862+
parser = expat.ParserCreate()
863+
parser.buffer_text = True
864+
rc_before = sys.getrefcount(parser)
865+
866+
# We avoid self.assertRaises(MemoryError) here because the
867+
# context manager itself needs memory allocations that fail
868+
# while the nomemory hook is active.
869+
self.testcapi.set_nomemory(1, 10)
870+
raised = False
871+
try:
872+
parser.ExternalEntityParserCreate(None)
873+
except MemoryError:
874+
raised = True
875+
finally:
876+
self.testcapi.remove_mem_hooks()
877+
self.assertTrue(raised, "MemoryError not raised")
878+
879+
rc_after = sys.getrefcount(parser)
880+
self.assertEqual(rc_after, rc_before)
881+
882+
846883
class ReparseDeferralTest(unittest.TestCase):
847884
def test_getter_setter_round_trip(self):
848885
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
@@ -1083,11 +1083,6 @@ pyexpat_xmlparser_ExternalEntityParserCreate_impl(xmlparseobject *self,
10831083
return NULL;
10841084
}
10851085

1086-
// The new subparser will make use of the parent XML_Parser inside of Expat.
1087-
// So we need to take subparsers into account with the reference counting
1088-
// of their parent parser.
1089-
Py_INCREF(self);
1090-
10911086
new_parser->buffer_size = self->buffer_size;
10921087
new_parser->buffer_used = 0;
10931088
new_parser->buffer = NULL;
@@ -1097,21 +1092,22 @@ pyexpat_xmlparser_ExternalEntityParserCreate_impl(xmlparseobject *self,
10971092
new_parser->ns_prefixes = self->ns_prefixes;
10981093
new_parser->itself = XML_ExternalEntityParserCreate(self->itself, context,
10991094
encoding);
1100-
new_parser->parent = (PyObject *)self;
1095+
// The new subparser will make use of the parent XML_Parser inside of Expat.
1096+
// So we need to take subparsers into account with the reference counting
1097+
// of their parent parser.
1098+
new_parser->parent = Py_NewRef(self);
11011099
new_parser->handlers = 0;
11021100
new_parser->intern = Py_XNewRef(self->intern);
11031101

11041102
if (self->buffer != NULL) {
11051103
new_parser->buffer = PyMem_Malloc(new_parser->buffer_size);
11061104
if (new_parser->buffer == NULL) {
11071105
Py_DECREF(new_parser);
1108-
Py_DECREF(self);
11091106
return PyErr_NoMemory();
11101107
}
11111108
}
11121109
if (!new_parser->itself) {
11131110
Py_DECREF(new_parser);
1114-
Py_DECREF(self);
11151111
return PyErr_NoMemory();
11161112
}
11171113

@@ -1125,7 +1121,6 @@ pyexpat_xmlparser_ExternalEntityParserCreate_impl(xmlparseobject *self,
11251121
new_parser->handlers = PyMem_New(PyObject *, i);
11261122
if (!new_parser->handlers) {
11271123
Py_DECREF(new_parser);
1128-
Py_DECREF(self);
11291124
return PyErr_NoMemory();
11301125
}
11311126
clear_handlers(new_parser, 1);
@@ -2496,6 +2491,9 @@ PyInit_pyexpat(void)
24962491
static void
24972492
clear_handlers(xmlparseobject *self, int initial)
24982493
{
2494+
if (self->handlers == NULL) {
2495+
return;
2496+
}
24992497
for (size_t i = 0; handler_info[i].name != NULL; i++) {
25002498
if (initial) {
25012499
self->handlers[i] = NULL;

0 commit comments

Comments
 (0)