Skip to content

Commit 282a34e

Browse files
[3.14] gh-159739: Handle pyexpat configuration changes during callbacks (GH-160399) (#160474)
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. (cherry picked from commit 0ba58e7) Co-authored-by: Jelle Zijlstra <jelle@openai.com>
1 parent c7fd7ed commit 282a34e

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
@@ -821,6 +821,34 @@ def test_disabling_buffer(self):
821821
parser.Parse(xml3, True)
822822
self.assertEqual(self.n, 12)
823823

824+
def test_disable_buffer_in_character_handler(self):
825+
parser = expat.ParserCreate()
826+
parser.buffer_size = 2
827+
parser.buffer_text = True
828+
seen = []
829+
830+
def consume(text):
831+
seen.append(text)
832+
if len(seen) == 1:
833+
parser.buffer_text = False
834+
835+
parser.CharacterDataHandler = consume
836+
parser.Parse(b'<r>a&#65;bc</r>', True)
837+
self.assertEqual(''.join(seen), 'aAbc')
838+
839+
def test_clear_element_handler_during_flush(self):
840+
parser = expat.ParserCreate()
841+
parser.buffer_text = True
842+
elements = []
843+
844+
def consume(text):
845+
parser.StartElementHandler = None
846+
847+
parser.CharacterDataHandler = consume
848+
parser.StartElementHandler = lambda name, attrs: elements.append(name)
849+
parser.Parse(b'<r>abc<s/></r>', True)
850+
self.assertEqual(elements, ['r'])
851+
824852
def counting_handler(self, text):
825853
self.n += 1
826854

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
@@ -312,7 +312,9 @@ call_with_frame(const char *funcname, int lineno, PyObject* func, PyObject* args
312312
{
313313
PyObject *res;
314314

315+
Py_INCREF(func);
315316
res = PyObject_Call(func, args, NULL);
317+
Py_DECREF(func);
316318
if (res == NULL) {
317319
_PyTraceback_Add(funcname, __FILE__, lineno);
318320
XML_StopParser(self->itself, XML_FALSE);
@@ -352,19 +354,25 @@ call_character_handler(xmlparseobject *self, const XML_Char *buffer, int len)
352354
if (!have_handler(self, CharacterData))
353355
return -1;
354356

355-
args = PyTuple_New(1);
356-
if (args == NULL)
357-
return -1;
358-
temp = (conv_string_len_to_unicode(buffer, len));
357+
/* Copy the buffer before tuple allocation can trigger a GC callback. */
358+
temp = conv_string_len_to_unicode(buffer, len);
359359
if (temp == NULL) {
360-
Py_DECREF(args);
361360
flag_error(self);
362361
XML_SetCharacterDataHandler(self->itself,
363362
noop_character_data_handler);
364363
return -1;
365364
}
365+
args = PyTuple_New(1);
366+
if (args == NULL) {
367+
Py_DECREF(temp);
368+
return -1;
369+
}
366370
PyTuple_SET_ITEM(args, 0, temp);
367371
/* temp is now a borrowed reference; consider it unused. */
372+
if (!have_handler(self, CharacterData)) {
373+
Py_DECREF(args);
374+
return 0;
375+
}
368376
self->in_callback = 1;
369377
temp = call_with_frame("CharacterData", __LINE__,
370378
self->handlers[CharacterData], args, self);
@@ -384,12 +392,13 @@ call_character_handler(xmlparseobject *self, const XML_Char *buffer, int len)
384392
static int
385393
flush_character_buffer(xmlparseobject *self)
386394
{
387-
int rc;
388395
if (self->buffer == NULL || self->buffer_used == 0)
389396
return 0;
390-
rc = call_character_handler(self, self->buffer, self->buffer_used);
397+
int len = self->buffer_used;
398+
/* The callback may change buffering or install a new handler, which
399+
also flushes. Do not expose the same pending data a second time. */
391400
self->buffer_used = 0;
392-
return rc;
401+
return call_character_handler(self, self->buffer, len);
393402
}
394403

395404
static void
@@ -400,27 +409,22 @@ my_CharacterDataHandler(void *userData, const XML_Char *data, int len)
400409
if (PyErr_Occurred())
401410
return;
402411

403-
if (self->buffer == NULL)
412+
if (self->buffer != NULL &&
413+
len > (self->buffer_size - self->buffer_used)) {
414+
if (flush_character_buffer(self) < 0)
415+
return;
416+
/* The callback may replace the handler or disable buffering. */
417+
if (!have_handler(self, CharacterData))
418+
return;
419+
}
420+
if (self->buffer == NULL ||
421+
len > (self->buffer_size - self->buffer_used)) {
404422
call_character_handler(self, data, len);
423+
}
405424
else {
406-
if (len > (self->buffer_size - self->buffer_used)) {
407-
if (flush_character_buffer(self) < 0)
408-
return;
409-
/* handler might have changed; drop the rest on the floor
410-
* if there isn't a handler anymore
411-
*/
412-
if (!have_handler(self, CharacterData))
413-
return;
414-
}
415-
if (len > self->buffer_size) {
416-
call_character_handler(self, data, len);
417-
self->buffer_used = 0;
418-
}
419-
else {
420-
memcpy(self->buffer + self->buffer_used,
421-
data, len * sizeof(XML_Char));
422-
self->buffer_used += len;
423-
}
425+
memcpy(self->buffer + self->buffer_used,
426+
data, len * sizeof(XML_Char));
427+
self->buffer_used += len;
424428
}
425429
}
426430

@@ -451,7 +455,9 @@ my_StartElementHandler(void *userData,
451455
max += 2;
452456
}
453457
/* Build the container. */
454-
if (self->ordered_attributes)
458+
/* Intern-dictionary comparisons can change the mode mid-loop. */
459+
int ordered_attributes = self->ordered_attributes;
460+
if (ordered_attributes)
455461
container = PyList_New(max);
456462
else
457463
container = PyDict_New();
@@ -474,7 +480,7 @@ my_StartElementHandler(void *userData,
474480
Py_DECREF(n);
475481
return;
476482
}
477-
if (self->ordered_attributes) {
483+
if (ordered_attributes) {
478484
PyList_SET_ITEM(container, i, n);
479485
PyList_SET_ITEM(container, i+1, v);
480486
}
@@ -500,6 +506,10 @@ my_StartElementHandler(void *userData,
500506
return;
501507
}
502508
/* Container is now a borrowed reference; ignore it. */
509+
if (!have_handler(self, StartElement)) {
510+
Py_DECREF(args);
511+
return;
512+
}
503513
self->in_callback = 1;
504514
rv = call_with_frame("StartElement", __LINE__,
505515
self->handlers[StartElement], args, self);
@@ -534,6 +544,10 @@ my_ ## NAME ## Handler PARAMS { \
534544
flag_error(self); \
535545
return RETURN_VARIABLE; \
536546
} \
547+
if (!have_handler(self, NAME)) { \
548+
Py_DECREF(args); \
549+
return RETURN_VARIABLE; \
550+
} \
537551
self->in_callback = 1; \
538552
PyObject *rv = call_with_frame( \
539553
#NAME, __LINE__, \
@@ -679,6 +693,9 @@ my_ElementDeclHandler(void *userData,
679693
flag_error(self);
680694
goto finally;
681695
}
696+
if (!have_handler(self, ElementDecl)) {
697+
goto finally;
698+
}
682699
self->in_callback = 1;
683700
rv = call_with_frame("ElementDecl", __LINE__,
684701
self->handlers[ElementDecl], args, self);

0 commit comments

Comments
 (0)