Overview

High
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
Tracker508281950
Fix commit28dd0bd6a62c (chromium/src) +62/-1
CISA KEVNot listed
CreditedGoogle
Disclosed2026-06-02

Changed Functions

FunctionChangeNotes
if
ios/chrome/browser/shared/ui/util/uiview_window_coordinates_unittest.mm
modified
UIViewWindowCoordinatesTest
ios/chrome/browser/shared/ui/util/uiview_window_coordinates_unittest.mm
modified
TEST_F
ios/chrome/browser/shared/ui/util/uiview_window_coordinates_unittest.mm
modified
for
ios/chrome/browser/shared/ui/util/uiview_window_coordinates_unittest.mm
modified

Files Changed

  • ios/chrome/browser/shared/ui/util/UIView+WindowCoordinates.swift
  • ios/chrome/browser/shared/ui/util/uiview_window_coordinates_unittest.mm
From 28dd0bd6a62c9bd95cc5530cd590a44d45587f01 Mon Sep 17 00:00:00 2001
From: Matt Reichhoff <[email protected]>
Date: Mon, 04 May 2026 07:41:35 -0700
Subject: [PATCH] [iOS] Switch to retain nonatomic in UIView util

Bug: 508281950
Change-Id: I2a23b0370af0d51defff1d9b41c415b32e453fd2
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7810082
Commit-Queue: Matt Reichhoff <[email protected]>
Reviewed-by: Mark Cogan <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1624657}
---

diff --git a/ios/chrome/browser/shared/ui/util/UIView+WindowCoordinates.swift b/ios/chrome/browser/shared/ui/util/UIView+WindowCoordinates.swift
index c7c4fd7..a905f8e 100644
--- a/ios/chrome/browser/shared/ui/util/UIView+WindowCoordinates.swift
+++ b/ios/chrome/browser/shared/ui/util/UIView+WindowCoordinates.swift
@@ -156,7 +156,7 @@
     }
     set {
       objc_setAssociatedObject(
-        self, UIView.MirrorViewInWindowKey, newValue, .OBJC_ASSOCIATION_ASSIGN)
+        self, UIView.MirrorViewInWindowKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
     }
   }
 
diff --git a/ios/chrome/browser/shared/ui/util/uiview_window_coordinates_unittest.mm b/ios/chrome/browser/shared/ui/util/uiview_window_coordinates_unittest.mm
index ec14d789..7f531d9 100644
--- a/ios/chrome/browser/shared/ui/util/uiview_window_coordinates_unittest.mm
+++ b/ios/chrome/browser/shared/ui/util/uiview_window_coordinates_unittest.mm
@@ -13,6 +13,20 @@
 using base::test::ios::kWaitForUIElementTimeout;
 using base::test::ios::WaitUntilConditionOrTimeout;
 
+// A minimal UIWindow subclass to use in tests that doesn't register globally
+// and tracks whether it gets deallocated.
+@interface TestUIWindow : UIWindow
+@property(nonatomic, assign) BOOL* deallocatedPtr;
+@end
+
+@implementation TestUIWindow
+- (void)dealloc {
+  if (self.deallocatedPtr) {
+    *self.deallocatedPtr = YES;
+  }
+}
+@end
+
 // Sets up a window and a view.
 class UIViewWindowCoordinatesTest : public PlatformTest {
  protected:
@@ -96,3 +110,50 @@
     return callback_called;
   }));
 }
+
+// Verifies that destroying the window while the view continues to observe it
+// doesn't cause a Use-After-Free access of the mirror view.
+TEST_F(UIViewWindowCoordinatesTest, WindowDeallocationRegressionTest) {
+  __block __weak UIView* weakNotifyingView = nil;
+  BOOL windowDeallocated = NO;
+
+  @autoreleasepool {
+    // Instantiate a freestanding window not attached to a scene so it can be
+    // deallocated directly.
+    TestUIWindow* local_window =
+        [[TestUIWindow alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
+    local_window.deallocatedPtr = &windowDeallocated;
+
+    // Attach the view and register the observer.
+    view_.cr_onWindowCoordinatesChanged = ^(UIView* view) {
+      // NOP
+    };
+    [local_window addSubview:view_];
+
+    // Force layout to instantiate the mirror view.
+    [local_window layoutIfNeeded];
+
+    // Find the NotifyingView in the hierarchy to track its lifetime.
+    for (UIView* subview in local_window.subviews) {
+      if ([NSStringFromClass([subview class])
+              containsString:@"NotifyingView"]) {
+        weakNotifyingView = subview;
+        break;
+      }
+    }
+    EXPECT_NE(weakNotifyingView, nil);
+
+    // Remove reference to allow deallocation.
+    local_window = nil;
+  }
+
+  // Ensure the window and notifying view are actually deallocated.
+  EXPECT_TRUE(WaitUntilConditionOrTimeout(kWaitForUIElementTimeout, ^{
+    return (BOOL)(windowDeallocated && weakNotifyingView == nil);
+  }));
+
+  // Now the internal associated object is DANGLING (if buggy).
+  // Accessing the property setter triggers cleanup which reads the dangling
+  // pointer.
+  view_.cr_onWindowCoordinatesChanged = nil;
+}
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/ios/chrome/browser/shared/ui/util/uiview_window_coordinates_unittest.mm b/ios/chrome/browser/shared/ui/util/uiview_window_coordinates_unittest.mm
index ec14d789..7f531d9 100644
--- a/ios/chrome/browser/shared/ui/util/uiview_window_coordinates_unittest.mm
+++ b/ios/chrome/browser/shared/ui/util/uiview_window_coordinates_unittest.mm
@@ -13,6 +13,20 @@
 using base::test::ios::kWaitForUIElementTimeout;
 using base::test::ios::WaitUntilConditionOrTimeout;
 
+// A minimal UIWindow subclass to use in tests that doesn't register globally
+// and tracks whether it gets deallocated.
+@interface TestUIWindow : UIWindow
+@property(nonatomic, assign) BOOL* deallocatedPtr;
+@end
+
+@implementation TestUIWindow
+- (void)dealloc {
+  if (self.deallocatedPtr) {
+    *self.deallocatedPtr = YES;
+  }
+}
+@end
+
 // Sets up a window and a view.
 class UIViewWindowCoordinatesTest : public PlatformTest {
  protected:
@@ -96,3 +110,50 @@
     return callback_called;
   }));
 }
