← WebKit — 2026-W35

5d23a56841c2cf72d4d5e20d7a7824d93517c814  Use-after-free in -[WebCLLocationManager locationManagerDidChangeAuthorization:]

severity high class UAF confidence 0.78 signal score 12.67 Source/WebCore/platform patch-now AI review CONFIRMED
Primitive: use-after-free of CoreLocationGeolocationProvider::Client when the owner is destroyed re-entrantly inside a CLLocationManager delegate callback
Triage note: WebCLLocationManager's raw Client* _client becomes a WeakPtr, Client gains ref-counting, and each delegate callback (geolocationAuthorizationGranted/positionChanged/errorOccurred) now takes RetainPtr protectedSelf + RefPtr client and re-checks _client after calls that can run -stop and free the owner; comments state clients may destroy it re-entrantly. Reachable via web geolocation permission flow.
Structural signals: smartptr_member_added (+6.47), null_guard (+1.2), msg_strong (+3.0), msg_medium (+2.0)

The bug at a glance

A use-after-free in the macOS/iOS Core Location geolocation provider (Source/WebCore/platform/cocoa/CoreLocationGeolocationProvider.mm) in the UIProcess, reachable through the web geolocation permission flow. The Objective-C delegate WebCLLocationManager held a raw CoreLocationGeolocationProvider::Client* and, inside CLLocationManager delegate callbacks, invoked client methods that can re-entrantly run -stop and destroy the delegate’s owner — freeing both the Client and the delegate (self) while a callback was still on the stack. The diff clearly proves a lifetime defect (raw Client* dangling across a re-entrant destroy, and self being the owner’s only strong reference); high severity is warranted for a UIProcess UAF driven by web-reachable location events, though escalation to code execution is (inferred).

A delegate that its owner solely owns, calling back into that owner: the callout can delete the owner, which deletes the delegate mid-method — a re-entrant self-destruction hiding behind an Objective-C delegate pattern.

Root cause

Two overlapping lifetime invariants were violated. First, WebCLLocationManager stored its owner’s callback sink as a raw pointer: CoreLocationGeolocationProvider::Client* _client. Second, the owner (CoreLocationGeolocationProvider, ultimately WebGeolocationManagerProxy) held the ONLY strong reference to the WebCLLocationManager delegate, and clients can destroy that owner re-entrantly from inside a callback.

The pre-patch sequence for -locationManagerDidChangeAuthorization is: 1. CLLocationManager fires the delegate callback on the delegate (self); 2. the code reads the raw _client and calls _client->geolocationAuthorizationGranted(_websiteIdentifier); 3. that client method can call back into the provider’s -stop and tear down the provider, which releases its only reference to the delegate, so self is deallocated AND the Client object is freed; 4. control returns into the still-executing Objective-C method, which then evaluates _mode and may call [_locationManager startUpdatingLocation] on a freed self, and in other callbacks continues to use the dangling _client (positionChanged/errorOccurred/resetGeolocation). Because _client was a raw pointer and self had no local retain, both the C++ Client and the ObjC delegate were used after free.

The fix makes Client ref-countable and weak-trackable (class Client : public AbstractRefCountedAndCanMakeWeakPtr), changes the stored pointer to WeakPtr<…::Client> _client, and in every delegate callback (a) pins self with RetainPtr protectedSelf = self; so the delegate survives its own method even if the owner is destroyed, and (b) materializes RefPtr client = _client and null-checks it before each use, so a destroyed owner yields a null WeakPtr and the call is skipped. -stop now proactively clears _client = nullptr and detaches the delegate ([_locationManager setDelegate:nil]); after a callout, if (_client ...) re-checks whether -stop ran before calling startUpdatingLocation. WebGeolocationManagerProxy adds USING_CAN_MAKE_WEAKPTR and ref()/deref() overrides so it can be the ref-counted/weak Client. Bugclass matches triage (UAF).

Key code

Delegate pins self and uses a WeakPtr->RefPtr client, re-checking after each re-entrant callout

-    WebCore::CoreLocationGeolocationProvider::Client* _client;
+    WeakPtr<WebCore::CoreLocationGeolocationProvider::Client> _client;
...
+    // Our owner holds the only reference to us, and clients may destroy it re-entrantly below.
+    RetainPtr protectedSelf = self;
...
-            _client->geolocationAuthorizationGranted(_websiteIdentifier);
+            if (RefPtr client = _client)
+                client->geolocationAuthorizationGranted(_websiteIdentifier);
+            // A null _client means -stop ran: our owner was destroyed by the call above.
+            if (_client && _mode != WebCore::CoreLocationGeolocationProvider::Mode::AuthorizationOnly)
+                [_locationManager startUpdatingLocation];

Fix approach

The patch closes the window with two holders and one invalidation. Client becomes AbstractRefCountedAndCanMakeWeakPtr so it can be weakly referenced and strongly pinned; _client becomes a WeakPtr that automatically reads null once the owner is destroyed. Each delegate callback takes RetainPtr protectedSelf = self to keep the Objective-C delegate alive across the re-entrant call (its owner is its only strong ref), and takes a scoped RefPtr client = _client guarded by a null check so a destroyed owner simply skips the call instead of dereferencing freed memory. -stop now eagerly nulls _client and sets the CLLocationManager delegate to nil, so no further callbacks reach a dead owner; and after a callout that could have destroyed the owner, the code re-reads _client (if (_client && …)) before continuing (e.g. startUpdatingLocation). WebGeolocationManagerProxy gains USING_CAN_MAKE_WEAKPTR and ref()/deref() so it can serve as that weak/ref-counted Client. Semantically, delegate callbacks become no-ops once the provider has stopped, rather than operating on a half-destroyed object.

