High watch CVSS 8.1 chrome UAF 📄 Reporter bug report 🔧 Commit mapped

Overview

High
Severity
8.1
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in Mobile
DescriptionUse after free in Mobile
ComponentMobile
Sub-componentiOS
Bug ClassUAF
Action tierwatch — corruption primitive but gated (uncommon) — not default-reachable
Computed by PatchGap's deterministic exploitability ruler from the fixing commit. patch-now = RCE-class & attacker-reachable in a default config, or exploited in the wild; watch = real security signal below that bar; noise = no security relevance.
Tracker517548647
Fix commit1d80ab8d4829 (chromium/src) +14/-5
CWECWE-416 (Use-after-free)
CVSS vectorCVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H
CISA KEVNot listed
CreditedGoogle
Reported2026-05-28 (per vendor advisory)
Fix landed2026-07-22 (public git)
Disclosed2026-08-25
Patch gap34 days — fix public in source before the advisory

Background

Key-Value Coding (KVC)
Foundation’s dynamic property-access mechanism; -[NSDictionary valueForKey:] treats a key beginning with @ specially, stripping the @ and dispatching to an NSObject/NSDictionary method of that name instead of doing a literal dictionary lookup.
Dictionary subscripting
dict[key] / dict[key] = value compile to objectForKeyedSubscript: / setObject:forKeyedSubscript:, which always treat key as a literal dictionary key.
Collapsed-section keys
iOS Chrome ListModel table views (e.g. Recent Tabs) persist each section’s collapsed/expanded state in a dictionary stored under kListModelCollapsedKey, keyed by a per-section string.
Foreign sync session tags
identifiers of the user’s other synced devices/sessions; per the commit message, Recent Tabs derives collapsed-section keys from them, so their content originates outside the local app.

Root Cause Analysis

The collapsed-state getters in ios/chrome/browser/shared/ui/list_model/list_model.mm and -sectionKeyIsCollapsed: in RecentTabsTableViewController looked up the flag with [collapsedSections valueForKey:sectionKey], and the ListModel setter stored it with [newCollapsedSection setValue:value forKey:sectionKey]. Because sectionKey can be derived from a foreign sync session tag, a key beginning with @ caused KVC to strip the prefix and invoke a method on the dictionary by name (the new test uses @count, mentioned alongside @self), so an externally influenced string selected which selector was dispatched. The invariant “section keys are opaque data” was violated, turning a lookup into dynamic method invocation; the advisory classifies the result as a use-after-free, and the exact selector/object-lifetime path that leads to the freed object is not shown in the patch (inferred).

The fix replaces KVC calls with literal subscripting (collapsedSections[sectionKey] and newCollapsedSection[sectionKey] = @(collapsed)), so no key is ever interpreted as a selector. The RecentTabsTableViewController setter already used subscripting; its only change ([NSNumber numberWithBool:] to @(collapsed)) is cosmetic.

Key insight
Using KVC (valueForKey:/setValue:forKey:) as a dictionary accessor lets an @-prefixed, externally derived key trigger arbitrary selector dispatch on the dictionary; switching to literal subscripting makes the key pure data.

Attack Path

  1. Influence a session tag An attacker gets a foreign sync session whose tag (and thus collapsed-section key) begins with @ into the victim’s synced sessions; how an attacker controls this tag (e.g. a malicious device or sync data in the victim’s account) is not shown in the patch (inferred).
  2. Open Recent Tabs on iOS The victim uses Chrome on iOS and views Recent Tabs, which renders a section for that foreign session and queries or toggles its collapsed state.
  3. KVC selector dispatch Pre-patch, valueForKey: with the @-prefixed key invoked the named method on the collapsed-sections dictionary instead of a literal lookup.
  4. Memory-safety consequence Invoking an attacker-selected method on the dictionary can disturb object lifetime and leave a dangling reference, matching the advisory’s use-after-free classification (inferred mechanism).

Impact Assessment

