[15] ScrollerMac cross-thread UAF
High로 평가된 이유는 이 diff가 매번 재현 가능한 cross-thread UAF를 수정하기 때문입니다.
WebCore: Scrolling스레드에서 실행 중이던NSAnimationcallback이, main thread에서ScrollerPairMac이 해제한CheckedPtr<ScrollerMac>를 역참조하고 있었습니다.
ScrollerMac은 WebScrollbarPartAnimationMac과 WebScrollerImpDelegateMac에서 CheckedPtr로 참조되는 구조였습니다. 소유권도 없고 cross-thread 동기화도 없는 참조 방식이었습니다. main thread에서 ScrollerPairMac::~ScrollerPairMac이 실행된 이후, scrolling thread의 [setCurrentProgress:] callback이 이미 해제된 메모리를 역참조하게 됩니다.
Source/WebCore/page/scrolling/mac/ScrollerMac.h
Source/WebCore/page/scrolling/mac/ScrollerMac.mm
Patch Details
ScrollerMac은 ThreadSafeRefCountedAndCanMakeThreadSafeWeakPtr<ScrollerMac, DestructionThread::Main>으로 변경되었습니다. Delegate들은 ThreadSafeWeakPtr<ScrollerMac>을 보유하며, callback마다 local RefPtr로 승격합니다. ScrollerPairMac은 scroller를 Ref<ScrollerMac>으로 저장합니다. ~ScrollerMac()은 isMainThread()를 assert합니다. 한편 m_pair에 대한 null 검사가 lastKnownMousePositionInScrollbar, visibilityChanged, updateMinimumKnobLength에 추가되었습니다. 이는 scrolling thread의 RefPtr이 ScrollerPairMac보다 오래 유지될 수 있는 구간을 처리하기 위한 조치입니다.
객체의 lifetime이 다른 스레드에 귀속된 상태에서, callback 실행과 소멸 사이에 동기화 없이 non-owning cross-thread pointer를 사용한 패턴.
Background
ScrollerMac은 macOS WebCore에서 방향별 scrollbar 객체입니다. ScrollerPairMac당 수직과 수평 각각 하나씩 존재합니다. WebScrollbarPartAnimationMac은 NSAnimation의 서브클래스로, setCurrentProgress:는 AppKit의 display-link에 의해 WebCore: Scrolling 스레드에서 호출됩니다. CheckedPtr<T>은 CanMakeCheckedPtr과 쌍을 이루어, 역참조 시점에 대상 객체가 살아있는지 assert합니다. 다만 이는 debug 시 UAF 감지 용도일 뿐이며, cross-thread 안전성은 보장하지 않습니다. ThreadSafeWeakPtr::get()은 RefPtr<T>을 원자적으로 반환하며, DestructionThread::Main은 최종 delete가 main thread에서 실행되도록 라우팅합니다.
Analysis
CheckedPtr은 "안전한 raw pointer"처럼 보이지만, 안전성 보장은 동일 스레드 내 접근에만 적용됩니다. 스레드를 넘어서면 lifetime 연장도 동기화도 기대할 수 없습니다.
이 패치의 구조는 WebKit에서 이 유형의 문제에 대한 표준적인 수정 방식으로 자리잡고 있습니다. m_pair에 대한 null 검사를 함께 추가했다는 점은 부수적인 미묘함을 드러냅니다. scrolling thread의 callback이 잠시 RefPtr을 보유함으로써 ScrollerMac이 ScrollerPairMac보다 오래 살 수 있게 되면, 이후 back-pointer 멤버에 대한 모든 접근이 nullable해집니다.
이 vulnerability는 WebContent process 내부의 메모리 안전성을 약화시킵니다. ScrollerPairMac 소유 객체는 main thread에서 해제된 이후 scrolling thread에 의해 접근되지 않는다는 invariant가 전제였습니다. 그러나 실행 중인 NSAnimation callback이 destructor와 경쟁하는 상황에서는 이 전제가 깨졌습니다.
Audit directions
CheckedPtr<T>as a cross-thread back-reference from Objective-C delegates.Source/WebCore에서@interface블록 안에CheckedPtr<필드가 있는 경우를 검색합니다. 또한 AppKit이 non-main thread(display link,NSAnimation,NSURLSession)에서 실행할 수 있는 selector 안에서CheckedPtr역참조가 발생하는지도 확인합니다.UniqueRef<T>owning an object referenced from non-owner threads. 단일 소유권은 cross-thread callback 생존과 양립하지 않습니다.Source/WebCore/page/scrolling과Source/WebKit/UIProcess/ViewGestureController*를 점검합니다.- Back-pointers (
m_pair,m_parent,m_owner) inside classes that just acquired their own refcount. 스레드에서 호출 가능한 메서드에서 이러한 back-pointer를 역참조할 때마다, local 승격 이후 null 검사가 필요합니다. WTF::DestructionThread::Mainomission.Source/WebCore/page/scrolling과 Cocoa 브리징 레이어에서ThreadSafeRefCounted서브클래스 중 destructor가 AppKit/CoreAnimation을 건드리지만DestructionThread::Main을 지정하지 않은 경우를 검색합니다.