Patch walkthrough

  • Source/WebCore/platform/cocoa/CoreLocationGeolocationProvider.h — Includes wtf/AbstractRefCountedAndCanMakeWeakPtr.h and makes class Client derive from AbstractRefCountedAndCanMakeWeakPtr, so Clients are ref-counted and weak-referenceable.
  • Source/WebCore/platform/cocoa/CoreLocationGeolocationProvider.mm — Changes _client from a raw Client* to WeakPtr; -stop now clears _client and detaches the delegate; every delegate callback adds RetainPtr protectedSelf and promotes _client to a null-checked RefPtr before use, re-checking _client after re-entrant callouts (e.g. before startUpdatingLocation). AuthorizationChecker gains ref()/deref() finals.
  • Source/WebKit/UIProcess/WebGeolocationManagerProxy.h — Adds USING_CAN_MAKE_WEAKPTR(IPC::MessageReceiver) and keeps ref()/deref() finals so the proxy can act as the ref-counted/weak CoreLocationGeolocationProvider::Client without lifetime ambiguity.

Background

CLLocationManagerDelegate callbacks — Core Location invokes delegate methods (didChangeAuthorization, didUpdateLocations, didFailWithError) on WebCLLocationManager. These are the re-entrancy points: the delegate calls into WebKit’s Client, which can synchronously tear everything down.

CoreLocationGeolocationProvider::Client — The WebKit sink for geolocation events (geolocationAuthorizationGranted/Denied, positionChanged, errorOccurred, resetGeolocation), implemented by WebGeolocationManagerProxy in the UIProcess. Its methods can cause the provider — and thus the delegate — to be destroyed.

WeakPtr vs raw pointer vs RetainPtr — A raw pointer gives no lifetime safety; a WeakPtr reads null after its target dies (requires CanMakeWeakPtr); RetainPtr is an ObjC ownership smart pointer. The fix uses WeakPtr for the C++ Client and RetainPtr(self) to keep the ObjC delegate alive across the callout.

Re-entrant self-destruction — When an object’s sole owner is reachable from a callback the object makes, the callback can delete the owner and hence the object while the object’s method is still executing; the fix is to locally retain self and re-validate owner references after each callout.

AbstractRefCountedAndCanMakeWeakPtr — A WTF base combining ref-counting with weak-pointer support, letting holders both pin an object (RefPtr) and observe its death (WeakPtr nulling) — exactly what the delegate needs to safely call back into its owner.

Vulnerability window

  1. Setup — A web page requests geolocation; the UIProcess creates a CoreLocationGeolocationProvider with a WebCLLocationManager delegate pointing back at the WebGeolocationManagerProxy Client.
  2. Trigger — Core Location fires a delegate callback (authorization change / position / error) on the delegate.
  3. Re-entry — The callback calls a Client method (e.g. geolocationAuthorizationGranted) whose handling runs -stop and destroys the provider, releasing the delegate’s only strong reference.
  4. Free — The delegate (self) and the Client are deallocated while the Objective-C callback is still on the stack.
  5. Use-after-free — The method resumes and touches the freed self (startUpdatingLocation) or the dangling _client, a UAF in the UIProcess.

Proof of concept

No regression test ships (conceptual; UIProcess path, web trigger inferred). Conceptual trigger on macOS/iOS: a page calls navigator.geolocation.getCurrentPosition/watchPosition to drive the Core Location permission/update flow so a CLLocationManager delegate callback fires; the embedding client’s geolocation handling tears down the provider (-stop / proxy destruction) synchronously from within that callback (e.g. on authorization grant or a position update). Pre-patch, the delegate then operates on a freed self/_client. Reliable reproduction depends on client teardown timing, which is not shown in the diff.

Exploitation

  1. Reach — Web geolocation API on an Apple platform; requires the geolocation permission flow but no memory-corruption precondition.
  2. Trigger — Induce a Core Location delegate callback whose Client handling destroys the provider re-entrantly (authorization transition or position/error event).
  3. Primary primitive — UAF of the ObjC delegate (self) and the C++ Client in the UIProcess; dereference/virtual call on freed memory (startUpdatingLocation on freed self, or dangling _client call).
  4. Secondary — UIProcess is the privileged process; a controlled reclaim of the freed delegate/Client could redirect an ObjC message send or a C++ virtual dispatch. The re-entrant window is single-shot per callback but the flow can be re-driven.
  5. Escalation (open) — Gating questions: can the small UIProcess window be reclaimed with attacker-controlled bytes before the post-callout use, and can the freed Client’s vtable / the delegate’s isa be steered? Not shown by the diff (inferred); absent that, this is a UIProcess UAF crash.

Detection & hunting

For defenders and detection engineers:

  • Provider destroyed inside a CL delegate callback —
  • ASan/zombie UAF in CoreLocation path —
  • Delegate callback after -stop —

Remaining risk in the patch

  • Per-callout re-check discipline — Safety after the fix relies on each callback both pinning self and re-reading _client after every re-entrant callout; a future callback that adds a post-callout use without the if (_client) re-check (as startUpdatingLocation now has) would reintroduce the window.
  • protectedSelf keeps self alive but owner may still be gone — RetainPtr protectedSelf prevents the ObjC object from being freed mid-method, but the owner/Client can still be destroyed; correctness depends on the WeakPtr nulling and the null checks, so any raw use of _locationManager state tied to the dead owner must be reasoned about individually.

Audit directions

  • Other owner-only ObjC delegates —
  • Raw C++ sink pointers in ObjC bridges —
  • Client callback re-entrancy —
  • UIProcess permission flows —

Before / after

Loading diff…