Medium CVSS 7.3 webkit UAF 🔧 Commit mapped

Overview

Medium
Severity
7.3
CVSS
No
Exploited ITW
Fixed
Fix Status
DescriptionProcessing maliciously crafted web content may lead to memory corruption
ComponentWebCore Page
Bug ClassUAF
Tracker289653
Fix commita23df0dfbec0 (WebKit/WebKit) +43/-6
CWECWE-119 (Buffer bounds error)
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:L
CISA KEVNot listed
Creditedwac working with Trend Micro Zero Day Initiative
Disclosed2025-05-12

Background

ScrollerMac
The WebCore C++ object representing a single macOS scrollbar (one orientation), owning the platform NSScrollerImp and its Objective-C delegate.
CheckedPtr / CanMakeCheckedPtr
A WebKit smart-pointer scheme where the pointee counts outstanding CheckedPtrs and aborts (rather than silently continuing) if it is destroyed while references remain or if a dangling pointer is used, turning UAFs into detectable crashes.
NSScrollerImpDelegate / NSAnimation retention
AppKit objects: an NSScrollerImp retains its delegate and running NSAnimations retain themselves, so these can outlive the WebCore object that created them if not explicitly invalidated.
adoptNS
A WebKit RAII helper that takes ownership of a +1-retained Objective-C object into a RetainPtr, tying its release to C++ scope.
invalidate (delegate teardown)
A method that severs a delegate’s back-references and stops its animations so lingering AppKit callbacks no longer touch the (soon-to-be-freed) C++ owner.

Root Cause Analysis

On macOS, scrollbar appearance animations are driven by AppKit through two Objective-C helper classes, WebScrollbarPartAnimationMac (an NSAnimation subclass) and WebScrollerImpDelegateMac (an NSScrollerImpDelegate), both of which held a raw C++ back-pointer ‘WebCore::ScrollerMac* _scroller’. These Objective-C objects have lifetimes controlled by AppKit and by pending animations: an NSScrollerImp retains its delegate, and running animations retain their animation objects, so they can outlive the C++ ScrollerMac that owns them. ScrollerMac::attach() re-creates the delegate: it did ’m_scrollerImpDelegate = adoptNS([[WebScrollerImpDelegateMac alloc] initWithScroller:this])’ and installed it on a fresh NSScrollerImp. If attach() ran while a previous delegate (and/or its part animations) was still alive and still referencing the old ScrollerMac, or if the ScrollerMac was subsequently destroyed while an AppKit-retained delegate/animation lingered, later NSScrollerImpDelegate/NSAnimation callbacks would dereference the stale raw _scroller pointer — a use-after-free. The bundled LayoutTest reaches this by churning scrollbar/scroller state during layout and focus/pointer-lock/form activity (‘scrollIntoViewIfNeeded’, ‘requestPointerLock’, form ‘submit’, and an appendMedium() style mutation), which can tear down and re-attach scrollers while animations/delegates are outstanding.

The fix has two parts. First, ScrollerMac is made ‘final’ and derives from CanMakeCheckedPtr<ScrollerMac> with WTF_MAKE_FAST_ALLOCATED and WTF_OVERRIDE_DELETE_FOR_CHECKED_PTR(ScrollerMac), and the two Objective-C classes’ raw ‘ScrollerMac* _scroller’ members become ‘CheckedPtrWebCore::ScrollerMac _scroller’ (with call sites updated to pass _scroller.get()). CheckedPtr participates in ScrollerMac’s checked-pointer accounting so that a dangling access is detected/aborted rather than silently reading freed memory, and the overridden delete enforces that no CheckedPtrs outlive the object. Second, ScrollerMac::attach() now calls ‘[m_scrollerImpDelegate invalidate]’ before replacing the delegate, so the outgoing delegate is explicitly torn down (its reference to the scroller cleared and its animations stopped/detached) instead of being left alive holding a pointer to a scroller that is being re-attached. Together these restore the invariant that no AppKit-retained delegate or animation ever dereferences a ScrollerMac after that scroller has stopped owning it. The exact body of WebScrollerImpDelegateMac’s invalidate method and the animation callbacks are not shown in the diff, so the precise clearing steps invalidate performs are inferred from the surrounding pattern.

Key insight
A C++ object (ScrollerMac) was referenced by AppKit-owned Objective-C helpers (delegate and animations) whose lifetimes it did not control, via raw back-pointers; re-attaching the scroller without invalidating the old delegate left those helpers dangling. The fix pairs lifetime-checked CheckedPtr back-pointers with an explicit invalidate() on delegate replacement.

