Overview

Critical
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in iOS
DescriptionUse after free in iOS
ComponentChromium
Bug ClassUAF
Tracker493221953
Fix commit2ed116751ff0 (chromium/src) +16/-1
CISA KEVNot listed
CreditedGoogle
Disclosed2026-04-28

Changed Functions

FunctionChangeNotes
if
ios/web/web_state/web_state_impl_realized_web_state.mm
modified

Files Changed

  • ios/web/web_state/web_state_impl_realized_web_state.mm
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();
Loading diff…

Original Bug Report

reported by [email protected]

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:

  1. OnNavigationStarted(NavigationContextImpl* context) (line 272): This method contains a mitigation using base::WeakPtr (line 284), gated behind the features::kDetectDestroyedNavigationContexts feature flag (line 288). If the context is destroyed during the loop, the loop breaks.

  2. OnNavigationRedirected(NavigationContextImpl* context) (line 296): This method iterates through observers and calls DidRedirectNavigation without 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.

  3. OnNavigationFinished(NavigationContextImpl* context) (line 303): Similar to the above, it calls DidFinishNavigation without a WeakPtr check. Crucially, it also accesses context->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:

  1. Initiation: An attacker-controlled web page initiates a navigation (e.g., triggering a server-side redirect or a download).
  2. State Update: CRWWKNavigationHandler updates the state of the navigation in CRWWKNavigationStates to REDIRECTED or leaves it pending.
  3. Observer Notification: The handler invokes self.webStateImpl->OnNavigationRedirected(context) or self.webStateImpl->OnNavigationFinished(context), passing the raw pointer to WebStateImpl.
  4. Looping Observers: Execution enters WebStateImpl::RealizedWebState and begins iterating over WebStateObserver objects.
  5. Synchronous Destruction: During this iteration, an observer synchronously triggers an action that disables web usage for the WebState (e.g., via WebUsageEnablerBrowserAgent::SetWebUsageEnabled(false)). This calls [web_controller_ setWebUsageEnabled:NO], which calls [self removeWebView].
  6. Pending Navigations Cleared: Inside removeWebView, the code explicitly iterates over all pending navigations and calls [self.navigationHandler.navigationStates removeNavigation:navigation].
  7. Memory Freed: [CRWWKNavigationStates removeNavigation:] removes the navigation record and releases its std::unique_ptr<web::NavigationContextImpl>. The context is destroyed and freed.
  8. Use-After-Free: Execution returns to the observer loop in WebStateImpl::RealizedWebState. The context parameter is now a dangling pointer.
  9. Subsequent Access: The loop continues to the next observer, passing the dangling pointer. Furthermore, if this occurred in OnNavigationFinished, the code evaluates context->IsSameDocument() on the freed object. Because IsSameDocument() 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.

View on issue tracker