← All issues

[11] AudioContext destructor touches Document during Document's own destruction

Severity: Medium | Component: WebCore Web Audio | ed04ff4

~Document 내부의 BaseAudioContext::deleteMarkedNodes를 통해 도달하는 ~AudioContext가 절반쯤 파괴된 Document에 쓰기를 수행하는 UAF-during-destruction을 수정하는 패치이므로 Medium으로 평가됩니다. 해제된 슬롯 내용에 대한 attacker의 영향력은 간접적입니다. Document storage가 해제된 뒤 그 자리를 차지하는 allocation에 따라 제한됩니다.

Document destructor가 호출되는 시점에, 현재 파괴 중인 Document를 참조하는 code path로 진입할 가능성이 있습니다. 해당 경로는 다음과 같습니다: Document::~DocumentScriptExecutionContext::~ScriptExecutionContextBaseAudioContext::deleteMarkedNodesAudioContext::~AudioContextDocument::removeAudioProducer. 패치는 audio producer 제거를 AudioContext::stop()에서 처리하도록 변경하고, ~AudioContext!isStopped() 조건으로 보호합니다.

Source/WebCore/Modules/webaudio/AudioContext.cpp

AudioContext::~AudioContext()
{
m_mediaSession->invalidateClient();
 
- if (RefPtr document = this->document())
- document->removeAudioProducer(*this);
+ if (!isStopped()) {
+ if (RefPtr document = this->document())
+ document->removeAudioProducer(*this);
+ }
}
...
+void AudioContext::stop()
+{
+ if (RefPtr document = this->document())
+ document->removeAudioProducer(*this);
+ BaseAudioContext::stop();
+}

Source/WebCore/Modules/webaudio/AudioContext.h

// ActiveDOMObject
+ void stop() final;
void suspend(ReasonForSuspension) final;

AudioContext teardown을 재구성하는 변경은 세 가지입니다. 먼저 ~AudioContextdocument->removeAudioProducer(*this) 호출을 if (!isStopped()) 조건으로 감쌉니다. 다음으로 새로운 override인 AudioContext::stop()BaseAudioContext::stop()에 위임하기 전에 document->removeAudioProducer(*this)를 먼저 호출합니다. 마지막으로 BaseAudioContext::stop()의 접근 제한이 private에서 public으로 상향되어, 파생 클래스에서 직접 이를 호출할 수 있게 됩니다. stop()의 실행 시점은 Document::commonTeardownScriptExecutionContext::stopActiveDOMObjects 흐름 내, Document destructor 시작 이전입니다. 수동 ASAN reproducer도 추가되었습니다.

소유 객체 자신의 teardown에 의한 부수 효과로 실행되는 child destructor에서, 절반쯤 파괴된 소유 객체로의 re-entrant 호출.

AudioContextBaseAudioContext를 상속하며, BaseAudioContextThreadSafeRefCounted이면서 ActiveDOMObject(per-Document lifecycle interface)를 상속합니다. ActiveDOMObjectstop() hook을 제공하는데, Document::commonTeardown~Document 실행 이전에 ScriptExecutionContext::stopActiveDOMObjects를 통해 이를 호출합니다. BaseAudioContext::deleteMarkedNodes는 삭제 예정으로 표시된 audio node의 reference를 해제하는 deferred-deletion sweep입니다. 해당 reference가 마지막 참조였을 경우, 소유 AudioContext의 소멸이 deleteMarkedNodes 내부에서 연쇄적으로 발생합니다. Document::addAudioProducer/removeAudioProducerDocument 위에서 MediaProducer* 집합을 관리하며, 현재 audio를 생성 중인 context를 추적합니다. BaseAudioContext::isStopped()가 true를 반환하는 시점은 stop()m_isStopScheduled를 설정한 이후입니다.

commit message에 기록된 crash chain의 흐름은 다음과 같습니다. Document::~Document가 실행되면 ~ScriptExecutionContext가 연쇄적으로 실행됩니다. 이 base destructor로 인해 BaseAudioContext::deleteMarkedNodes가 삭제 지연된 AudioContext의 마지막 reference를 해제합니다. 그 결과 ~AudioContext가 호출되고, 내부에서 document->removeAudioProducer(*this)가 실행됩니다. 이 시점에 Document subobject는 파괴 진행 중인 상태입니다. Document에 속하지만 ScriptExecutionContext에는 아직 속하지 않은 필드들이 이미 선언 역순으로 파괴된 뒤입니다. removeAudioProducer는 이 절반쯤 파괴된 Document 객체의 상태를 변경합니다.

🔒

The teardown ordering and re-entrant destructor chain behind this UAF are traced end-to-end, with an assessment of how much attacker control is realistic during the destruction window.

더 확인하려면 구독해 주세요

🔒

Four reusable audit patterns identified around ActiveDOMObject lifetimes and deferred-deletion queues, with concrete subsystem starting points for variant discovery.

더 확인하려면 구독해 주세요