On Chrome for iOS, an attacker-influenced foreign session tag could cause unintended method dispatch in the browser app process when Recent Tabs reads collapsed-section state, which the advisory reports as a use-after-free; any resulting code execution is (inferred). Preconditions are significant: the attacker must influence a foreign sync session tag in the victim’s synced data and the victim must view Recent Tabs on iOS; ordinary web content does not reach this path. The advisory gives only “Use after free in Mobile” and does not state in-the-wild exploitation.

Changed Functions

FunctionChangeNotes
TEST_F
ios/chrome/browser/shared/ui/list_model/list_model_collapse_unittest.mm
modified

Files Changed

  • ios/chrome/browser/recent_tabs/ui/recent_tabs_table_view_controller.mm
  • ios/chrome/browser/shared/ui/list_model/list_model_collapse_unittest.mm
  • ios/chrome/browser/shared/ui/list_model/list_model.mm

Audit Directions

  • KVC used as a dictionary API
    Grep iOS code for valueForKey:, setValue:forKey:, valueForKeyPath: and setValuesForKeysWithDictionary: on NSDictionary/NSMutableDictionary where the key can come from sync, the network, web content, or user data, and replace with subscripting or objectForKey:.
  • External strings as identifiers
    Trace where sync-derived values (session tags, device names, tab titles) become dictionary keys, NSUserDefaults keys, selectors or key paths, and ensure they are treated as opaque data.
  • Dynamic dispatch from data
    Look for NSSelectorFromString, performSelector:, and KVC key paths built from non-constant strings in Chrome’s iOS UI layers.
From 1d80ab8d4829a9e5b9802cdda56de66a18130349 Mon Sep 17 00:00:00 2001
From: Huiting Yu <huitingyu@google.com>
Date: Wed, 22 Jul 2026 07:17:04 -0700
Subject: [PATCH] [iOS] Look up ListModel collapsed-section keys with dictionary subscripting

ListModelCollapsedMediator and RecentTabsTableViewController used
-[NSDictionary valueForKey:] and -[NSMutableDictionary setValue:forKey:]
to look up and set a section's collapsed flag.

When a collapsed key starts with "@" (derived from foreign sync session
tags), KVC strips the "@" prefix and forwards to NSObject KVC selector
dispatch instead of performing a literal dictionary lookup.

Use dictionary subscripting so keys are always treated as literal keys.

Fixed: 517548647
Change-Id: I6bab75593a44261acf01e8fe78ce3f516085b4a1
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8131683
Commit-Queue: Huiting Yu <huitingyu@google.com>
Reviewed-by: Gauthier Ambard <gambard@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1666267}
---
diff --git a/ios/chrome/browser/recent_tabs/ui/recent_tabs_table_view_controller.mm b/ios/chrome/browser/recent_tabs/ui/recent_tabs_table_view_controller.mm
index 5474a16..c637c84 100644
--- a/ios/chrome/browser/recent_tabs/ui/recent_tabs_table_view_controller.mm
+++ b/ios/chrome/browser/recent_tabs/ui/recent_tabs_table_view_controller.mm
@@ -1670,13 +1670,13 @@
   NSMutableDictionary* newCollapsedSection = [NSMutableDictionary
       dictionaryWithDictionary:newUserInfo[kListModelCollapsedKey]];
   newUserInfo[kListModelCollapsedKey] = newCollapsedSection;
-  newCollapsedSection[sectionKey] = [NSNumber numberWithBool:collapsed];
+  newCollapsedSection[sectionKey] = @(collapsed);
   _session.userInfo = newUserInfo;
 }
 
 - (BOOL)sectionKeyIsCollapsed:(NSString*)sectionKey {
   NSDictionary* collapsedSections = _session.userInfo[kListModelCollapsedKey];
-  NSNumber* value = (NSNumber*)[collapsedSections valueForKey:sectionKey];
+  NSNumber* value = (NSNumber*)collapsedSections[sectionKey];
   return [value boolValue];
 }
 
diff --git a/ios/chrome/browser/shared/ui/list_model/list_model.mm b/ios/chrome/browser/shared/ui/list_model/list_model.mm
index 77d999526..ec799c85 100644
--- a/ios/chrome/browser/shared/ui/list_model/list_model.mm
+++ b/ios/chrome/browser/shared/ui/list_model/list_model.mm
@@ -451,8 +451,7 @@
       [defaults dictionaryForKey:kListModelCollapsedKey];
   NSMutableDictionary* newCollapsedSection =
       [NSMutableDictionary dictionaryWithDictionary:collapsedSections];