Attack Path

  1. Create scrollable content with custom scrollbars Load a page that produces overlay/animated scrollbars on macOS (the test uses root margin and rtl direction to force scrollbar geometry), instantiating ScrollerMac objects with WebScrollerImpDelegateMac delegates and part animations.
  2. Provoke scroller re-attach Trigger operations that rebuild scroller state while animations/delegates are in flight — the test uses scrollIntoViewIfNeeded(), requestPointerLock() with an error handler, a form submit(), and a stylesheet media appendMedium() mutation — so ScrollerMac::attach() runs (pre-patch, without invalidating the old delegate).
  3. Outlive the C++ owner Because AppKit’s NSScrollerImp retains the delegate and running NSAnimations retain themselves, a delegate/animation persists after its ScrollerMac is freed, leaving a stale raw _scroller pointer.
  4. Fire a stale callback A subsequent AppKit animation tick or NSScrollerImpDelegate callback dereferences the freed ScrollerMac (e.g. calling _scroller->scrollerImp()), reading and acting on freed heap memory — the use-after-free.
  5. (Background, separate) escalate the UAF Standard exploitation would groom the heap to reclaim the freed ScrollerMac allocation with attacker-controlled data before the stale callback fires, turning the dangling dereference into a controlled read/write toward code execution; this is generic technique, not something the commit itself demonstrates.

Impact Assessment

The bug is a use-after-free of a heap-allocated ScrollerMac reachable from AppKit-retained scrollbar delegates/animations, giving a dangling-object dereference that fires from asynchronous animation/delegate callbacks — a strong UAF primitive because the reclamation window (a running or pending animation) is attacker-influenceable. It lives in the WebContent process’s rendering/scrolling code and is therefore confined to the WebContent sandbox, matching the ‘memory corruption’ description; realistic escalation is heap-grooming the freed slot to gain a controlled read/write toward code execution within that process, though the checked-pointer conversion means post-patch such accesses abort as controlled crashes rather than corrupting memory. On its own it does not cross the sandbox boundary.

Changed Functions

FunctionChangeNotes
ScrollerMac (class declaration)
Source/WebCore/page/scrolling/mac/ScrollerMac.h
modified Made 'final', now inherits CanMakeCheckedPtr<ScrollerMac>, and adds WTF_MAKE_FAST_ALLOCATED and WTF_OVERRIDE_DELETE_FOR_CHECKED_PTR(ScrollerMac) so the object supports checked-pointer lifetime accounting and detects dangling references.
WebScrollbarPartAnimationMac (ivar _scroller)
Source/WebCore/page/scrolling/mac/ScrollerMac.mm
modified Changed the raw 'WebCore::ScrollerMac* _scroller' instance variable to 'CheckedPtr<WebCore::ScrollerMac> _scroller' so an animation callback can no longer silently use a freed scroller.
WebScrollerImpDelegateMac (ivar _scroller)
Source/WebCore/page/scrolling/mac/ScrollerMac.mm
modified Changed the raw 'WebCore::ScrollerMac* _scroller' instance variable to 'CheckedPtr<WebCore::ScrollerMac> _scroller'; call sites that construct WebScrollbarPartAnimationMac now pass _scroller.get().
ScrollerMac::attach
Source/WebCore/page/scrolling/mac/ScrollerMac.mm
modified Added '[m_scrollerImpDelegate invalidate]' before allocating and installing a new WebScrollerImpDelegateMac, so the previous delegate (and its animations) is torn down and no longer references the scroller being re-attached.

Files Changed

  • LayoutTests/fast/scrolling/mac/scrollbars/scrollbar-crash-expected.txt
  • LayoutTests/fast/scrolling/mac/scrollbars/scrollbar-crash.html
  • Source/WebCore/page/scrolling/mac/ScrollerMac.h
  • Source/WebCore/page/scrolling/mac/ScrollerMac.mm

Audit Directions

  • Other raw back-pointers in ScrollerMac.mm
    Verify every _scroller / m_pair / owner reference in ScrollerMac.mm and ScrollerPairMac is now a CheckedPtr/WeakPtr and that all replace-and-reinstall sites (like attach()) invalidate the prior delegate before reassigning m_scrollerImpDelegate / m_scrollerImp.
  • Objective-C delegates holding raw C++ pointers
    Grep the mac scrolling/rendering code for Objective-C @interface ivars typed as bare ‘WebCore::Something*’ (especially NSAnimation, NSScrollerImpDelegate, CALayer delegate, and NSObject subclasses stored via adoptNS) and check each has a matching invalidate-on-teardown and a checked/weak pointer.
  • adoptNS reassignment without invalidate
    Search for ’m_…Delegate = adoptNS(’ or similar delegate/animation reassignments that are not preceded by an [oldObject invalidate] or setDelegate:nil, since a replaced-but-still-retained AppKit object with a back-pointer is the exact pattern fixed here.
  • CanMakeCheckedPtr adoption gaps
    Look for WebCore classes referenced from platform Objective-C callback objects that do NOT yet derive from CanMakeCheckedPtr/CanMakeWeakPtr (no WTF_OVERRIDE_DELETE_FOR_CHECKED_PTR), as these remain candidates for the same cross-language lifetime UAF.

Original Bug Report

The reporter's bug is still restricted on the tracker.