Overview

Critical
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in Chrome for iOS
DescriptionUse after free in Chrome for iOS
ComponentChrome for iOS
Bug ClassUAF
Tracker513514692
Fix commit509fc948ac91 (chromium/src) +29/-7
CISA KEVNot listed
CreditedGoogle
Disclosed2026-06-02

Changed Functions

FunctionChangeNotes
if
ios/web/navigation/crw_web_view_navigation_observer.mm
modified
if
ios/web/navigation/crw_wk_navigation_handler.mm
modified
if
ios/web/web_state/ui/crw_web_controller.mm
modified

Files Changed

  • ios/web/navigation/crw_web_view_navigation_observer.mm
  • ios/web/navigation/crw_wk_navigation_handler.mm
  • ios/web/web_state/ui/crw_web_controller.mm
From 509fc948ac91f6bafb13e3419d48551b7b07363f Mon Sep 17 00:00:00 2001
From: Mike Dougherty <[email protected]>
Date: Fri, 15 May 2026 13:08:15 -0700
Subject: [PATCH] Ensure NavigationContext is not accessed after being freed

WebState observer methods could allow for a caller to close the WebState
which would destroy all NavigationContexts. Verify NavigationContexts
are still valid after observer calls before further use.

Fixed: 513514692
Change-Id: I0eab9559803668562f2e32d661a1cd9fb9de03ce
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7853140
Reviewed-by: Ginny Huang <[email protected]>
Commit-Queue: Mike Dougherty <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1631497}
---

diff --git a/ios/web/navigation/crw_web_view_navigation_observer.mm b/ios/web/navigation/crw_web_view_navigation_observer.mm
index b9130e5..df3319b1 100644
--- a/ios/web/navigation/crw_web_view_navigation_observer.mm
+++ b/ios/web/navigation/crw_web_view_navigation_observer.mm
@@ -6,6 +6,7 @@
 
 #import "base/check.h"
 #import "base/logging.h"
+#import "base/memory/weak_ptr.h"
 #import "base/metrics/histogram_functions.h"
 #import "base/sequence_checker.h"
 #import "base/strings/sys_string_conversions.h"
@@ -237,10 +238,14 @@
       }
       existingContext->SetIsSameDocument(isSameDocumentNavigation);
       existingContext->SetHasCommitted(!isSameDocumentNavigation);
+      base::WeakPtr<web::NavigationContextImpl> weakContext =
+          existingContext->GetWeakPtr();
       self.webStateImpl->OnNavigationStarted(existingContext);
-      [self.delegate navigationObserver:self
-               didChangePageWithContext:existingContext];
-      self.webStateImpl->OnNavigationFinished(existingContext);
+      if (weakContext) {
+        [self.delegate navigationObserver:self
+                 didChangePageWithContext:weakContext.get()];
+        self.webStateImpl->OnNavigationFinished(weakContext.get());
+      }
     }
   }
 
diff --git a/ios/web/navigation/crw_wk_navigation_handler.mm b/ios/web/navigation/crw_wk_navigation_handler.mm
index 7320446..c1d2620 100644
--- a/ios/web/navigation/crw_wk_navigation_handler.mm
+++ b/ios/web/navigation/crw_wk_navigation_handler.mm
@@ -9,6 +9,7 @@
 #import "base/apple/foundation_util.h"
 #import "base/feature_list.h"
 #import "base/ios/ns_error_util.h"
+#import "base/memory/weak_ptr.h"
 #import "base/metrics/histogram_functions.h"
 #import "base/metrics/histogram_macros.h"
 #import "base/metrics/user_metrics.h"
@@ -945,15 +946,17 @@
   [self.navigationStates setState:web::WKNavigationState::COMMITTED
                     forNavigation:navigation];
 
+  base::WeakPtr<web::NavigationContextImpl> weakContext =
+      context ? context->GetWeakPtr() : nullptr;
   if (!committedNavigation && context && !context->IsLoadingErrorPage()) {
     self.webStateImpl->OnNavigationFinished(context);
   }
 
   // The actual navigation item will not be committed until the native content
   // or WebUI is shown.
