← All issues

[3] WKRevealItemPresenter: re-entrancy UAF in showContextMenu

Severity: Medium | Component: WebKit UIProcess (macOS) | d089fe1

Rated Medium because the diff fixes a use-after-free in the UI process triggered by an IPC handler re-entering during a nested AppKit modal run loop and reassigning the sole-owner WebViewImpl::m_revealItemPresenter member. The UAF is reachable from WebContent via crafted data-detection click results; sandbox escape from this UAF requires a separate exploitation chain the diff does not establish.

A crash occurs in -[WKRevealItemPresenter showContextMenu] because the presenter is held by a single strong reference m_revealItemPresenter in WebViewImpl. During the modal popup, this reference may be cleared by a new data-detection click arriving via IPC, which overwrites m_revealItemPresenter with a newly allocated presenter, releasing the old one. The fix uses protect(m_revealItemPresenter) to retain the presenter for the duration of the showContextMenu call.

Source/WebKit/UIProcess/mac/WebViewImpl.mm

m_revealItemPresenter = adoptNS([[WKRevealItemPresenter alloc] initWithWebViewImpl:*this item:adoptNS([PAL::allocRVItemInstance() initWithDDResult:info.result.get()]).get() frame:info.elementBounds menuLocation:clickLocation]);
[m_revealItemPresenter setShouldUseDefaultHighlight:NO];
- [m_revealItemPresenter showContextMenu];
+ [protect(m_revealItemPresenter) showContextMenu];

A single-line change in WebKit::WebViewImpl::handleClickForDataDetectionResult. The direct call [m_revealItemPresenter showContextMenu] is replaced with [protect(m_revealItemPresenter) showContextMenu]. protect() is a WebKit idiom that materializes a local strong reference (a RetainPtr<> temporary) from a smart-pointer member; the temporary's lifetime extends through the full statement, so the presenter object is held alive for the entire duration of the modal showContextMenu invocation regardless of any subsequent reassignment to m_revealItemPresenter.

Sole-owner member pointer mutated by re-entrant IPC during a nested modal run loop, freeing the receiver of the in-flight method call.

WKRevealItemPresenter is an Objective-C UI helper that displays a context menu for a data-detection match (phone number, address, calendar event) using Apple's Reveal framework. WebViewImpl is the per-WKWebView C++ object on macOS that owns UI-process state, including a single strong reference m_revealItemPresenter held via an adoptNS-style smart pointer. handleClickForDataDetectionResult is invoked when the WebContent process sends back a data-detection click hit result via IPC.

showContextMenu displays an AppKit context menu, which is modal: AppKit spins a nested run loop until the user dismisses the menu. While that nested run loop is active, the UI-process main thread continues to dispatch incoming IPC messages — meaning new messages from the WebContent process can re-enter UI-process IPC handlers before the outer showContextMenu call has returned. protect(...) is a WebKit idiom that materializes a local strong reference from a smart-pointer member so the referenced object survives even if the member is mutated during the call.

The bug is a textbook use-after-free via re-entrant overwrite of a sole-owner strong reference during a nested modal run loop. Before the fix, WebViewImpl held the WKRevealItemPresenter solely via the strong member m_revealItemPresenter. The call sequence is the dangerous shape: m_revealItemPresenter = adoptNS([[WKRevealItemPresenter alloc] init...]) allocates the presenter and stores it via assignment; the next line invokes a modal Objective-C method on that member directly.

When -[WKRevealItemPresenter showContextMenu] runs, AppKit spins its nested run loop. While that loop is active, the UI process is still pumping IPC. A second data-detection click result from WebContent — reachable from any page that triggers another data-detection event before the menu is dismissed — re-enters handleClickForDataDetectionResult. That handler reassigns m_revealItemPresenter to a freshly adoptNS-allocated presenter. The smart-pointer assignment releases the previous strong reference — and since m_revealItemPresenter is the sole owner, the previous reference count drops to zero and the original presenter is deallocated. Control returns through the nested run loop into the still-executing -[WKRevealItemPresenter showContextMenu] frame, whose self now points at freed Objective-C memory: classic dangling self UAF.

  Outer frame (UI process main thread)
  handleClickForDataDetectionResult (call #1)
    m_revealItemPresenter = adoptNS(...)     // refcount 1
    [m_revealItemPresenter showContextMenu]  // begins modal run loop
      |
      |  while nested run loop spins, IPC dispatches...
      |
      +-- handleClickForDataDetectionResult (call #2, re-entered)
            m_revealItemPresenter = adoptNS(new presenter)  // releases #1, dealloc
            ...
      |
      v
    showContextMenu frame resumes on freed self  <-- UAF

An attacker model here is a compromised WebContent process able to send a crafted second data-detection click result while the first is showing its menu. The window is large by design — the modal run loop persists for as long as the user can see the context menu. The resulting UAF lives in the UI process, the privileged side of the WebContent sandbox boundary, which is exactly the kind of cross-boundary UAF most useful for sandbox escape primitives. The diff does not establish a concrete read/write primitive on the freed presenter; the practical primitive depends on what reuses the freed Objective-C slot during the gap and which methods are subsequently dispatched on the dangling self.

This vulnerability weakens memory safety inside the UI process by violating the implicit assumption that strong-reference members in WebViewImpl outlive any synchronous use on the stack. That assumption breaks whenever an IPC handler is re-entered through a nested modal run loop and reassigns the member.

Note: The exact re-entrancy mechanics — that IPC dispatches on the main thread while showContextMenu's nested run loop is active, that m_revealItemPresenter is genuinely the sole strong reference — are inferred from the commit message text and the shape of the fix rather than from the diff alone. The dangling-self mechanism is consistently supported by the patch.