← All issues

[6] MediaPlayerPrivateMediaSourceAVFObjC re-entrant KVO during destruction

Severity: Medium | Component: WebCore platform media | 4193d2c

Severity가 Medium으로 평가된 이유는, KVO re-entrancy를 통해 이미 소멸된 m_logger에 접근하는 MediaPlayerPrivateMediaSourceAVFObjC의 teardown 중 UAF를 수정하는 fix이기 때문입니다. Attacker가 행사할 수 있는 영향력은 teardown 형태를 조작하는 수준에 그치며, 즉각적인 결과는 제어 가능한 read/write가 아닌 renderer crash입니다.

~MediaPlayerPrivateMediaSourceAVFObjC()의 맨 앞에서 모든 weak pointer를 revoke하여, member 소멸 도중 발생하는 re-entrant callback을 차단합니다. C++은 선언 역순으로 member를 소멸하므로, m_loggerm_mediaSourcePrivate보다 먼저 소멸됩니다. m_mediaSourcePrivate의 destructor는 동기적으로 KVO notification을 발생시킬 수 있으며, 이 notification은 부분적으로 소멸된 객체에서 WeakPtr로 보호된 callback을 호출합니다. CanMakeWeakPtr base 클래스의 destructor는 모든 derived class member destructor가 완료된 후에야 실행됩니다. 이 시점까지 WeakPtr::get()은 여전히 null이 아닌 값을 반환하여, callback이 이미 소멸된 member에 접근하는 상황이 발생합니다.

Source/WebCore/platform/graphics/avfoundation/objc/MediaPlayerPrivateMediaSourceAVFObjC.mm

MediaPlayerPrivateMediaSourceAVFObjC::~MediaPlayerPrivateMediaSourceAVFObjC()
{
ALWAYS_LOG(LOGIDENTIFIER);
 
+ // member 소멸 중 re-entrant callback을 방지합니다. 이 코드가 없으면
+ // m_mediaSourcePrivate 소멸 중 발생한 KVO notification이
+ // WeakPtr로 보호된 callback을 통해 부분적으로 소멸된 객체를
+ // 재진입합니다(예: error callback이 setNetworkState()를 호출하고,
+ // 이미 소멸된 m_logger에 접근하는 경우).
+ weakPtrFactory().revokeAll();
+
cancelPendingSeek();
m_seekTimer.stop();
dispatchToRendererQueue([](auto& renderer) {
renderer.pause();
});
}

Destructor 맨 앞에 weakPtrFactory().revokeAll() 한 줄이 추가되었습니다. 이 호출은 해당 객체를 가리키는 모든 WeakPtr를 무효화합니다. 이후 m_mediaSourcePrivate 같은 member가 소멸되는 과정에서 KVO notification이 발생하더라도, WeakPtr를 역참조하여 부분적으로 소멸된 인스턴스로 돌아오는 경로가 차단됩니다.

Teardown 중 re-entrant callback이 도달할 수 있는 member를 소멸하기 전에 weak reference를 무효화하지 않은 패턴.

WebKit의 WeakPtr는 non-owning reference로, CanMakeWeakPtr CRTP base를 통해 대상 객체에 저장된 WeakPtrFactory가 backing storage 역할을 담당합니다. Factory가 revoke되거나 소멸된 이후에만 WeakPtr::get()은 null을 반환합니다. C++에서 derived class의 member는 선언 역순으로 소멸되며, 이후 base class destructor가 실행됩니다. Factory를 revoke하는 CanMakeWeakPtr의 destructor는 base destructor에 해당하므로, 모든 derived class member destructor가 완료된 후에야 실행됩니다. KVO(Key-Value Observing)는 Cocoa의 observer pattern으로, AVFoundation 객체는 등록된 observer에게 동기적으로 KVO notification을 전달합니다. MediaPlayerPrivateMediaSourceAVFObjCMediaSourcePrivateAVFObjC 타입의 m_mediaSourcePrivate와 Logger 참조인 m_logger를 보유합니다. setNetworkState()는 network 상태 변화를 처리하는 MediaPlayer의 표준 hook입니다. 이 구현에서는 m_logger를 통해 상태 변화를 로그에 기록합니다.

이 destructor는 CanMakeWeakPtr의 기본 teardown 순서에 의존하고 있었습니다. WeakPtrFactory는 base destructor가 실행될 때만 revoke되는데, 이 시점은 모든 derived class member가 이미 소멸된 이후입니다. Member 소멸 과정, 특히 m_mediaSourcePrivate의 destructor가 실행되는 동안 AVFoundation은 동기적으로 KVO notification을 발생시킬 수 있습니다. 이 notification들은 WeakPtr로 보호된 callback을 통해 전달됩니다. 이 시점에는 factory가 아직 revoke되지 않은 상태이므로, WeakPtr::get()은 여전히 null이 아닌 pointer를 반환합니다.

Callback이 실행되면 setNetworkState() 같은 메서드가 호출됩니다. 이 메서드는 선언 역순 소멸 규칙에 따라 이미 소멸된 m_logger에 접근하는 코드를 포함합니다. 결과적으로 부분적으로 소멸된 객체에 대한 use-after-destruction이 발생하며, 소멸 중 setNetworkState 내에서 crash로 나타납니다. 이 fix는 destructor 맨 앞에서 모든 WeakPtr를 revoke하여 이 window를 사전에 차단합니다. 이후 KVO 경유 callback은 null WeakPtr를 마주치게 되므로, 소멸된 상태에 접근하기 전에 실행이 종료됩니다.

이 vulnerability는 media element teardown 중 WebContent process의 memory safety를 약화시킵니다. 핵심 문제는 invariant 위반에 있습니다. 객체가 소멸되기 시작하면 WeakPtr 사용자는 null pointer를 받아야 하지만, member destructor가 callback을 발생시킬 수 있는 객체에서는 CanMakeWeakPtr base의 revoke 시점이 너무 늦습니다. CanMakeWeakPtr를 상속하는 클래스 중 member destructor가 동기적으로 callback을 호출할 수 있는 경우가 있습니다. KVO, dispatch sources, NSNotificationCenter observer, AVFoundation delegate, Core Media listener 등을 통한 경로가 그에 해당합니다. 이런 클래스는 모두 같은 teardown 시점 re-entrancy에 취약합니다. 이번에 확립된 방어 패턴, 즉 destructor 첫 줄에서 weakPtrFactory().revokeAll()을 호출하는 방식은, AVFoundation 또는 Cocoa observer를 보유하는 모든 클래스의 기본 관행으로 삼는 것이 바람직합니다. 이 commit은 해당 문제를 r301011 이후 발생한 regression으로 표시하고 있습니다.