Repository navigation
Segfault/UB from expat when re-entering the XML Parser #146169
Description
Activity
- addedtype-crashA hard crash of the interpreter, possibly with a core dumpA hard crash of the interpreter, possibly with a core dump
on Mar 19, 2026 Yeah, that sounds sensible, unfortunately I don't know enough about expat, or how python uses it, to know the full ramifications of that change
- changed the title
[-]Segfault/UB from expat when re-entering the Parser[/-][+]Segfault/UB from expat when re-entering the XML Parser[/+]on Mar 19, 2026 - addedextension-modulesC modules in the Modules dirC modules in the Modules dir
on Mar 19, 2026 Considering I do not want PRs generated by AI and then reviews where I talk to an AI, I will fix those issues when I come back from my travels, namely next week.
Thanks for the report and reproducers though!
Reacted by Steve StaggSo as you said, there are two issues:
- The re-entrancy issue with Parse().
- The error code suppression of XML_SetEncoding().
I will make two different PRs. Note that for Parse(), we do have a weird way to handle that for the CharacterDataHandler which I don't remember why we added this:
static int xmlparse_handler_setter(PyObject *op, PyObject *v, void *closure) { ... /* If this is the character data handler, and a character data handler is already active, we need to be more careful. What we can safely do is replace the existing character data handler callback function with a no-op function that will refuse to call Python. The downside is that this doesn't completely remove the character data handler from the C layer if there's any callback active, so Expat does a little more work than it otherwise would, but that's really an odd case. A more elaborate system of handlers and state could remove the C handler more effectively. */ if (handlernum == CharacterData && self->in_callback) { c_handler = (xmlhandler)noop_character_data_handler; } ... }
in_callbackis only ever used here and inpyexpat_xmlparser_GetInputContext_implwhich only makes sense when we're in a callback. All other accesses toin_callbackare write accesses.From a FT-POV as well, I think we likely have concurrency issues despite the use of
{Py_mod_gil, Py_MOD_GIL_NOT_USED}sinceself->in_callbackcould be called set concurrently and modified concurrently as well but I'm not entirely sure on that point.@ZeroIntensity What's your insight on FT-concerns for PyExpat? should we just use the GIL in the meantime?
Ok, after fiddling a bit more with the reproducer:
- With the current code, it's actually hanging out if I already decode the content.
- If I use UTF-16 encoding for the payload, it's crashing.
- If I add a recursive guard, it's no more hanging or crashing (whether I use encoding or not).
- I suspect that an additional patch is necessary for the encoding part as well.
@stestagg @picnixz FYI Expat >=2.8.2 now protects against this form of parser re-entry by itself. The related pull requests and CVEs are:
- CVE-2026-50219 — [CVE-2026-50219] Introduce handler call depth tracking libexpat/libexpat#1246
- CVE-2026-56131 — [CVE-2026-56131] lib: Protect
XML_ResumeParserfrom being called from a handler libexpat/libexpat#1267 - CVE-2026-56412 — [CVE-2026-56412] lib: Guard
XML_TOK_DATA_CHARShandler calls indoCdataSectionlibexpat/libexpat#1278
Reacted by Steve Stagg and Piraty- added a commit that references this issue
on Jun 25, 2026 @ZeroIntensity What's your insight on FT-concerns for PyExpat? should we just use the GIL in the meantime?
Sorry, missed this. We really don't want to re-enable the GIL for any module. Are the Python objects thread-unsafe, or the Expat library as a whole?
- added a commit that references this issue
on Jun 28, 2026
Crash report
What happened?
Semi-reliable(Interstingly, python in macos doesn't segfault for me, but docker/linux aarch64 does reliably) crash with this code:
This code /is/ doing some pretty naughty stuff, but the main problem seems to be that the handler is being set to re-enter the parser. The expat docs do say:
and I see that the python expat parser code tracks in_callback:
cpython/Modules/pyexpat.c
Line 91 in 52c0186
So I wonder if we can avoid the segfault by preventing Parse calls when in_callback==true?
There's also a secondary issue in play here, that Parse() seems to call
XML_SetEncoding
cpython/Modules/pyexpat.c
Line 872 in 52c0186
Without the check outlined in the expat docs:
This is almost definitely not going to cause issues unless the encoding is actually changing, (not that common) at which point the UB will rear its head as the internal state of the parser becomes inconsistent.
Dockerfile reproducer
Gives on my pc:
CPython versions tested on:
CPython main branch
Operating systems tested on:
macOS
Output from running 'python -VV' on the command line:
Python 3.15.0a7+ (heads/main:52c0186, Mar 19 2026, 13:06:19) [GCC 13.3.0]
Linked PRs