fe5c369f217719e724782de8d29f29edc5f203a6 [M150] Protect ParseChunk against re-entrancy
The bug at a glance
The diff’s own comments say a nested xmlParseChunk() can xmlRealloc() ctxt->pushTab/nsTab buffers while xmlParseEndTag2 still holds raw pointers into them, which is heap use-after-free/corruption in the renderer. It was cherry-picked to an M150 release branch with a restricted bug, which fits a high-severity renderer memory-safety fix.
XML documents are parsed by libxml2 inside the renderer, so if web content can make Blink feed data to the parser while a SAX callback is still running, it gets dangling pointers into reallocated libxml2 arrays. That is a classic starting point for renderer code execution.
Root cause
XMLDocumentParser::DoWrite() drives libxml2’s push parser through xmlParseChunk(), and the parser calls back into Blink (SAX callbacks) in the middle of a chunk. If such a callback re-entered Append()/DoWrite() while not paused, a nested xmlParseChunk() ran on the same context and could reallocate ctxt->pushTab/nsTab, while the outer frame (e.g. xmlParseEndTag2) kept raw pointers into the freed buffers. The fix adds an in_parse_chunk_ flag: Append() queues data into pending_src_ while it is set, and DoWrite() CHECKs and sets it with an AutoReset guard.
Key code
Re-entrant data is deferred to pending_src_, and nested xmlParseChunk becomes a safe CHECK crash
- if (parser_paused_) {
+ if (parser_paused_ || in_parse_chunk_) {
pending_src_.Append(source);
return;
}
...
+ CHECK(!in_parse_chunk_);
+ base::AutoReset<bool> reentrancy_guard(&in_parse_chunk_, true);
Patch walkthrough
third_party/blink/renderer/core/xml/parser/xml_document_parser.cc— Append() now also defers input to pending_src_ while a chunk is being parsed. DoWrite() hard-CHECKs against re-entry and sets in_parse_chunk_ for the duration of the xmlParseChunk call, so libxml2 context buffers cannot be reallocated under the outer parse frame.third_party/blink/renderer/core/xml/parser/xml_document_parser.h— Adds the bool in_parse_chunk_ member, with comments documenting that libxml2 push contexts are not re-entrant and that nesting corrupts ctxt->pushTab/nsTab.
Background
libxml2 push parser (xmlParseChunk) — An incremental parser API that keeps growable arrays (node, name, namespace and push stacks) in xmlParserCtxt and calls SAX callbacks while it parses.
XMLDocumentParser — Blink’s XML/XHTML/SVG document parser; its SAX callbacks build DOM and can run author-observable work while libxml2 is mid-parse (inferred).
pending_src_ — Blink’s buffer for input that arrives while the parser cannot consume it; it is drained when parsing resumes.
Vulnerability window
- Reachability — Ordinary web content in a stock build can serve XML/XHTML/SVG documents parsed by this code. The re-entry needs a SAX callback that synchronously leads back into Append() while the parser is not paused (inferred, for example via script- or DOM-driven writes into the parsing document).
- Trigger — During an outer xmlParseChunk, a callback causes a nested DoWrite/xmlParseChunk on the same context, which grows and reallocates ctxt->pushTab/nsTab.
- Primitive — The outer xmlParseEndTag2 frame keeps using raw pointers into the freed buffers, a heap use-after-free/write in the renderer (per the diff comments).
- Escalation — Heap corruption in the renderer could be turned into renderer code execution and then chained with a sandbox escape (inferred).
Proof of concept
Conceptually: load an XML-family document so that, during element processing, a parser callback synchronously appends more markup to the same document while the parser is not paused. The nested parse then grows libxml2’s namespace/push stacks while an end-tag parse is in progress. No code is provided.
Exploitation
- Re-entry — Find a SAX callback path that synchronously reaches XMLDocumentParser::Append() without pausing the parser (inferred).
- Reallocation — Make the nested chunk push enough names/namespaces that libxml2 reallocates ctxt->nsTab/pushTab (inferred).
- Use of stale pointers — The outer xmlParseEndTag2 reads or writes through stale pointers into the freed allocation, which an attacker would try to reclaim with controlled data (inferred).
Detection & hunting
For defenders and detection engineers:
- CHECK(!in_parse_chunk_) crash reports in XMLDocumentParser::DoWrite — After the patch, any attempted re-entry crashes safely here; crash clusters with this signature show attempted triggers or new re-entry paths.
- ASan heap-use-after-free in xmlParseEndTag2 / xmlParseChunk with XMLDocumentParser frames on the stack — On unpatched builds, fuzz XML documents with script-driven writes and look for this stack shape.
Audit directions
- Other Blink callers of libxml2/libxslt push or SAX APIs (XSLT processing, XMLHttpRequest document parsing, DOMParser for XML) — Check whether any callback can synchronously re-enter xmlParseChunk or another API that mutates the same ctxt.
- XMLDocumentParser callbacks that run script or fire events (script elements, processing instructions, custom element reactions) — Make sure every path that can run author code mid-chunk either pauses the parser or is now covered by in_parse_chunk_.