CVE-2026-7361
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifios/web/web_state/web_state_impl_realized_web_state.mm |
modified |
Files Changed
ios/web/web_state/web_state_impl_realized_web_state.mm
Patch
From 2ed116751ff0f0b4f8d67f3298ceae16143a492b Mon Sep 17 00:00:00 2001 From: Mike Dougherty <[email protected]> Date: Fri, 17 Apr 2026 14:07:12 -0700 Subject: [PATCH] Break from navigation observer loops if WebState is deallocated Fixed: 493221953 Change-Id: I02ecc528453f8d7895f5f9bddb39d05d04ecf748 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7766683 Reviewed-by: Andrew Liu <[email protected]> Auto-Submit: Mike Dougherty <[email protected]> Commit-Queue: Andrew Liu <[email protected]> Cr-Commit-Position: refs/heads/main@{#1616847} --- diff --git a/ios/web/web_state/web_state_impl_realized_web_state.mm b/ios/web/web_state/web_state_impl_realized_web_state.mm index c56ff4c..2a1de50 100644 --- a/ios/web/web_state/web_state_impl_realized_web_state.mm +++ b/ios/web/web_state/web_state_impl_realized_web_state.mm @@ -296,7 +296,14 @@ void WebStateImpl::RealizedWebState::OnNavigationRedirected( NavigationContextImpl* context) { + base::WeakPtr<NavigationContextImpl> weak_context = context->GetWeakPtr(); for (auto& observer : observers()) { + // Observers might cancel this navigation, destroying the context. Guard + // against that by checking if the context is still alive. + if (!weak_context && base::FeatureList::IsEnabled( + features::kDetectDestroyedNavigationContexts)) { + break; + } observer.DidRedirectNavigation(owner_, context); } } @@ -309,12 +316,20 @@ return; } + const bool same_document = context->IsSameDocument(); + base::WeakPtr<NavigationContextImpl> weak_context = context->GetWeakPtr(); for (auto& observer : observers()) { + // Observers might cancel this navigation, destroying the context. Guard + // against that by checking if the context is still alive. + if (!weak_context && base::FeatureList::IsEnabled( + features::kDetectDestroyedNavigationContexts)) { + break; + } observer.DidFinishNavigation(owner_, context); } // Update cached_favicon_urls_. - if (!context->IsSameDocument()) { + if (!same_document) { // Favicons are not valid after document change. Favicon URLs will be // refetched by CRWWebController and passed to OnFaviconUrlUpdated. cached_favicon_urls_.clear();
Original Bug Report
Potential Use-After-Free in RealizedWebState via synchronous NavigationContext destruction
Flapjack (go/flapjack), an LLM-powered static analysis tool, has identified the following potential security issue.
Overview: A potential Use-After-Free (UAF) exists in WebStateImpl::RealizedWebState when notifying observers of navigation events. If an observer synchronously destroys the NavigationContextImpl (e.g., by disabling web usage), subsequent observers and method accesses use a dangling raw pointer. While OnNavigationStarted mitigates this with a base::WeakPtr check, OnNavigationRedirected and OnNavigationFinished lack this protection, potentially leading to Remote Code Execution (RCE).
Affected files:
ios/web/web_state/web_state_impl_realized_web_state.mm
Estimated timestamp from git blame: 2025-01-23
Summary
A potential Use-After-Free (UAF) vulnerability has been identified in ios/web/web_state/web_state_impl_realized_web_state.mm. When notifying WebStateObservers of navigation events (DidRedirectNavigation, DidFinishNavigation), the code passes a raw pointer to a NavigationContextImpl object. If an observer synchronously triggers an action that causes this context to be destroyed, subsequent access to the pointer results in a UAF.
Technical Details
In WebStateImpl::RealizedWebState, the following methods iterate over observers using a raw NavigationContextImpl* pointer:
-
OnNavigationStarted(NavigationContextImpl* context)(line 272): This method contains a mitigation usingbase::WeakPtr(line 284), gated behind thefeatures::kDetectDestroyedNavigationContextsfeature flag (line 288). If the context is destroyed during the loop, the loop breaks. -
OnNavigationRedirected(NavigationContextImpl* context)(line 296): This method iterates through observers and callsDidRedirectNavigationwithout any checks for the validity of the context between calls. If an observer causes the navigation to be canceled or the web view to be removed, the next observer receives a dangling pointer. -
OnNavigationFinished(NavigationContextImpl* context)(line 303): Similar to the above, it callsDidFinishNavigationwithout aWeakPtrcheck. Crucially, it also accessescontext->IsSameDocument()on line 316 after the observer notification loop has completed.IsSameDocument()is a virtual method. If any observer caused the context’s destruction during the loop, this virtual method call on a freed object makes RCE a viable threat.
Potential Trigger Mechanism
NavigationContextImpl objects for pending navigations (including those in the REDIRECTED state or downloads) are owned by CRWWKNavigationStates via a std::unique_ptr. A synchronous destruction of these objects can be triggered by calling WebState::SetWebUsageEnabled(false), which eventually invokes CRWWebController removeWebView.
Here is a potential step-by-step sequence an attacker might use to trigger this vulnerability:
- Initiation: An attacker-controlled web page initiates a navigation (e.g., triggering a server-side redirect or a download).
- State Update:
CRWWKNavigationHandlerupdates the state of the navigation inCRWWKNavigationStatestoREDIRECTEDor leaves it pending. - Observer Notification: The handler invokes
self.webStateImpl->OnNavigationRedirected(context)orself.webStateImpl->OnNavigationFinished(context), passing the raw pointer toWebStateImpl. - Looping Observers: Execution enters
WebStateImpl::RealizedWebStateand begins iterating overWebStateObserverobjects. - Synchronous Destruction: During this iteration, an observer synchronously triggers an action that disables web usage for the
WebState(e.g., viaWebUsageEnablerBrowserAgent::SetWebUsageEnabled(false)). This calls[web_controller_ setWebUsageEnabled:NO], which calls[self removeWebView]. - Pending Navigations Cleared: Inside
removeWebView, the code explicitly iterates over all pending navigations and calls[self.navigationHandler.navigationStates removeNavigation:navigation]. - Memory Freed:
[CRWWKNavigationStates removeNavigation:]removes the navigation record and releases itsstd::unique_ptr<web::NavigationContextImpl>. The context is destroyed and freed. - Use-After-Free: Execution returns to the observer loop in
WebStateImpl::RealizedWebState. Thecontextparameter is now a dangling pointer. - Subsequent Access: The loop continues to the next observer, passing the dangling pointer. Furthermore, if this occurred in
OnNavigationFinished, the code evaluatescontext->IsSameDocument()on the freed object. BecauseIsSameDocument()is virtual, this dereferences a hijacked vtable pointer.
Note: The LLM agent does not have the ability to run code or build a working PoC, so these steps are suggested/potential based on static analysis.
Impact
As ios/web runs within the main browser process on iOS, this Use-After-Free vulnerability allows for memory corruption. Because it involves a virtual method call (IsSameDocument()) on a synchronously freed object, an attacker could potentially overwrite the memory via heap spraying and hijack the control flow, leading to full Remote Code Execution (RCE) and a sandbox escape.
Evaluated with Chrome root at commit: 43c4f3945742db6f06efbaf9a77b90f34a720277
Results from Flapjack so far have been promising, but it can be wrong in its deductions. At this time, it does not produce proof of concepts or fuzzer tests. If this proves to be a false positive, please close as WAI; data from false positives will be used to improve Flapjack’s accuracy over time. And please feel free to reach out to me directly if you have concerns or feedback on the project.