CVE-2026-10896
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifios/web/navigation/crw_web_view_navigation_observer.mm |
modified | |
ifios/web/navigation/crw_wk_navigation_handler.mm |
modified | |
ifios/web/web_state/ui/crw_web_controller.mm |
modified |
Files Changed
ios/web/navigation/crw_web_view_navigation_observer.mmios/web/navigation/crw_wk_navigation_handler.mmios/web/web_state/ui/crw_web_controller.mm
Patch
From 509fc948ac91f6bafb13e3419d48551b7b07363f Mon Sep 17 00:00:00 2001
From: Mike Dougherty <michaeldo@chromium.org>
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 <ginnyhuang@chromium.org>
Commit-Queue: Mike Dougherty <michaeldo@chromium.org>
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());
Original Bug Report
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.mmios/web/web_state/web_state_impl_realized_web_state.mmios/web/navigation/crw_web_view_navigation_observer.mmios/web/navigation/crw_wk_navigation_handler.mmios/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
- Host a malicious page that performs history manipulation (e.g.,
history.go(-1)) or fragment changes (location.hash = '#...') to triggerWKWebViewKVO notifications. - Implement a
WebStateObserverthat, upon receivingDidStartNavigation, synchronously destroys theWebState(e.g., by closing the tab or removing it from theWebStateList). - The browser process will reach the KVO handler in
CRWWebViewNavigationObserverorCRWWebController, callOnNavigationStarted, and then attempt to dereference the deallocated context pointer (e.g., callingReleaseItem()orSetHasCommitted()). - 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.