[10] DocumentLoader ScriptDisallowedScope Bypass via PluginView Destruction
Severity: Medium | Component: WebCore document loader | 7fec184
ScriptDisallowedScope 내에서 JavaScript가 실행된다는 점에서 Medium으로 평가합니다. Debug 빌드에서는 assertion failure가 발생하고, release 빌드에서는 조용히 re-entrancy가 발생합니다. 잠재적 escalation 경로로는 style recalculation 중 DOM mutation을 통한 memory corruption이 있습니다. 다만 이를 실현하려면 공격자의 JS callback이 recalculation call stack의 특정 stale reference를 건드려야 하며, 이론적으로는 가능하지만 검증되지 않은 시나리오입니다.
Patch Details
DocumentLoader::removePlugInStreamLoader()에서 checkLoadComplete()를 동기적으로 호출하던 방식이 document의 event loop에 비동기 task를 추가하는 방식으로 변경되었습니다. 새 코드는 m_frame과 m_frame->document()가 null이 아닌지 먼저 확인하며, task 실행 전에 DocumentLoader가 소멸되지 않도록 Ref { *this }를 capture합니다.
Source/WebCore/loader/DocumentLoader.cpp
void DocumentLoader::removePlugInStreamLoader(ResourceLoader& loader)
{
ASSERT(m_plugInStreamLoaders.contains(&loader));
-
m_plugInStreamLoaders.remove(&loader);
- checkLoadComplete();
+ if (m_frame && m_frame->document()) {
+ protect(m_frame->document())->eventLoop().queueTask(TaskSource::Networking, [protectedThis = Ref { *this }]() {
+ protectedThis->checkLoadComplete();
+ });
+ }
}
LayoutTests/fast/pdf-plugin-destruction-dispatches-print-event-crash.html
+<embed id="embed" style="top: 200vmin; visibility: hidden;" type="application/pdf" src="about:blank"></embed>
+<script>
+ window.testRunner?.dumpAsText();
+ window.testRunner?.waitUntilDone();
+ window.addEventListener("load", _ => {
+ embed.setAttribute("onsubmit", "");
+ window.print();
+ requestAnimationFrame(_ => requestAnimationFrame(_ => window.testRunner?.notifyDone()));
+ });
+</script>
Style recalculation 중 ScriptDisallowedScope 내부에서 발생하는 동기적 script 유발 callback.
Background
ScriptDisallowedScope는 WebKit에서 사용하는 RAII guard로, 해당 scope가 살아있는 동안 main thread에서 JavaScript가 실행되지 않음을 보장합니다. Debug 빌드에서는 assertion으로 강제되지만, release 빌드에서는 실행 자체를 막지 못합니다. 따라서 이 규칙을 위반하는 코드는 단순한 debug crash가 아니라 실제 attack surface가 됩니다.
Document::updateStyleIfNeeded()는 document의 style recalculation을 수행하는 함수입니다. 이 과정에서는 style/layout tree가 중간 상태에 있을 수 있기 때문에 ScriptDisallowedScope가 활성화됩니다. PluginView는 PDF viewer와 같은 embedded plugin을 나타냅니다. style 업데이트 결과 plugin element가 더 이상 렌더링되지 않는다고 판단되면 PluginView가 소멸되고, 이때 연결된 stream loader들이 DocumentLoader::removePlugInStreamLoader()를 통해 정리됩니다. checkLoadComplete()는 모든 subresource가 로딩을 완료했는지 확인하는 DocumentLoader 메서드로, event dispatch를 포함한 completion callback을 유발할 수 있습니다.
Analysis
패치 이전에는 removePlugInStreamLoader()가 checkLoadComplete()를 동기적으로 호출했습니다. 이 함수는 ScriptDisallowedScope가 활성화된 updateStyleIfNeeded() 도중에 도달할 수 있었습니다. style 업데이트가 PluginView의 소멸을 유발하면 updateStyleIfNeeded() → PluginView 소멸 → removePlugInStreamLoader() → checkLoadComplete() → dispatchPrintEvent 순서의 호출 체인이 형성됩니다. 결과적으로 ScriptDisallowedScope 내부에서 JavaScript event listener가 실행되는 상황이 발생했습니다. release 빌드에서는 ScriptDisallowedScope가 이 invariant를 강제하지 않으므로, style recalculation 중 DOM/style tree가 중간 상태에 있는 동안 JavaScript가 조용히 실행됩니다.
Aaa Aaaa Aaaaaaaaaaaa Aaaaaaa Aaa Aaa Aaaaaaa Aaaaaaa Aa a Aaaaaaaaaaaaaaaaa Aaaaaa Aaaaa Aaaaa Aaaaaaaa Aaa Aaaaaaaaaa Aaaaaa Aa Aaaaaaaaaaaa Aaaaaa a Aaaaaaaaaaa Aaaaaaaa Aaaa Aaaa Aa Aaaaaaaaaa Aaaaa Aaaaaaaaaaaaa Aaaa Aaaaaa Aa Aaaaaaaa Aaaaaaaaa Aa Aa Aaaa Aaaa Aaa a Aaaaa Aaaaaaaaa Aaaaaaaaaaaaaa Aaaaa Aa Aa Aaa Aaaa Aaaaaaaaaaaaaa Aa Aaaaa Aaaaaaa Aaaaaaaaaaa Aaa Aaa Aaaa Aaaaa Aa Aaaa Aaaaaa Aaaaaaaaaa Aaaaaaaaaa Aaaaa Aa Aaaaa Aaaaaaaaaa Aaaa Aaa Aaa Aaa Aaaaaa
a Aaaaaaaaaaaaaa Aaaaaaaaaaaaaaaaaaaaaaaa Aaaa Aaaaaaaaa Aaaa Aaa Aaa Aaaaaaa Aaaaaaa Aaaa Aaaaaaaaaaaaaaaaaaaaaaaa Aaaaaaaaa Aaaaa Aaaa Aaa Aaa Aa Aaaa Aaa Aaaaaaaa a Aaa Aaaaaa
Aaaaaaaaaaaaaaaaaaaaaaa Aaa Aaaaaaaa Aaaaa Aaaa Aaaaaa Aaaaaaa Aaa a Aaa Aaaa Aa Aa Aaaaaa Aaaaaaaaa Aaaaaaaaa Aaa Aa Aaaaa Aaaaaaaaa Aaaa Aaaaaa Aa Aaaa Aaa Aa Aa a Aaaaaaaaaa Aaaaaa Aaa Aaaaa Aaaa Aaaaa Aaaa Aa a a a Aaa Aa Aaaa Aaaaaaa
🔒Explores the re-entrancy implications and what an attacker could achieve with script execution during style recalculation
더 확인하려면 구독해 주세요
Audit directions
a Aaaaaaaaaaaaaaaaaaaaaaa Aaa Aa Aaaa Aaaaa Aaaaaaaaa Aaa a Aa Aaa Aaaaaaaaaa Aaaaaaaaaaa Aaaaaaaaaaaaaaaaaaaaaaaa Aaaaaaaaaaaaaaaa Aa Aaaaaaaaaaaaaaaaaaaaaa Aaa Aa Aa Aa Aaaa Aaaaaaaaaaa Aaaaaaa Aaa Aaa Aaaaaaaaaaaaaaaaaaaaaa Aaaaaaaaaaaaaaaaaa Aaaaaaaaaaaaaaaaaaaaaaa Aaaaa Aaaa Aaa Aaaa Aaaa Aaaaaaaaaaaaaaaaaaa Aa Aaa Aaaaa a Aaaa Aaaaaaaaaaaaaaaaaaaaa Aaaaaaaaa Aa Aaaa Aaaaaa Aaaaaaaaaaaaaaaaaaaaaaaaaa Aaa Aaa Aaa Aaa Aaa Aaaaa
a Aaaaaaa Aaaaaaaaaaaaa a Aa Aaa Aaaaaaa Aaaa Aaaa Aaaaaaa Aaaa Aaaaa Aaaaaaaaaaa Aaa Aaa Aaaa Aaaaaa Aaaaaaaaaaa Aaaaaaaaaa Aaaaaaaa Aaaa Aaaaaaaaaaa Aa Aaaaaaaaa Aa Aaa Aa a Aaaaa Aaaaaaaaaaaaaaaaaaaa Aaaaaaaaaaaaaaaaaaa Aaaaaaaaaaaaaaaaaaaa Aa Aaaa Aaaaa Aaaaaaaaa Aaaaa Aaaaaa Aaaaaaaaaaaa Aa a Aaaa Aaa Aaaaaaaaaaaa Aa Aaa Aaaaaaaaaaaaaaa Aaa Aaaaaa
a Aaaaaaaaa Aaaaa Aaaaaaaaaaaaaaaaaaaaa Aa Aa Aaaaa Aaa Aaa Aaaaaaaaa Aaaa Aa Aaaa Aaa Aaaaaa Aaaaaaa Aa Aaa Aaaa Aaa Aaa Aaaaa Aa Aa Aaaaaaaaaaaaaaaaaaaaa Aaaa Aaaaa Aaaaaaaaa Aaa Aaaa Aaa Aaa a Aaaaa Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa Aa Aaa Aaa Aa Aaaa Aaaaaa
🔒Multiple audit patterns identified for finding similar ScriptDisallowedScope violations across WebKit's object lifecycle and event dispatch paths
더 확인하려면 구독해 주세요