[6] MediaPlayerPrivateMediaSourceAVFObjC re-entrant KVO during destruction
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_logger는 m_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
Patch Details
Destructor 맨 앞에 weakPtrFactory().revokeAll() 한 줄이 추가되었습니다. 이 호출은 해당 객체를 가리키는 모든 WeakPtr를 무효화합니다. 이후 m_mediaSourcePrivate 같은 member가 소멸되는 과정에서 KVO notification이 발생하더라도, WeakPtr를 역참조하여 부분적으로 소멸된 인스턴스로 돌아오는 경로가 차단됩니다.
Teardown 중 re-entrant callback이 도달할 수 있는 member를 소멸하기 전에 weak reference를 무효화하지 않은 패턴.
Background
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을 전달합니다. MediaPlayerPrivateMediaSourceAVFObjC는 MediaSourcePrivateAVFObjC 타입의 m_mediaSourcePrivate와 Logger 참조인 m_logger를 보유합니다. setNetworkState()는 network 상태 변화를 처리하는 MediaPlayer의 표준 hook입니다. 이 구현에서는 m_logger를 통해 상태 변화를 로그에 기록합니다.
Analysis
이 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으로 표시하고 있습니다.
Audit directions
- Member destructor가 동기적으로 callback을 발생시킬 수 있는
CanMakeWeakPtr파생 클래스 (KVO,NSNotificationCenter, AVFoundation/Core Media delegate, dispatch handler 등).Source/WebCore/platform/graphics/avfoundation/objc/와Source/WebCore/platform/mediastream/가 대상 디렉토리입니다. 이 경로에서CanMakeWeakPtr를 상속하면서 KVO로 관찰되거나 delegate로 등록된 Objective-C member를 보유하는 클래스를 모두 점검합니다. Destructor의 첫 줄이weakPtrFactory().revokeAll()이 아니면 취약 후보로 분류합니다..mm파일에서class .* : .*CanMakeWeakPtr패턴과addObserver:/removeObserver:사용을 교차 검색하는 것부터 시작합니다. - 인프라 필드(
m_logger, 식별자, factory 등)가 callback 가능 객체의 소유자보다 앞에 선언된 member 선언 순서. 이 배치로 인해 callback 소유자가 아직 소멸 중인 동안 인프라가 먼저 소멸됩니다. Media/graphics player 클래스의 member 선언 순서를 검토하고,setNetworkState,setReadyState등m_logger또는 앞서 선언된 필드에 접근하는 state-callback 진입 지점과 대조합니다. - r301011 regression. 해당 destructor와 주변 KVO observer 등록 지점에
git log -p및git blame을 적용합니다. r301011에서 새로운 KVO 구독이 추가되었는지, 소멸 순서가 변경되었는지, 또는 기존revokeAll()호출이 제거되었는지 확인합니다. - KVO 외 소멸 중 동기 callback re-entrancy.
Timer::stop(),dispatchToRendererQueueflush,NativePromiserejection 경로에서도 re-entrancy가 발생할 수 있습니다. Teardown 근처에서cancelPendingSeek,m_seekTimer.stop(), 또는dispatch_sync를 호출하는 destructor를 점검합니다. Weak reference가 해당 호출 이후가 아닌 이전에 revoke되는지 확인합니다.