python / python/cpython

xml.sax expatreader swallows KeyboardInterrupt during external entity parsing

Abierto
#148,427 2 comentarios 0 reacciones 0 asignados Ver en GitHub

Nadie ha tomado este issue todavía.

stdlib topic-XML type-bug
Lenguaje dominante
Python
Estrellas
77.2k
Forks
35.9k
Métricas de merge de PR
Métricas de PR pendientes

Descripción

When parsing XML with external entity resolution enabled, pressing Ctrl+C (or raising KeyboardInterrupt / SystemExit inside a content handler) is silently swallowed and converted into a generic SAXParseException.

The root cause is a bare except: in ExpatParser.external_entity_ref() at Lib/xml/sax/expatreader.py line 427:

try:
    xmlreader.IncrementalParser.parse(self, source)
except:
    return 0  # FIXME: save error info here?

The bare except: catches everything — KeyboardInterrupt, SystemExit, MemoryError — and returns 0 to expat, which then raises a generic "error in processing external entity reference". The inline FIXME notes the error info is lost, but the broader issue is that BaseException subclasses like KeyboardInterrupt should never be caught here at all.

There's also a secondary issue: the _entity_stack cleanup (lines 430–431) only runs on success, so the parser's internal state is corrupted after any error during entity parsing.

Reproduction
import xml.sax
from xml.sax.handler import feature_external_ges
from xml.sax import ContentHandler
from xml.sax.xmlreader import InputSource
from io import BytesIO

class KBHandler(ContentHandler):
    def startElement(self, name, attrs):
        if name == 'entity':
            raise KeyboardInterrupt('simulated Ctrl+C')

class Resolver:
    def resolveEntity(self, pubId, sysId):
        src = InputSource()
        src.setByteStream(BytesIO(b'<entity/>'))
        return src

parser = xml.sax.make_parser()
parser.setFeature(feature_external_ges, True)
parser.setEntityResolver(Resolver())
parser.setContentHandler(KBHandler())

try:
    parser.feed('<!DOCTYPE d [<!ENTITY e SYSTEM "x">]><d>&e;</d>')
    parser.close()
except KeyboardInterrupt:
    print('GOOD: KeyboardInterrupt propagated')
except xml.sax.SAXParseException as e:
    print(f'BUG: KeyboardInterrupt became SAXParseException: {e}')

Output: BUG: KeyboardInterrupt became SAXParseException: <unknown>:1:6: error in processing external entity reference

Suggested fix

Change except: to except Exception: and move the _entity_stack cleanup into a finally block. I checked pyexpat.c — the C layer handles Python exception propagation correctly through call_with_frame() / XML_StopParser() / get_parse_result(), so letting KeyboardInterrupt pass through is safe.

The other bare except: in the same file (line 104 in parse()) correctly re-raises after cleanup, so this is not a deliberate pattern.

Linked PRs
  • gh-148435

Guía de contribución

Abrir la guía de contribución

Primeros pasos

  1. Lee el issue completo y luego la guía de contribución del proyecto.
  2. Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
  3. Haz un fork del repositorio y trabaja en una rama.
  4. Abre un pull request que haga referencia al número del issue.

Línea de trabajo

Lee Lib/xml/sax/expatreader.py en ExpatParser.external_entity_ref() y luego ejecuta la reproducción proporcionada de la entidad externa para observar la propagación de excepciones. Se considera terminado cuando KeyboardInterrupt y otras subclases de BaseException no se convierten en SAXParseException y el estado de _entity_stack se limpia después de los errores; PR gh-148435 ya está vinculado.

Escrito por el modelo de indexación a partir del texto del issue.

Evaluación

Stack tecnológico
python
Área
backend
Tipo de issue
Error
Dificultad
2/5
Tiempo estimado
1-3 horas
Estado de actividad
Estancado
Claridad
Bien especificado
Aptitud para principiantes
25/100

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.