CVE-2026-10885
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifios/web_view/internal/cwv_web_view.mm |
modified |
Files Changed
ios/web_view/internal/cwv_web_view.mm
Patch
From 67184bab7ef9a8e301189d8a9dcd78d82393f27c Mon Sep 17 00:00:00 2001
From: Mike Dougherty <michaeldo@chromium.org>
Date: Wed, 22 Apr 2026 17:33:44 -0700
Subject: [PATCH] Ensure CWVWebView cleanup is consistent
`shutdown` may not be called before `dealloc`. In these cases, ensure
that `_backForwardList.navigationManager` is set to nil.
Additionally, move the dealloc and shutdown methods to be close to each
other and move shared logic to a helper method to make it more clear
that there are two destrution paths.
Fixed: 504072665
Change-Id: I352c3f75de196f7136046bcc74c908ea42d95b94
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7783652
Reviewed-by: Rohit Rao <rohitrao@chromium.org>
Commit-Queue: Mike Dougherty <michaeldo@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1619215}
---
diff --git a/ios/web_view/internal/cwv_web_view.mm b/ios/web_view/internal/cwv_web_view.mm
index 29b4ba1..8377e03 100644
--- a/ios/web_view/internal/cwv_web_view.mm
+++ b/ios/web_view/internal/cwv_web_view.mm
@@ -511,16 +511,6 @@
_webState->GetWebViewProxy().allowsLinkPreview = allowsLinkPreview;
}
-- (void)dealloc {
- if (_webState) {
- if (_webStateObserver) {
- _webState->RemoveObserver(_webStateObserver.get());
- _webStateObserver.reset();
- }
- WebViewHolder::RemoveFromWebState(_webState.get());
- }
-}
-
- (void)goBack {
if (![self isWebStateSafeToUse]) {
return;
@@ -1253,20 +1243,31 @@
- (void)shutDown {
if (_webState) {
- // CWVBackForwardList is unsafe to use after shutting down.
- _backForwardList.navigationManager = nil;
-
// To handle the case where -[CWVWebView encodeRestorableStateWithCoder:] is
// called after this method, precompute the session storage so it may be
// used during encoding later.
[_serializationHelper updateStateFromWebState:_webState.get()];
- if (_webStateObserver) {
- _webState->RemoveObserver(_webStateObserver.get());
- _webStateObserver.reset();
- }
- WebViewHolder::RemoveFromWebState(_webState.get());
+
+ [self cleanupWebStateReference];
_webState.reset();
}
}
+- (void)dealloc {
+ if (_webState) {
+ [self cleanupWebStateReference];
+ }
+}
+
+- (void)cleanupWebStateReference {
+ // CWVBackForwardList is unsafe to use after shutting down.
+ _backForwardList.navigationManager = nil;
+
+ if (_webStateObserver) {
+ _webState->RemoveObserver(_webStateObserver.get());
+ _webStateObserver.reset();
+ }
+ WebViewHolder::RemoveFromWebState(_webState.get());
+}
+
@end
Original Bug Report
Potential Use-After-Free in CWVBackForwardList via dangling NavigationManager
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 without the Chrome Security team. Please see go/chrome-ai-generated-security-bugs-faq for more information.
Overview: A Use-After-Free vulnerability exists in ios/web_view where CWVBackForwardList retains a dangling raw C++ pointer to a destroyed web::NavigationManager. This occurs when an embedding application releases a CWVWebView without calling the internal shutDown method, leaving the pointer un-nulled during standard deallocation. Accessing the list thereafter results in virtual calls on freed memory, potentially leading to Remote Code Execution.
Affected files:
ios/web_view/internal/cwv_web_view.mmios/web_view/internal/cwv_back_forward_list.mmios/web_view/internal/cwv_back_forward_list_internal.h
Estimated timestamp from git blame: 2018-10-24
Background
In ChromeWebView.framework (ios/web_view), the CWVBackForwardList Objective-C object provides the host application with access to the navigation history of a CWVWebView. Internally, it relies on a raw C++ pointer to fetch data, declared in ios/web_view/internal/cwv_back_forward_list_internal.h:
@property(nonatomic, nullable) const web::NavigationManager* navigationManager;
Vulnerability Details
The web::NavigationManager memory is owned by the web::WebState instance managed by the parent CWVWebView. A Use-After-Free condition arises because the standard deallocation path of CWVWebView does not clear the navigationManager pointer stored in its associated CWVBackForwardList.
While the internal -[CWVWebView shutDown] method correctly nullifies this pointer (_backForwardList.navigationManager = nil;), standard ARC deallocation via -[CWVWebView dealloc] does not. It is a documented expectation that dealloc can be called without shutDown occurring first (see comments in ios/web_view/internal/autofill/web_view_autofill_client_ios.h).
When a CWVWebView is deallocated, its std::unique_ptr<web::WebState> is destroyed, which in turn destroys the NavigationManagerImpl. If the embedding iOS application has retained a strong reference to the CWVBackForwardList object to power its history UI, the navigationManager property becomes a dangling pointer to freed memory.
Subsequent access to the list (e.g., -[CWVBackForwardList currentItem]) bypasses the nil check because the pointer is dangling, and executes a virtual C++ call: self.navigationManager->GetLastCommittedItem().
Potential Exploitation Steps
Note: These are theoretical steps based on code analysis; our tooling agent does not run exploit code.
- Setup: An attacker hosts a malicious page and the victim visits it via an app using
ChromeWebView.framework. - Popup Creation: The attacker’s script calls
window.open(), prompting the embedder to create a newCWVWebViewinstance. - UI Retention: The embedder application requests
webView.backForwardListand retains it for its custom navigation UI. - Trigger Closure: The attacker’s script calls
window.close()on the popup. This triggers-[CWVUIDelegate webViewDidClose:]. - Deallocation: The embedder handles the delegate call by removing the popup from the view hierarchy and dropping its strong reference. The
CWVWebViewdeallocates, destroying theNavigationManagerwhile leaving the dangling pointer in the retainedCWVBackForwardList. - Heap Spray: The attacker’s surviving opener page runs a JavaScript loop to allocate large objects, reclaiming the freed memory slot previously held by the
NavigationManagerImpl. (Note:ios/web_viewapp extensions are built withuse_partition_alloc = false, meaning BackupRefPtr mitigation is not active). - Execution: The embedder app updates its UI and accesses the
currentItemof the retained back-forward list. The virtual call uses the attacker-controlled vtable, leading to Remote Code Execution (RCE) in the browser process.
Suggested Fix
The -[CWVWebView dealloc] method should be updated to explicitly nullify the navigationManager pointer on the _backForwardList, mirroring the logic found in the shutDown method.
// In ios/web_view/internal/cwv_web_view.mm
- (void)dealloc {
_backForwardList.navigationManager = nil;
if (_webState) {
if (_webStateObserver) {
_webState->RemoveObserver(_webStateObserver.get());
_webStateObserver.reset();
}
WebViewHolder::RemoveFromWebState(_webState.get());
}
}
Evaluated with Chrome root at commit: 7353d249d9cacf9c7218e1d7b8a39cf39c72d646
Results so far have been promising, but there can be wrong deductions. If this proves to be a false positive, please close as WAI; 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.