+
+// Verifies that destroying the window while the view continues to observe it
+// doesn't cause a Use-After-Free access of the mirror view.
+TEST_F(UIViewWindowCoordinatesTest, WindowDeallocationRegressionTest) {
+  __block __weak UIView* weakNotifyingView = nil;
+  BOOL windowDeallocated = NO;
+
+  @autoreleasepool {
+    // Instantiate a freestanding window not attached to a scene so it can be
+    // deallocated directly.
+    TestUIWindow* local_window =
+        [[TestUIWindow alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
+    local_window.deallocatedPtr = &windowDeallocated;
+
+    // Attach the view and register the observer.
+    view_.cr_onWindowCoordinatesChanged = ^(UIView* view) {
+      // NOP
+    };
+    [local_window addSubview:view_];
+
+    // Force layout to instantiate the mirror view.
+    [local_window layoutIfNeeded];
+
+    // Find the NotifyingView in the hierarchy to track its lifetime.
+    for (UIView* subview in local_window.subviews) {
+      if ([NSStringFromClass([subview class])
+              containsString:@"NotifyingView"]) {
+        weakNotifyingView = subview;
+        break;
+      }
+    }
+    EXPECT_NE(weakNotifyingView, nil);
+
+    // Remove reference to allow deallocation.
+    local_window = nil;
+  }
+
+  // Ensure the window and notifying view are actually deallocated.
+  EXPECT_TRUE(WaitUntilConditionOrTimeout(kWaitForUIElementTimeout, ^{
+    return (BOOL)(windowDeallocated && weakNotifyingView == nil);
+  }));
+
+  // Now the internal associated object is DANGLING (if buggy).
+  // Accessing the property setter triggers cleanup which reads the dangling
+  // pointer.
+  view_.cr_onWindowCoordinatesChanged = nil;
+}
Loading diff…

Original Bug Report

reported by [email protected]

Potential UAF in UIView+WindowCoordinates; Change OBJC_ASSOCIATION_ASSIGN to RETAIN

Flapjack, 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 https://chromium.googlesource.com/chromium/src/+/main/docs/security/ai-generated-security-bugs-faq.md for more information.

Overview: Fix requires changing .OBJC_ASSOCIATION_ASSIGN to .OBJC_ASSOCIATION_RETAIN_NONATOMIC for the mirror view. The current unsafe policy creates a dangling pointer when the associated UIWindow is deallocated. Accessing this pointer during subsequent KVO updates triggers a Use-After-Free.

Affected files:

  • ios/chrome/browser/shared/ui/util/UIView+WindowCoordinates.swift

Estimated timestamp from git blame: Unknown (Google3 checkout)

Suggested Fix: Change the associated object policy for MirrorViewInWindowKey from .OBJC_ASSOCIATION_ASSIGN to .OBJC_ASSOCIATION_RETAIN_NONATOMIC (ios/chrome/browser/shared/ui/util/UIView+WindowCoordinates.swift:159).

Vulnerability Summary

Initial KVO observer registration and helper view instantiation are validated as standard UI state management. The critical flaw lies in the Objective-C associated object policy.

The mirrorViewInWindow property holds an __unsafe_unretained reference to a NotifyingView whose sole strong owner is a UIWindow. Upon window deallocation, the NotifyingView is freed.

If the tracked UIView outlives its window (e.g., cached UI components like OmniboxAssistiveKeyboardMediator), it retains a dangling pointer.

Potential Exploitation Steps

  1. A compromised renderer directs IPC to allocate the UI state and then triggers window destruction, freeing the NotifyingView.
  2. The attacker uses standard heap spraying to reallocate the freed NotifyingView memory with a forged object.
  3. The attacker forces a UI hierarchy update on the cached UIView so it detaches or moves, triggering the KVO observer with self.window == nil.
  4. The KVO closure jumps to removeMirrorViewInWindow().
  5. The Swift runtime evaluates the property getter (as? NotifyingView), dereferencing the attacker-controlled isa pointer from the forged object, leading to potential arbitrary code execution.

Note: These are suggested potential steps; our tooling agent does not yet have the ability to run code to produce a live proof-of-concept.

Evaluated with Chrome root at commit: cc901875d53bf4e4fe0e01f02843871da4106e70


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