5d23a56841c2cf72d4d5e20d7a7824d93517c814 Use-after-free in -[WebCLLocationManager locationManagerDidChangeAuthorization:]
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.
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
- Setup — A web page requests geolocation; the UIProcess creates a CoreLocationGeolocationProvider with a WebCLLocationManager delegate pointing back at the WebGeolocationManagerProxy Client.
- Trigger — Core Location fires a delegate callback (authorization change / position / error) on the delegate.
- 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.
- Free — The delegate (self) and the Client are deallocated while the Objective-C callback is still on the stack.
- 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
- Reach — Web geolocation API on an Apple platform; requires the geolocation permission flow but no memory-corruption precondition.
- Trigger — Induce a Core Location delegate callback whose Client handling destroys the provider re-entrantly (authorization transition or position/error event).
- 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).
- 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.
- 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 —