CVE-2026-13915
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifios/chrome/browser/collaboration/model/ios_collaboration_controller_delegate.mm |
modified | |
TabGroupios/chrome/browser/share_kit/model/share_kit_manage_configuration.h |
modified | |
TabGroupios/chrome/browser/share_kit/model/share_kit_share_group_configuration.h |
modified | |
ifios/chrome/browser/share_kit/model/test_share_kit_service.mm |
modified |
Files Changed
ios/chrome/browser/collaboration/model/ios_collaboration_controller_delegate.hios/chrome/browser/collaboration/model/ios_collaboration_controller_delegate.mmios/chrome/browser/share_kit/model/share_kit_manage_configuration.hios/chrome/browser/share_kit/model/share_kit_share_group_configuration.hios/chrome/browser/share_kit/model/test_share_kit_service.mm
Patch
From 820b6b6d5544a732ac00d19c10b9184fa573f9b2 Mon Sep 17 00:00:00 2001 From: Matt Reichhoff <[email protected]> Date: Wed, 06 May 2026 14:11:19 -0700 Subject: [PATCH] [iOS] Use WeakPtr for tab groups with ShareKit Bug: 508275293 Change-Id: If0bde857b55a3815aa08ef44885c970d403be156 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7807110 Commit-Queue: Matt Reichhoff <[email protected]> Reviewed-by: Andrew Liu <[email protected]> Reviewed-by: Rohit Rao <[email protected]> Cr-Commit-Position: refs/heads/main@{#1626456} --- diff --git a/ios/chrome/browser/collaboration/model/ios_collaboration_controller_delegate.h b/ios/chrome/browser/collaboration/model/ios_collaboration_controller_delegate.h index 5007beb7..c798ae6 100644 --- a/ios/chrome/browser/collaboration/model/ios_collaboration_controller_delegate.h +++ b/ios/chrome/browser/collaboration/model/ios_collaboration_controller_delegate.h @@ -190,13 +190,13 @@ // Configures the shareKit config for the share flow and starts the flow. void ConfigureAndShareTabGroup(const tab_groups::EitherGroupID& either_id, ResultWithGroupTokenCallback result, - const TabGroup* tab_group, + base::WeakPtr<const TabGroup> tab_group, UIImage* faviconsGridImage); // Configures the shareKit config for the manage flow and starts the flow. void ConfigureAndManageTabGroup(const tab_groups::EitherGroupID& either_id, ResultCallback result, - const TabGroup* tab_group, + base::WeakPtr<const TabGroup> tab_group, UIImage* faviconsGridImage); // Returns the join group image displayed in the join flow. diff --git a/ios/chrome/browser/collaboration/model/ios_collaboration_controller_delegate.mm b/ios/chrome/browser/collaboration/model/ios_collaboration_controller_delegate.mm index 8ae4021..6895b2fa 100644 --- a/ios/chrome/browser/collaboration/model/ios_collaboration_controller_delegate.mm +++ b/ios/chrome/browser/collaboration/model/ios_collaboration_controller_delegate.mm @@ -385,7 +385,8 @@ auto callback = base::BindOnce( &IOSCollaborationControllerDelegate::ConfigureAndShareTabGroup, - weak_ptr_factory_.GetWeakPtr(), either_id, std::move(result), tab_group); + weak_ptr_factory_.GetWeakPtr(), either_id, std::move(result), + tab_group->GetWeakPtr()); favicons_grid_configurator_->FetchFaviconsGrid(tab_group, std::move(callback)); @@ -419,7 +420,8 @@ auto callback = base::BindOnce( &IOSCollaborationControllerDelegate::ConfigureAndManageTabGroup, - weak_ptr_factory_.GetWeakPtr(), either_id, std::move(result), tab_group); + weak_ptr_factory_.GetWeakPtr(), either_id, std::move(result), + tab_group->GetWeakPtr()); favicons_grid_configurator_->FetchFaviconsGrid(tab_group, std::move(callback)); @@ -785,7 +787,7 @@ void IOSCollaborationControllerDelegate::ConfigureAndShareTabGroup( const tab_groups::EitherGroupID& either_id, ResultWithGroupTokenCallback result, - const TabGroup* tab_group, + base::WeakPtr<const TabGroup> tab_group, UIImage* faviconsGridImage) { if (!tab_group || !faviconsGridImage) { std::move(result).Run(CollaborationControllerDelegate::Outcome::kFailure, @@ -817,7 +819,7 @@ void IOSCollaborationControllerDelegate::ConfigureAndManageTabGroup( const tab_groups::EitherGroupID& either_id, ResultCallback result, - const TabGroup* tab_group, + base::WeakPtr<const TabGroup> tab_group, UIImage* faviconsGridImage) { if (!tab_group || !faviconsGridImage) { std::move(result).Run(CollaborationControllerDelegate::Outcome::kFailure); diff --git a/ios/chrome/browser/share_kit/model/share_kit_manage_configuration.h b/ios/chrome/browser/share_kit/model/share_kit_manage_configuration.h index b115676..f455302 100644 --- a/ios/chrome/browser/share_kit/model/share_kit_manage_configuration.h +++ b/ios/chrome/browser/share_kit/model/share_kit_manage_configuration.h @@ -7,6 +7,8 @@ #import <UIKit/UIKit.h> +#import "base/memory/weak_ptr.h" + @protocol SceneCommands; enum class ShareKitFlowOutcome; class TabGroup; @@ -23,7 +25,7 @@ @property(nonatomic, copy) NSString* collabID; // Local tab group. -@property(nonatomic, assign) const TabGroup* tabGroup; +@property(nonatomic, assign) base::WeakPtr<const TabGroup> tabGroup; // The group image preview. @property(nonatomic, copy) UIImage* groupImage; diff --git a/ios/chrome/browser/share_kit/model/share_kit_share_group_configuration.h b/ios/chrome/browser/share_kit/model/share_kit_share_group_configuration.h index e7204ea..20dcf842 100644 --- a/ios/chrome/browser/share_kit/model/share_kit_share_group_configuration.h +++ b/ios/chrome/browser/share_kit/model/share_kit_share_group_configuration.h @@ -7,6 +7,8 @@ #import <UIKit/UIKit.h> +#import "base/memory/weak_ptr.h" + @protocol SceneCommands; enum class ShareKitFlowOutcome; class TabGroup; @@ -18,7 +20,7 @@ @property(nonatomic, weak) UIViewController* baseViewController; // Local tab group. -@property(nonatomic, assign) const TabGroup* tabGroup; +@property(nonatomic, assign) base::WeakPtr<const TabGroup> tabGroup; // The group image preview. @property(nonatomic, copy) UIImage* groupImage; diff --git a/ios/chrome/browser/share_kit/model/test_share_kit_service.mm b/ios/chrome/browser/share_kit/model/test_share_kit_service.mm index 3d40b8a..ad4a5dc 100644 --- a/ios/chrome/browser/share_kit/model/test_share_kit_service.mm +++ b/ios/chrome/browser/share_kit/model/test_share_kit_service.mm @@ -141,7 +141,7 @@ NSString* TestShareKitService::ShareTabGroup( ShareKitShareGroupConfiguration* config) { - const TabGroup* tab_group = config.tabGroup; + const TabGroup* tab_group = config.tabGroup.get(); if (!tab_group) { return nil; }
Original Bug Report
Potential UAF in ShareKit via TabGroup pointer bypassing MiraclePtr
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: A Use-After-Free (UAF) vulnerability exists in the iOS collaboration flow when a TabGroup is closed during an asynchronous favicon fetch. A dangling pointer is assigned to an Objective-C configuration object without raw_ptr protection, bypassing MiraclePtr and leading to a potential UAF when accessed by the ShareKit service.
Affected files:
ios/chrome/browser/collaboration/model/ios_collaboration_controller_delegate.mmios/chrome/browser/share_kit/model/share_kit_share_group_configuration.hios/chrome/browser/share_kit/model/share_kit_manage_configuration.h
Estimated timestamp from git blame: 2025-02-28
Technical Details
A potential Use-After-Free (UAF) vulnerability has been identified in the iOS collaboration and sharing flow. The issue occurs when the TabGroup backing a share operation is destroyed while an asynchronous visual data fetch is pending, leading to a MiraclePtr bypass.
When a user initiates sharing for a tab group, IOSCollaborationControllerDelegate::ShowShareDialog is called. This method retrieves the local TabGroup* and initiates an asynchronous fetch of the group’s favicons via favicons_grid_configurator_->FetchFaviconsGrid. A callback is created using base::BindOnce to handle the completion of this fetch:
auto callback = base::BindOnce(
&IOSCollaborationControllerDelegate::ConfigureAndShareTabGroup,
weak_ptr_factory_.GetWeakPtr(), either_id, std::move(result), tab_group);
The FetchFaviconsGrid operation can take up to 5 seconds (enforced by a dispatch_after timeout), during which the UI is not blocked. If the tab group is deleted during this window (e.g., via the user closing it in the UI, or a remote sync deletion event), the C++ TabGroup object is destroyed immediately.
While the TabGroup is destroyed, the memory is temporarily protected (quarantined) by MiraclePtr because the base::BindOnce state holds an internal raw_ptr to it. However, the cancellation flow triggered by the group’s deletion is flawed. IOSCollaborationControllerDelegate::Cancel attempts to cancel the downstream UI by passing session_id_ to ShareKitService. Because the flow is still pending the favicon fetch, session_id_ is nil, meaning the operation is not properly aborted.
When the favicon fetch completes (or the 5-second timeout fires), the callback executes ConfigureAndShareTabGroup. It takes the quarantined tab_group pointer and assigns it to a ShareKitShareGroupConfiguration object:
@property(nonatomic, assign) const TabGroup* tabGroup;
Because this property uses the Objective-C assign semantic, the pointer is stored as a raw, unprotected memory address. The configuration object is then passed to the downstream ShareKitService.
Once the ConfigureAndShareTabGroup method finishes, the base::BindOnce callback state is destroyed. The internal raw_ptr goes out of scope, the MiraclePtr reference count drops to zero, and the TabGroup memory is completely unquarantined and freed to the system allocator.
The ShareKitService now holds a raw pointer to fully freed memory. If it accesses config.tabGroup asynchronously to extract metadata for the UI, a Use-After-Free occurs in the highly-privileged browser process.
Potential Exploitation Steps (Theoretical)
Note: These are suggested steps based on static analysis; a working Proof of Concept has not been executed.
- An attacker initiates the tab group sharing flow, triggering the 5-second asynchronous favicon fetch.
- Immediately, the attacker triggers the deletion of the tab group (e.g., via an incoming remote sync update, or programmatically closing the group).
- The
TabGroupC++ object is destroyed, but its memory is temporarily held in quarantine by the pending callback’sBindState. - The callback executes, assigning the raw pointer to the Objective-C
ShareKitShareGroupConfigurationobject. - The callback finishes, destroying the
BindStateand releasing the memory from quarantine. - The attacker heavily allocates memory in the browser process to reclaim the exact memory chunk previously used by the
TabGroupwith attacker-controlled data. - The downstream
ShareKitServiceasynchronously accesses thetabGrouppointer, dereferencing the attacker-controlled memory and triggering the UAF.
Suggested Remediation
- Update
ShareKitShareGroupConfigurationandShareKitManageConfigurationto store the tab group as abase::WeakPtr<const TabGroup>instead of a rawassignpointer. Provide a C++ setter/getter if necessary to bridge between Objective-C and the C++ WeakPtr. - In
IOSCollaborationControllerDelegate::ConfigureAndShareTabGroup(andConfigureAndManageTabGroup), pass theTabGroupas abase::WeakPtrand verify its validity (if (!tab_group) return;) before attempting to construct the configuration object and initiate the downstream ShareKit session.
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.