Skip to content

Commit 0ba58e7

Browse files
authored
gh-159739: Handle pyexpat configuration changes during callbacks (#160399)
A character data callback can disable buffering or remove the next event handler during a flush. Interning an attribute name can also change ordered_attributes while pyexpat is filling the attribute container. Using the freed buffer, cleared handler, or wrong container type can crash the interpreter. Clear the buffered length before dispatch, keep the active handler alive, recheck the buffer and handlers after callbacks, and use one attribute mode for the whole start event.
1 parent 1ff83ad commit 0ba58e7

3 files changed

Lines changed: 77 additions & 29 deletions

File tree

‎Lib/test/test_pyexpat.py‎

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -910,6 +910,34 @@ def test_disabling_buffer(self):
910910
parser.Parse(xml3, True)
911911
self.assertEqual(self.n, 12)
912912

913+
def test_disable_buffer_in_character_handler(self):
914+
parser = expat.ParserCreate()
915+
parser.buffer_size = 2
916+
parser.buffer_text = True
917+
seen = []
918+
919+
def consume(text):
920+
seen.append(text)
921+
if len(seen) == 1:
922+
parser.buffer_text = False
923+
924+
parser.CharacterDataHandler = consume
925+
parser.Parse(b'<r>a&#65;bc</r>', True)
926+
self.assertEqual(''.join(seen), 'aAbc')
927+
928+
def test_clear_element_handler_during_flush(self):
929+
parser = expat.ParserCreate()
930+
parser.buffer_text = True
931+
elements = []
932+
933+
def consume(text):
934+
parser.StartElementHandler = None
935+
936+
parser.CharacterDataHandler = consume
937+
parser.StartElementHandler = lambda name, attrs: elements.append(name)
938+
parser.Parse(b'<r>abc<s/></r>', True)
939+
self.assertEqual(elements, ['r'])
940+
913941
def counting_handler(self, text):
914942
self.n += 1
915943

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fix crashes in :mod:`xml.parsers.expat` when a character data callback
2+
disables buffering or clears another handler during a flush, or when interning
3+
an attribute name changes :attr:`~xml.parsers.expat.xmlparser.ordered_attributes`.

‎Modules/pyexpat.c‎

Lines changed: 46 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -317,7 +317,9 @@ call_with_frame(const char *funcname, int lineno, PyObject* func, PyObject* args
317317
{
318318
PyObject *res;
319319

320+
Py_INCREF(func);
320321
res = PyObject_Call(func, args, NULL);
322+
Py_DECREF(func);
321323
if (res == NULL) {
322324
_PyTraceback_Add(funcname, __FILE__, lineno);
323325
XML_StopParser(self->itself, XML_FALSE);
@@ -357,19 +359,25 @@ call_character_handler(xmlparseobject *self, const XML_Char *buffer, int len)
357359
if (!have_handler(self, CharacterData))
358360
return -1;
359361

360-
args = PyTuple_New(1);
361-
if (args == NULL)
362-
return -1;
363-
temp = (conv_string_len_to_unicode(buffer, len));
362+
/* Copy the buffer before tuple allocation can trigger a GC callback. */
363+
temp = conv_string_len_to_unicode(buffer, len);
364364
if (temp == NULL) {
365-
Py_DECREF(args);
366365
flag_error(self);
367366
XML_SetCharacterDataHandler(self->itself,
368367
noop_character_data_handler);
369368
return -1;
370369
}
370+
args = PyTuple_New(1);
371+
if (args == NULL) {
372+
Py_DECREF(temp);
373+
return -1;
374+
}
371375
PyTuple_SET_ITEM(args, 0, temp);
372376
/* temp is now a borrowed reference; consider it unused. */
377+
if (!have_handler(self, CharacterData)) {
378+
Py_DECREF(args);
379+
return 0;
380+
}
373381
self->in_callback = 1;
374382
temp = call_with_frame("CharacterData", __LINE__,
375383
self->handlers[CharacterData], args, self);
@@ -389,12 +397,13 @@ call_character_handler(xmlparseobject *self, const XML_Char *buffer, int len)
389397
static int
390398
flush_character_buffer(xmlparseobject *self)
391399
{
392-
int rc;
393400
if (self->buffer == NULL || self->buffer_used == 0)
394401
return 0;
395-
rc = call_character_handler(self, self->buffer, self->buffer_used);
402+
int len = self->buffer_used;
403+
/* The callback may change buffering or install a new handler, which
404+
also flushes. Do not expose the same pending data a second time. */
396405
self->buffer_used = 0;
397-
return rc;
406+
return call_character_handler(self, self->buffer, len);
398407
}
399408

400409
static void
@@ -405,27 +414,22 @@ my_CharacterDataHandler(void *userData, const XML_Char *data, int len)
405414
if (PyErr_Occurred())
406415
return;
407416

408-
if (self->buffer == NULL)
417+
if (self->buffer != NULL &&
418+
len > (self->buffer_size - self->buffer_used)) {
419+
if (flush_character_buffer(self) < 0)
420+
return;
421+
/* The callback may replace the handler or disable buffering. */
422+
if (!have_handler(self, CharacterData))
423+
return;
424+
}
425+
if (self->buffer == NULL ||
426+
len > (self->buffer_size - self->buffer_used)) {
409427
call_character_handler(self, data, len);
428+
}
410429
else {
411-
if (len > (self->buffer_size - self->buffer_used)) {
412-
if (flush_character_buffer(self) < 0)
413-
return;
414-
/* handler might have changed; drop the rest on the floor
415-
* if there isn't a handler anymore
416-
*/
417-
if (!have_handler(self, CharacterData))
418-
return;
419-
}
420-
if (len > self->buffer_size) {
421-
call_character_handler(self, data, len);
422-
self->buffer_used = 0;
423-
}
424-
else {
425-
memcpy(self->buffer + self->buffer_used,
426-
data, len * sizeof(XML_Char));
427-
self->buffer_used += len;
428-
}
430+
memcpy(self->buffer + self->buffer_used,
431+
data, len * sizeof(XML_Char));
432+
self->buffer_used += len;
429433
}
430434
}
431435

@@ -456,7 +460,9 @@ my_StartElementHandler(void *userData,
456460
max += 2;
457461
}
458462
/* Build the container. */
459-
if (self->ordered_attributes)
463+
/* Intern-dictionary comparisons can change the mode mid-loop. */
464+
int ordered_attributes = self->ordered_attributes;
465+
if (ordered_attributes)
460466
container = PyList_New(max);
461467
else
462468
container = PyDict_New();
@@ -479,7 +485,7 @@ my_StartElementHandler(void *userData,
479485
Py_DECREF(n);
480486
return;
481487
}
482-
if (self->ordered_attributes) {
488+
if (ordered_attributes) {
483489
PyList_SET_ITEM(container, i, n);
484490
PyList_SET_ITEM(container, i+1, v);
485491
}
@@ -505,6 +511,10 @@ my_StartElementHandler(void *userData,
505511
return;
506512
}
507513
/* Container is now a borrowed reference; ignore it. */
514+
if (!have_handler(self, StartElement)) {
515+
Py_DECREF(args);
516+
return;
517+
}
508518
self->in_callback = 1;
509519
rv = call_with_frame("StartElement", __LINE__,
510520
self->handlers[StartElement], args, self);
@@ -561,6 +571,10 @@ my_ ## NAME ## Handler PARAMS { \
561571
flag_error(self); \
562572
return RETURN_VARIABLE; \
563573
} \
574+
if (!have_handler(self, NAME)) { \
575+
Py_DECREF(args); \
576+
return RETURN_VARIABLE; \
577+
} \
564578
self->in_callback = 1; \
565579
PyObject *rv = call_with_frame( \
566580
#NAME, __LINE__, \
@@ -709,6 +723,9 @@ my_ElementDeclHandler(void *userData,
709723
flag_error(self);
710724
goto finally;
711725
}
726+
if (!have_handler(self, ElementDecl)) {
727+
goto finally;
728+
}
712729
self->in_callback = 1;
713730
rv = call_with_frame("ElementDecl", __LINE__,
714731
self->handlers[ElementDecl], args, self);

0 commit comments

Comments
 (0)