XML Parser Billion-Laughs Mitigation via Deferred Entity Registration
d093eba
Source/WebCore/xml/parser/XMLDocumentParserLibxml2.cpp
+void XMLDocumentParser::entityDecl(const xmlChar* name, int type, const xmlChar* publicId, const xmlChar* systemId, xmlChar* content)
+{
+ auto contentString = toString(content);
+ if (type == XML_INTERNAL_GENERAL_ENTITY && contentString.contains('&')) {
+ m_deferredEntityDeclarations.append({...});
+ m_deferredEntityDeclarationsFlushed = false;
+ return;
+ }
+ xmlSAX2EntityDecl(context(), name, type, publicId, systemId, content);
+}
+
+void XMLDocumentParser::flushDeferredEntityDeclarations()
+{
+ m_deferredEntityDeclarationsFlushed = true;
+ m_entityTransitiveReferenceCounts.clear();
+ ...
+ for (auto& decl : m_deferredEntityDeclarations) {
+ HashSet<AtomString> visiting;
+ if (computeTransitiveEntityReferenceCount(entityName, entityContents, visiting) > m_maxEntityExpansionCount) {
+ handleError(XMLErrors::Type::Fatal, "Entity reference expansion limit reached", textPosition());
+ return;
+ }
+ xmlSAX2EntityDecl(context(), ...);
+ }
+}
WebKit XML parser에서 발생하던 billion-laughs(지수적 entity 확장) hang을 수정합니다. 기존에는 xmlSAX2EntityDecl을 통해 internal entity를 즉시 등록했습니다. 수정 후에는 모든 선언을 먼저 수집한 뒤, memoized recursion으로 transitive entity reference count를 계산하는 방식으로 변경되었습니다. Transitive 확장 횟수가 512를 초과하는 entity는 등록 자체가 차단되고, 대신 parse error가 발생합니다.
macOS에 내장된 libxml2 버전은 libxml2 자체의 mitigation API인 xmlCtxtSetMaxAmplification을 지원하지 않습니다. 이 때문에 별도의 deferred registration 방식이 필요했습니다.
Before (synchronous registration):
DTD parsing
└─► entityDecl SAX callback
└─► xmlSAX2EntityDecl() ──► registered, libxml2 can expand recursively
After (deferred with transitive count):
DTD parsing → append to m_deferredEntityDeclarations[]
Entity reference → flushDeferredEntityDeclarations()
└─► computeTransitiveEntityReferenceCount() [memoized]
├── count ≤ 512: register with xmlSAX2EntityDecl()
└── count > 512: fatal parse error
한 가지 중요한 복잡성이 있습니다. libxml2의 xmlParseBalancedChunkMemoryInternal은 중첩 parser context를 생성하며, 이 과정에서 SAX의 getEntity callback을 우회하고 xmlGetDocEntity를 직접 호출합니다. 따라서 SAX 레이어에만 guard를 두면, entity가 이미 document에 등록된 경우 해당 guard는 무력화됩니다. 등록 자체를 지연하는 방식은 위험한 entity가 애초에 document에 등록되지 않도록 보장합니다.
Significance
WebKit에서 로드되는 모든 XML 문서(XHTML, SVG, MathML)를 통해 도달 가능한 DoS 수준의 hang이었습니다. 페이지를 로드하는 것 이외의 별도 상호작용은 필요하지 않았습니다.
Audit directions
a Aaa Aaaaaa Aaaaaaa Aaaaa Aaaaaaaa Aaa Aaaa Aaaaaa Aaaaaaaaa Aaaaaaa Aaaa Aa Aaa Aaaa Aaaa Aa Aaaaaa Aaaa Aaa Aaaaaa Aaaaaaaaa Aa Aaa Aaa Aaa Aaa Aaa Aaaaa Aa Aaa Aaaa Aaaaa Aaaa Aaaaa Aaaaaaa Aaaaaaaaa Aaaaaaaaaa Aa Aaaaaa Aaaa Aaaa Aaaaaa a Aa Aaaa Aaaaaaaaaa Aaaaaaa Aaaa Aa Aaa Aa a Aaaaa Aaaaa Aaaaaa Aaaaaa Aaaaaaaaaa Aa Aa Aaaaaa Aa Aa Aaa Aaa Aaa Aaaaaa Aaa Aaaa Aaaa Aaaaaa Aaaaaa Aaaaaa Aaa a Aaa Aaa Aaaaaa Aa Aaaaaaaaa Aaaaaaaaaaaaaaaaa Aa Aaaaaaa Aa Aaa Aaaa Aaaaaaaa Aaaaa Aaa Aaaaaa Aaa Aaaaaa Aaaa Aaaa Aaaa
🔒Several edge cases in the new entity counting logic and its interaction with libxml2's own expansion paths are worth security investigation.
더 확인하려면 구독해 주세요