-  NSNumber* value = [NSNumber numberWithBool:collapsed];
-  [newCollapsedSection setValue:value forKey:sectionKey];
+  newCollapsedSection[sectionKey] = @(collapsed);
   [defaults setObject:newCollapsedSection forKey:kListModelCollapsedKey];
 }
 
@@ -460,7 +459,7 @@
   NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
   NSDictionary* collapsedSections =
       [defaults dictionaryForKey:kListModelCollapsedKey];
-  NSNumber* value = (NSNumber*)[collapsedSections valueForKey:sectionKey];
+  NSNumber* value = (NSNumber*)collapsedSections[sectionKey];
   return [value boolValue];
 }
 
diff --git a/ios/chrome/browser/shared/ui/list_model/list_model_collapse_unittest.mm b/ios/chrome/browser/shared/ui/list_model/list_model_collapse_unittest.mm
index 61a02f0c..73e60f875 100644
--- a/ios/chrome/browser/shared/ui/list_model/list_model_collapse_unittest.mm
+++ b/ios/chrome/browser/shared/ui/list_model/list_model_collapse_unittest.mm
@@ -192,4 +192,14 @@
   EXPECT_FALSE([model sectionIsCollapsed:SectionIdentifierBar]);
 }
 
+// Tests that `@`-prefixed collapsed keys (e.g., `@count` or `@self`) round-trip
+// correctly without Key-Value Coding (KVC) dynamic selector dispatch.
+TEST_F(ListModelCollapseTest, CollapsedKeyWithAtPrefix) {
+  [model setSectionIdentifier:SectionIdentifierFoo collapsedKey:@"@count"];
+  [model setSection:SectionIdentifierFoo collapsed:YES];
+  EXPECT_TRUE([model sectionIsCollapsed:SectionIdentifierFoo]);
+  [model setSection:SectionIdentifierFoo collapsed:NO];
+  EXPECT_FALSE([model sectionIsCollapsed:SectionIdentifierFoo]);
+}
+
 }  // namespace
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/ios/chrome/browser/shared/ui/list_model/list_model_collapse_unittest.mm b/ios/chrome/browser/shared/ui/list_model/list_model_collapse_unittest.mm
index 61a02f0c..73e60f875 100644
--- a/ios/chrome/browser/shared/ui/list_model/list_model_collapse_unittest.mm
+++ b/ios/chrome/browser/shared/ui/list_model/list_model_collapse_unittest.mm
@@ -192,4 +192,14 @@
   EXPECT_FALSE([model sectionIsCollapsed:SectionIdentifierBar]);
 }
 
+// Tests that `@`-prefixed collapsed keys (e.g., `@count` or `@self`) round-trip
+// correctly without Key-Value Coding (KVC) dynamic selector dispatch.
+TEST_F(ListModelCollapseTest, CollapsedKeyWithAtPrefix) {
+  [model setSectionIdentifier:SectionIdentifierFoo collapsedKey:@"@count"];
+  [model setSection:SectionIdentifierFoo collapsed:YES];
+  EXPECT_TRUE([model sectionIsCollapsed:SectionIdentifierFoo]);
+  [model setSection:SectionIdentifierFoo collapsed:NO];
+  EXPECT_FALSE([model sectionIsCollapsed:SectionIdentifierFoo]);
+}
+
 }  // namespace
Loading diff…

Original Bug Report

reported by vm...@google.com

Potential Use-after-free in iOS Recent Tabs via KVC over-release of collapsed sections dictionary

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: The Recent Tabs UI on iOS utilizes valueForKey: instead of objectForKey: to query if a section is collapsed, using a sync-controlled session tag as the lookup key. Under Cocoa/Foundation Key-Value Coding (KVC), lookup keys prefixed with @ strip the prefix and dynamically invoke matching memory-management selectors (like -release or -dealloc) on the target dictionary. This can allow an attacker who controls a synced device to trigger an over-release or premature deallocation, leading to a potential Use-After-Free (UAF) in the browser process.

