← All issues

[15] ScrollerMac cross-thread UAF

Severity: High | Component: WebCore macOS scrolling | a926a67

Rated High because the diff fixes a deterministic cross-thread UAF: in-flight NSAnimation callbacks on the WebCore: Scrolling thread dereferenced a CheckedPtr<ScrollerMac> after the owning ScrollerPairMac had freed it on the main thread.

ScrollerMac was held in WebScrollbarPartAnimationMac and WebScrollerImpDelegateMac via CheckedPtr — non-owning, no cross-thread synchronization. Once ScrollerPairMac::~ScrollerPairMac ran on the main thread, the scrolling thread's [setCurrentProgress:] callback dereferenced freed memory.

Source/WebCore/page/scrolling/mac/ScrollerMac.h

-class ScrollerMac final : public CanMakeThreadSafeCheckedPtr<ScrollerMac> {
+class ScrollerMac final : public ThreadSafeRefCountedAndCanMakeThreadSafeWeakPtr<ScrollerMac, WTF::DestructionThread::Main> {

Source/WebCore/page/scrolling/mac/ScrollerMac.mm

@interface WebScrollbarPartAnimationMac : NSAnimation {
- CheckedPtr<WebCore::ScrollerMac> _scroller;
+ ThreadSafeWeakPtr<WebCore::ScrollerMac> _scroller;
- (void)setCurrentProgress:(NSAnimationProgress)progress
{
[super setCurrentProgress:progress];
+ RefPtr scroller = _scroller.get();
+ if (!scroller)
+ return;

ScrollerMac becomes ThreadSafeRefCountedAndCanMakeThreadSafeWeakPtr<ScrollerMac, DestructionThread::Main>. Delegates hold ThreadSafeWeakPtr<ScrollerMac> and promote to local RefPtr per callback. ScrollerPairMac stores its scrollers as Ref<ScrollerMac>. ~ScrollerMac() asserts isMainThread(). Null guards on m_pair cover lastKnownMousePositionInScrollbar, visibilityChanged, updateMinimumKnobLength for the window where a scrolling-thread RefPtr outlives ScrollerPairMac.

Non-owning cross-thread pointer to an object whose lifetime is bound to a different thread, with no synchronization between callback dispatch and destruction.

ScrollerMac is the per-orientation scrollbar object in macOS WebCore; one vertical and one horizontal per ScrollerPairMac. WebScrollbarPartAnimationMac is an NSAnimation subclass whose setCurrentProgress: is invoked by AppKit's display-link from the WebCore: Scrolling thread. CheckedPtr<T> pairs with CanMakeCheckedPtr to assert at dereference that the pointee is still alive — debug-time UAF detection only, no cross-thread safety. ThreadSafeWeakPtr::get() returns a RefPtr<T> atomically. DestructionThread::Main routes the final delete to the main thread.

CheckedPtr looks like "safe raw pointer" but its safety guarantee covers only same-thread access. Across threads it provides neither lifetime extension nor synchronization.

🔒

Detailed cross-thread lifetime analysis covering the race window and reclamation feasibility for this scrolling-thread crash

Subscribe to read more

🔒

Four reusable audit patterns identified for cross-thread lifetime bugs in Cocoa-bridging code, with concrete starting points across WebCore scrolling and platform layers

Subscribe to read more