-  if (context && !context->GetUrl().SchemeIs(url::kAboutScheme)) {
+  if (weakContext && !weakContext->GetUrl().SchemeIs(url::kAboutScheme)) {
     [self.delegate webViewHandlerUpdateSSLStatusForCurrentNavigationItem:self];
-    if (!context->IsLoadingErrorPage()) {
+    if (!weakContext->IsLoadingErrorPage()) {
       [self setLastCommittedNavigationItemTitle:webView.title];
     }
   }
@@ -1951,8 +1954,12 @@
         // WKWebView will revert the url to about:blank. Simply discard pending
         // item and fail the navigation.
         navigationContext->ReleaseItem();
+        base::WeakPtr<web::NavigationContextImpl> weakContext =
+            navigationContext->GetWeakPtr();
         self.webStateImpl->OnNavigationFinished(navigationContext);
-        self.webStateImpl->OnPageLoaded(navigationContext->GetUrl(), false);
+        if (weakContext) {
+          self.webStateImpl->OnPageLoaded(weakContext->GetUrl(), false);
+        }
         return;
       }
     }
@@ -2264,13 +2271,17 @@
         // `OnNavigationFinished` callback.
         navContext->SetUrl(failingURL);
         navContext->SetHasCommitted(true);
+        base::WeakPtr<web::NavigationContextImpl> weakContext =
+            navContext->GetWeakPtr();
         self.webStateImpl->OnNavigationFinished(navContext);
 
         // For SSL cert error pages, SSLStatus needs to be set manually because
         // the placeholder navigation for the error page is committed and
         // there is no server trust (since there's no network navigation), which
         // is required to create a cert in CRWSSLStatusUpdater.
-        if (web::IsWKWebViewSSLCertError(navContext->GetError()) && info.cert) {
+        if (weakContext &&
+            web::IsWKWebViewSSLCertError(weakContext->GetError()) &&
+            info.cert) {
           web::SSLStatus& SSLStatus =
               self.navigationManagerImpl->GetLastCommittedItem()->GetSSL();
           SSLStatus.cert_status = info.cert_status;
diff --git a/ios/web/web_state/ui/crw_web_controller.mm b/ios/web/web_state/ui/crw_web_controller.mm
index 55c37bf..13067f0 100644
--- a/ios/web/web_state/ui/crw_web_controller.mm
+++ b/ios/web/web_state/ui/crw_web_controller.mm
@@ -15,6 +15,7 @@
 #import "base/ios/block_types.h"
 #import "base/ios/ios_util.h"
 #import "base/json/string_escape.h"
+#import "base/memory/weak_ptr.h"
 #import "base/metrics/histogram_functions.h"
 #import "base/metrics/user_metrics.h"
 #import "base/metrics/user_metrics_action.h"
@@ -1823,7 +1824,12 @@
           contextForPendingMainFrameNavigationWithURL:newURL];
     }
     navigationContext->SetIsSameDocument(true);
+    base::WeakPtr<web::NavigationContextImpl> weakContext =
+        navigationContext->GetWeakPtr();
     self.webStateImpl->OnNavigationStarted(navigationContext);
+    if (!weakContext) {
+      return;
+    }
     [self didStartLoading];
     self.navigationManagerImpl->CommitPendingItem(
         navigationContext->ReleaseItem());
Loading diff…

Original Bug Report

reported by [email protected]

Potential Use-After-Free of NavigationContextImpl in iOS Chrome Browser Process

Project Fortify, an experimental security project, has identified the following potential security issue. If you’re a feature owner CC-ed on this bug, please do your best to review these reports. Please see https://chromium.googlesource.com/chromium/src/+/main/docs/security/ai-generated-security-bugs-faq.md for more information.

Overview: A potential Use-After-Free (UAF) vulnerability exists in iOS Chrome when a NavigationContextImpl is synchronously destroyed during observer notifications. While internal loops are protected, the calling functions continue to use dangling raw pointers to the deallocated context. This issue impacts the unsandboxed browser process and is not mitigated by MiraclePtr on the iOS platform.

Affected files:

  • ios/web/web_state/ui/crw_web_controller.mm
  • ios/web/web_state/web_state_impl_realized_web_state.mm
  • ios/web/navigation/crw_web_view_navigation_observer.mm
  • ios/web/navigation/crw_wk_navigation_handler.mm
  • ios/web/web_state/ui/crw_web_request_controller.mm