Affected files:

  • ios/chrome/browser/recent_tabs/ui/recent_tabs_table_view_controller.mm
  • ios/chrome/browser/shared/ui/list_model/list_model.mm

Estimated timestamp from git blame: 2018-03-15

Description

In iOS Chrome, the Recent Tabs UI manages the collapsed state of remote sessions using a dictionary. When querying whether a session section is collapsed, both ListModelCollapsedSceneSessionMediator and ListModelCollapsedMediator retrieve the collapsed state from a dictionary using valueForKey: instead of objectForKey:. The lookup key (sectionKey) is derived directly from the foreign session’s session_tag, which is sync-controlled.

In ios/chrome/browser/recent_tabs/ui/recent_tabs_table_view_controller.mm:

- (BOOL)sectionKeyIsCollapsed:(NSString*)sectionKey {
  NSDictionary* collapsedSections = _session.userInfo[kListModelCollapsedKey];
  NSNumber* value = (NSNumber*)[collapsedSections valueForKey:sectionKey];   // <-- sync-controlled key
  return [value boolValue];
}

In ios/chrome/browser/shared/ui/list_model/list_model.mm:

- (BOOL)sectionKeyIsCollapsed:(NSString*)sectionKey {
  NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
  NSDictionary* collapsedSections = [defaults dictionaryForKey:kListModelCollapsedKey];
  NSNumber* value = (NSNumber*)[collapsedSections valueForKey:sectionKey];   // <-- sync-controlled key
  return [value boolValue];
}

Under Cocoa/Foundation Key-Value Coding (KVC), if valueForKey: is called on an NSDictionary with an @-prefixed key (e.g., "@release" or "@dealloc"), the dictionary strips the @ prefix and forwards the call to [super valueForKey:]. The KVC engine then dynamically resolves and executes the matching selectors (like -release or -dealloc) on the receiver dictionary. This bypasses ARC ownership rules and can lead to immediate or delayed over-release, resulting in a potential Use-After-Free (UAF) or double-free condition when UIKit or ARC subsequently references the dictionary.

Potential Attack Scenario

Note: The following steps are potential/suggested vectors; our tooling does not currently have the capability to execute live proof-of-concept code on physical iOS devices.

  1. Ingest: An attacker with control over a synced device modifies a SESSIONS entity, setting the session_tag to a malicious string such as "@release" or "@dealloc".
  2. UI Loading: The victim opens the Recent Tabs UI, which parses the synced sessions and populates the table sections using addSessionSections.
  3. KVC Lookup: The UI queries sectionIsCollapsed: with the mapped session tag string. This triggers valueForKey: with the malicious key on the collapsed sections dictionary.
  4. Corruption: The dictionary receives a -release or -dealloc message directly via the KVC resolver. This decrements the dictionary’s reference count prematurely, resulting in a dangling reference/UAF or immediate crash when the dictionary is later accessed or serialized.

Suggested Fix

To remediate this issue, replace all instances of valueForKey: with objectForKey: (or dictionary subscripting) to ensure standard lookup semantics are enforced rather than dynamic KVC selector dispatch.

In ios/chrome/browser/recent_tabs/ui/recent_tabs_table_view_controller.mm:

- (BOOL)sectionKeyIsCollapsed:(NSString*)sectionKey {
  NSDictionary* collapsedSections = _session.userInfo[kListModelCollapsedKey];
  NSNumber* value = (NSNumber*)[collapsedSections objectForKey:sectionKey];
  return [value boolValue];
}

In ios/chrome/browser/shared/ui/list_model/list_model.mm:

- (BOOL)sectionKeyIsCollapsed:(NSString*)sectionKey {
  NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
  NSDictionary* collapsedSections = [defaults dictionaryForKey:kListModelCollapsedKey];
  NSNumber* value = (NSNumber*)[collapsedSections objectForKey:sectionKey];
  return [value boolValue];
}

Evaluated with Chrome root at commit: 5133b93d189b383c37805b1cf3a9d2dbfe8d7379


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