python / python/cpython

xml.sax expatreader swallows KeyboardInterrupt during external entity parsing

未關閉
#148,427 2 則留言 0 個 reaction 已指派 0 人 在 GitHub 檢視

還沒有人認領這個 Issue。

stdlib topic-XML type-bug
主要語言
Python
星號
77.2k
分支
36k
PR 合併指標
PR 指標待擷取

描述

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

貢獻指南

開啟貢獻指南

從這裡開始

  1. 先讀完整個 Issue,再讀專案的貢獻指南。
  2. 在 Issue 下留言說明你要接手 —— 這能避免兩個人做同樣的事。
  3. Fork 儲存庫,在一個分支上完成修改。
  4. 送出 Pull Request,並在描述裡引用這個 Issue 編號。

研究方向

閱讀 Lib/xml/sax/expatreader.py 中的 ExpatParser.external_entity_ref(),然後執行提供的外部實體重現程式碼,以觀察例外傳播。完成的標準是 KeyboardInterrupt 和其他 BaseException 子類別不會被轉換為 SAXParseException,且錯誤後 _entity_stack 狀態會被清理;PR gh-148435 已經連結。

由索引模型根據 Issue 內容生成。

評估

技術堆疊
python
領域
backend
Issue 類型
缺陷
難度
2/5
預估耗時
1-3 小時
活躍度
停滯
描述清晰度
描述清楚
新手友好度
25/100

把新 issue 寄到你的電子郵件信箱

精選適合新手參與的 GitHub issue 摘要。