CVE-2026-11202
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifios/chrome/browser/content_suggestions/ui/cells/content_suggestions_tile_saver.mm |
modified | |
ifios/chrome/browser/share_extension/model/share_extension_utils.mm |
modified |
Files Changed
ios/chrome/browser/content_suggestions/ui/cells/content_suggestions_tile_saver.mmios/chrome/browser/share_extension/model/share_extension_utils.mmios/chrome/common/ntp_tile/ntp_tile.hios/chrome/common/ntp_tile/ntp_tile.mmios/chrome/common/ui/favicon/favicon_attributes.hios/chrome/common/ui/favicon/favicon_attributes.mm
Patch
From 3976724788f092cfb41c0265101c1ae2cfa6e2b8 Mon Sep 17 00:00:00 2001 From: Olivier ROBIN <[email protected]> Date: Wed, 29 Apr 2026 03:03:16 -0700 Subject: [PATCH] Use SecureCoding for app group serialized data Use secure coding on extensions <-> chrome communication to avoid unserializing uncontrolled data. Bug: 505144022 Change-Id: Icac4fd5300646443b1beb1a46e95aad520494e5f Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7800139 Commit-Queue: Olivier Robin <[email protected]> Reviewed-by: Gauthier Ambard <[email protected]> Cr-Commit-Position: refs/heads/main@{#1622317} --- diff --git a/ios/chrome/browser/content_suggestions/ui/cells/content_suggestions_tile_saver.mm b/ios/chrome/browser/content_suggestions/ui/cells/content_suggestions_tile_saver.mm index e52f4d1..145d7efe 100644 --- a/ios/chrome/browser/content_suggestions/ui/cells/content_suggestions_tile_saver.mm +++ b/ios/chrome/browser/content_suggestions/ui/cells/content_suggestions_tile_saver.mm @@ -122,8 +122,11 @@ return [[NSMutableDictionary alloc] init]; } - unarchiver.requiresSecureCoding = NO; - return [unarchiver decodeObjectForKey:NSKeyedArchiveRootObjectKey]; + unarchiver.requiresSecureCoding = YES; + NSSet* classes = [NSSet + setWithObjects:[NSDictionary class], [NSURL class], [NTPTile class], nil]; + return [unarchiver decodeObjectOfClasses:classes + forKey:NSKeyedArchiveRootObjectKey]; } void GetFaviconsAndSave( @@ -222,7 +225,7 @@ NSDate* last_modification_date = NSDate.date; NSError* error = nil; NSData* data = [NSKeyedArchiver archivedDataWithRootObject:most_visited_data - requiringSecureCoding:NO + requiringSecureCoding:YES error:&error]; if (!data || error) { DLOG(WARNING) << "Error serializing most visited: " diff --git a/ios/chrome/browser/share_extension/model/share_extension_utils.mm b/ios/chrome/browser/share_extension/model/share_extension_utils.mm index 06548a61..1cd09757 100644 --- a/ios/chrome/browser/share_extension/model/share_extension_utils.mm +++ b/ios/chrome/browser/share_extension/model/share_extension_utils.mm @@ -73,8 +73,12 @@ return result; } - unarchiver.requiresSecureCoding = NO; - id entryID = [unarchiver decodeObjectForKey:NSKeyedArchiveRootObjectKey]; + unarchiver.requiresSecureCoding = YES; + NSSet* classes = [NSSet setWithObjects:[NSDictionary class], [NSNumber class], + [NSURL class], [NSString class], + [NSDate class], nil]; + id entryID = [unarchiver decodeObjectOfClasses:classes + forKey:NSKeyedArchiveRootObjectKey]; NSDictionary* entry = base::apple::ObjCCast<NSDictionary>(entryID); if (!entry) { return result; diff --git a/ios/chrome/common/ntp_tile/ntp_tile.h b/ios/chrome/common/ntp_tile/ntp_tile.h index 956cf46..bc7bde6 100644 --- a/ios/chrome/common/ntp_tile/ntp_tile.h +++ b/ios/chrome/common/ntp_tile/ntp_tile.h @@ -9,7 +9,7 @@ // This class stores all the data associated with an NTP Most Visited tile // suggestion in an NSCoding-enabled format. -@interface NTPTile : NSObject <NSCoding> +@interface NTPTile : NSObject <NSSecureCoding> // The most visited site's title. @property(readonly, atomic) NSString* title; diff --git a/ios/chrome/common/ntp_tile/ntp_tile.mm b/ios/chrome/common/ntp_tile/ntp_tile.mm index e90d8a6..a11aa98f 100644 --- a/ios/chrome/common/ntp_tile/ntp_tile.mm +++ b/ios/chrome/common/ntp_tile/ntp_tile.mm @@ -61,18 +61,29 @@ return self; } ++ (BOOL)supportsSecureCoding { + return YES; +} + - (instancetype)initWithCoder:(NSCoder*)aDecoder { - return [self initWithTitle:[aDecoder decodeObjectForKey:kTitleKey] - URL:[aDecoder decodeObjectForKey:kURLKey] - faviconFileName:[aDecoder decodeObjectForKey:kfaviconFileNameKey] + return [self initWithTitle:[aDecoder decodeObjectOfClass:[NSString class] + forKey:kTitleKey] + URL:[aDecoder decodeObjectOfClass:[NSURL class] + forKey:kURLKey] + faviconFileName:[aDecoder decodeObjectOfClass:[NSString class] + forKey:kfaviconFileNameKey] fallbackTextColor:[aDecoder - decodeObjectForKey:kFallbackTextColorKey] + decodeObjectOfClass:[UIColor class] + forKey:kFallbackTextColorKey] fallbackBackgroundColor: - [aDecoder decodeObjectForKey:kFallbackBackgroundColorKey] + [aDecoder decodeObjectOfClass:[UIColor class] + forKey:kFallbackBackgroundColorKey] fallbackIsDefaultColor:[aDecoder decodeBoolForKey:kFallbackIsDefaultColorKey] - fallbackMonogram:[aDecoder decodeObjectForKey:kFallbackMonogram] - position:[[aDecoder decodeObjectForKey:kPosition] + fallbackMonogram:[aDecoder decodeObjectOfClass:[NSString class] + forKey:kFallbackMonogram] + position:[[aDecoder decodeObjectOfClass:[NSNumber class] + forKey:kPosition] unsignedIntegerValue]]; } diff --git a/ios/chrome/common/ui/favicon/favicon_attributes.h b/ios/chrome/common/ui/favicon/favicon_attributes.h index 0e9f38c..d2eb01b 100644 --- a/ios/chrome/common/ui/favicon/favicon_attributes.h +++ b/ios/chrome/common/ui/favicon/favicon_attributes.h @@ -12,7 +12,7 @@ // Attributes of a favicon. A favicon is represented either with an image or // with a fallback monogram of a given color and background color. -@interface FaviconAttributes : NSObject <NSCoding> +@interface FaviconAttributes : NSObject <NSSecureCoding> // Favicon image. Can be nil. If it is nil, monogram string and color are // guaranteed to be not nil. diff --git a/ios/chrome/common/ui/favicon/favicon_attributes.mm b/ios/chrome/common/ui/favicon/favicon_attributes.mm index c582884..b9fa4996 100644 --- a/ios/chrome/common/ui/favicon/favicon_attributes.mm +++ b/ios/chrome/common/ui/favicon/favicon_attributes.mm @@ -61,13 +61,21 @@ #pragma mark - NSCoding ++ (BOOL)supportsSecureCoding { + return YES; +} + - (instancetype)initWithCoder:(NSCoder*)aDecoder { UIImage* faviconImage = - [UIImage imageWithData:[aDecoder decodeObjectForKey:kFaviconImageKey]]; - NSString* monogramString = [aDecoder decodeObjectForKey:kFaviconMonogramKey]; - UIColor* textColor = [aDecoder decodeObjectForKey:kFaviconTextColorKey]; + [UIImage imageWithData:[aDecoder decodeObjectOfClass:[NSData class] + forKey:kFaviconImageKey]]; + NSString* monogramString = [aDecoder decodeObjectOfClass:[NSString class] + forKey:kFaviconMonogramKey]; + UIColor* textColor = [aDecoder decodeObjectOfClass:[UIColor class] + forKey:kFaviconTextColorKey]; UIColor* backgroundColor = - [aDecoder decodeObjectForKey:kFaviconBackgroundColorKey]; + [aDecoder decodeObjectOfClass:[UIColor class] + forKey:kFaviconBackgroundColorKey]; if (faviconImage || (monogramString && textColor && backgroundColor)) { return [self initWithImage:faviconImage monogram:monogramString
Original Bug Report
Potential Sandbox Escape via Insecure Deserialization of App Group Data
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 go/chrome-ai-generated-security-bugs-faq for more information.
Overview: Multiple iOS Chrome components process data from the shared App Group container using NSKeyedUnarchiver with requiresSecureCoding explicitly disabled. A compromised App Extension can write malicious serialized objects to this shared storage. When the Main Browser process reads and deserializes this data, it can lead to arbitrary code execution (RCE) and a sandbox escape.
Affected files:
ios/chrome/browser/content_suggestions/ui/cells/content_suggestions_tile_saver.mmios/chrome/browser/share_extension/model/share_extension_utils.mmios/chrome/credential_provider_extension/favicon_util.mm
Estimated timestamp from git blame: 2025-07-25
Summary
A potential sandbox escape vulnerability exists in iOS Chrome due to the insecure deserialization of data retrieved from the shared App Group container.
App Extensions (such as the Widget Extension or Share Extension) operate in a restricted sandbox but share access to an “App Group” container with the Main Chrome Browser app for data synchronization. In several places, the Main Browser process reads data from this shared container and deserializes it using NSKeyedUnarchiver with requiresSecureCoding explicitly set to NO.
Because the App Group container is writable by the less privileged App Extensions, an attacker who compromises an extension can write a maliciously crafted serialized Objective-C object graph into the shared storage. When the fully privileged Main Browser process deserializes this payload without secure coding enforced, it will instantiate any class specified by the attacker, potentially leading to Arbitrary Code Execution (RCE) via standard Objective-C gadget chains.
Technical Details
The vulnerability manifests in at least two distinct paths where the Main Browser process reads from the shared container:
Path 1: Content Suggestions (NTP Tiles)
In ios/chrome/browser/content_suggestions/ui/cells/content_suggestions_tile_saver.mm, the DecodeData function explicitly disables secure coding:
NSDictionary* DecodeData(NSData* data) {
// ... initialization ...
unarchiver.requiresSecureCoding = NO;
return [unarchiver decodeObjectForKey:NSKeyedArchiveRootObjectKey];
}
This function is called by a background task ClearOutdatedIcons(), which reads the dictionary stored at app_group::kSuggestedItemsForMultiprofile in the shared NSUserDefaults. A compromised extension can poison this key.
Path 2: Share Extension Processing
In ios/chrome/browser/share_extension/model/share_extension_utils.mm, the PerformBlockingFileReadAndParse function reads files from the shared ExternalCommands folder:
ParsedShareExtensionEntry* PerformBlockingFileReadAndParse(NSURL* file_url) {
// ... initialization ...
unarchiver.requiresSecureCoding = NO;
id entryID = [unarchiver decodeObjectForKey:NSKeyedArchiveRootObjectKey];
// ...
}
This is called by ShareExtensionController::handleFileAtURL:, which monitors a shared folder that the Share Extension writes to.
Potential Attack Steps
Note: These steps describe a potential attack path; we do not currently have a working proof of concept to verify execution.
- Initial Compromise: An attacker exploits a separate vulnerability to gain initial code execution within a sandboxed iOS App Extension belonging to Chrome (e.g., the Widget or Share extension).
- Payload Generation: The attacker crafts a malicious serialized Objective-C object graph (
NSDatapayload) utilizing known deserialization gadget chains. - Data Poisoning: Running within the extension, the attacker writes the
NSDatapayload to the shared App Group container. This could be writing to theapp_group::kSuggestedItemsForMultiprofilekey in the sharedNSUserDefaults, or writing a file to the sharedExternalCommandsdirectory. - Triggering the Main App: The user interacts with the Main Chrome Browser app, triggering actions that read the shared data (e.g., opening the New Tab Page triggers the tile saver background task; the
ShareExtensionControllerautomatically detects new files). - Insecure Deserialization: The Main Browser process reads the poisoned data and passes it to
NSKeyedUnarchiver. - Arbitrary Code Execution: Because
requiresSecureCoding = NO, the unarchiver allocates and initializes the attacker’s gadget classes. The subsequent execution of methods likeinitWithCoder:ordeallocprovides Arbitrary Code Execution within the highly privileged Main Browser process, completing the sandbox escape.
Recommendation
To remediate this issue, data crossing process boundaries must enforce secure coding:
- Ensure that all classes serialized and stored in the App Group container (such as
NTPTileandFaviconAttributes) implement theNSSecureCodingprotocol. - Audit all usages of
NSKeyedUnarchiverthat process data from shared containers. - Set
unarchiver.requiresSecureCoding = YES;. - Use
decodeObjectOfClasses:forKey:ordecodeObjectOfClass:forKey:to specify an explicit allowlist of expected classes during deserialization, rather than the overly permissivedecodeObjectForKey:.
Evaluated with Chrome root at commit: 4a3e9db74111a3c6c4b3acfd70050a05077cf27a
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.