Estimated timestamp from git blame: 2019-03-02

Description

A potential Use-After-Free (UAF) vulnerability has been identified in the iOS implementation of Chromium. The issue arises from the synchronous destruction of web::NavigationContextImpl objects during WebState observer notifications (e.g., DidStartNavigation, DidFinishNavigation).

Chromium previously introduced a mitigation feature, kDetectDestroyedNavigationContexts, which uses a base::WeakPtr to guard the internal observer fan-out loops in WebStateImpl::RealizedWebState. However, this mitigation only protects the loop itself. When control returns to the calling function (typically in CRWWebViewNavigationObserver or CRWWebController), the caller proceeds to dereference its own raw pointer to the NavigationContextImpl, which may have been freed during the observer notification.

Root Cause Analysis

In ios/web/web_state/web_state_impl_realized_web_state.mm, methods like OnNavigationStarted check the context’s validity within the loop:

base::WeakPtr<NavigationContextImpl> weak_context = context->GetWeakPtr();
for (auto& observer : observers()) {
  if (!weak_context && base::FeatureList::IsEnabled(
                           features::kDetectDestroyedNavigationContexts)) {
    break;
  }
  observer.DidStartNavigation(owner_, context);
}

If an observer synchronously destroys the WebState or cancels the navigation, the NavigationContextImpl (often owned by CRWWKNavigationStates) is deleted. The loop breaks correctly, but the caller in CRWWebViewNavigationObserver.mm or CRWWebController.mm continues execution using the dangling context pointer.

Impact and Exploitability

This is a high-impact vulnerability because it occurs in the unsandboxed browser process on iOS. Furthermore, MiraclePtr (BackupRefPtr) is not enabled on iOS due to platform-specific address space limitations, meaning these stack-based bare pointers are unprotected.

A particularly dangerous primitive exists in CRWWebViewNavigationObserver::webViewLoadingStateDidChange and CRWWebController::URLDidChangeWithoutDocumentChange::

// CRWWebViewNavigationObserver.mm
self.navigationManagerImpl->CommitPendingItem(
    existingContext->ReleaseItem());

If existingContext is freed and its memory reclaimed by an attacker, the ReleaseItem() call (which moves a std::unique_ptr<NavigationItemImpl>) can be manipulated to return a pointer to an attacker-controlled memory region. Subsequent operations on this NavigationItemImpl by the NavigationManager can lead to arbitrary code execution (RCE).

Suggested Potential Steps to Reproduce

  1. Host a malicious page that performs history manipulation (e.g., history.go(-1)) or fragment changes (location.hash = '#...') to trigger WKWebView KVO notifications.
  2. Implement a WebStateObserver that, upon receiving DidStartNavigation, synchronously destroys the WebState (e.g., by closing the tab or removing it from the WebStateList).
  3. The browser process will reach the KVO handler in CRWWebViewNavigationObserver or CRWWebController, call OnNavigationStarted, and then attempt to dereference the deallocated context pointer (e.g., calling ReleaseItem() or SetHasCommitted()).
  4. Reclaim the freed memory during the observer callback to control the values read during the UAF.

Proposed Fix

Callers that trigger observer notifications for a NavigationContextImpl must use base::WeakPtr<NavigationContextImpl> to track its lifetime and check for its existence before any subsequent dereference.

For example, in CRWWebViewNavigationObserver.mm:

base::WeakPtr<web::NavigationContextImpl> weakContext = existingContext->GetWeakPtr();
self.webStateImpl->OnNavigationStarted(existingContext);
if (!weakContext) {
  return;
}
// Safe to use weakContext.get() or existingContext here.

Evaluated with Chrome root at commit: b3153093eb3c78c3e88ccf562bcbc20437a04b0e


Results so far have been promising, but there can be wrong deductions. Feel free to adjust as follows:

  • If you are familiar with the severity guidelines, you may adjust the severity.
  • If this is a false positive, and there’s no work to be done, please close as WAI.
  • If there is work to do here but not a vulnerability, please change the issue type to Task/Bug/FR.

Data from false positives will be used to improve 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