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
Tracker504072665
Fix commit67184bab7ef9 (chromium/src) +19/-18
CISA KEVNot listed
CreditedGoogle
Disclosed2026-06-02

Changed Functions

FunctionChangeNotes
if
ios/web_view/internal/cwv_web_view.mm
modified

Files Changed

  • ios/web_view/internal/cwv_web_view.mm
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
Loading diff…

Original Bug Report

reported by vm...@google.com

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.mm
  • ios/web_view/internal/cwv_back_forward_list.mm
  • ios/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.

  1. Setup: An attacker hosts a malicious page and the victim visits it via an app using ChromeWebView.framework.
  2. Popup Creation: The attacker’s script calls window.open(), prompting the embedder to create a new CWVWebView instance.
  3. UI Retention: The embedder application requests webView.backForwardList and retains it for its custom navigation UI.
  4. Trigger Closure: The attacker’s script calls window.close() on the popup. This triggers -[CWVUIDelegate webViewDidClose:].
  5. Deallocation: The embedder handles the delegate call by removing the popup from the view hierarchy and dropping its strong reference. The CWVWebView deallocates, destroying the NavigationManager while leaving the dangling pointer in the retained CWVBackForwardList.
  6. 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_view app extensions are built with use_partition_alloc = false, meaning BackupRefPtr mitigation is not active).
  7. Execution: The embedder app updates its UI and accesses the currentItem of 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.

View on issue tracker