Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInsufficient validation of untrusted input in Chrome for iOS
DescriptionInsufficient validation of untrusted input in Chrome for iOS
ComponentChrome for iOS
Bug ClassLogic Error
Tracker517610676
Fix commit406a0fa3ec41 (chromium/src) +8/-6
CISA KEVNot listed
CreditedGoogle
Disclosed2026-06-30

Files Changed

  • ios/chrome/widget_kit_extension/shortcuts_widget.swift
From 406a0fa3ec41d15b6bd951692bb4d812e947f4fd Mon Sep 17 00:00:00 2001
From: Olivier Robin <olivierrobin@google.com>
Date: Thu, 04 Jun 2026 01:19:05 -0700
Subject: [PATCH] Use secureCoding for favicon decoding in shortcuts widget

Fixed: 517610676
Change-Id: I5ada70d48f792a160aaf287a06c8097f2f0dae50
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7895591
Commit-Queue: Olivier Robin <olivierrobin@chromium.org>
Reviewed-by: Robbie Gibson <rkgibson@google.com>
Cr-Commit-Position: refs/heads/main@{#1641522}
---

diff --git a/ios/chrome/widget_kit_extension/shortcuts_widget.swift b/ios/chrome/widget_kit_extension/shortcuts_widget.swift
index 94b99f1..75630fbd 100644
--- a/ios/chrome/widget_kit_extension/shortcuts_widget.swift
+++ b/ios/chrome/widget_kit_extension/shortcuts_widget.swift
@@ -229,10 +229,12 @@
   guard let unarchiver = unarchiverForAccount
   else { return emptyEntry }
 
-  unarchiver.requiresSecureCoding = false
+  unarchiver.requiresSecureCoding = true
 
+  let allowedClasses = [NSDictionary.self, NSURL.self, NTPTile.self]
   guard
-    let mostVisitedSites = unarchiver.decodeObject(forKey: NSKeyedArchiveRootObjectKey)
+    let mostVisitedSites = unarchiver.decodeObject(
+      of: allowedClasses, forKey: NSKeyedArchiveRootObjectKey)
       as? [NSURL: NTPTile]
   else {
     return emptyEntry
@@ -287,16 +289,16 @@
 
   // Create a chromewidgetkit:// url to open the given URL.
   private func convertURL(url: URL) -> URL {
-    let query_url = URLQueryItem(name: "url", value: url.absoluteString)
+    let queryUrl = URLQueryItem(name: "url", value: url.absoluteString)
     var urlcomps = URLComponents(
       url: WidgetConstants.ShortcutsWidget.open,
       resolvingAgainstBaseURL: false)!
     if entry.gaiaID == nil {
-      urlcomps.queryItems = [query_url]
+      urlcomps.queryItems = [queryUrl]
     } else {
       // Add the gaia_id parameter only if available.
-      let query_gaia = URLQueryItem(name: "gaia_id", value: entry.gaiaID)
-      urlcomps.queryItems = [query_url, query_gaia]
+      let queryGaia = URLQueryItem(name: "gaia_id", value: entry.gaiaID)
+      urlcomps.queryItems = [queryUrl, queryGaia]
     }
     return urlcomps.url!
   }
Loading diff…

Original Bug Report

reported by vm...@google.com

Insecure deserialization in shortcuts_widget.swift via shared defaults

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 WidgetKit extension in iOS Chrome reads an NSData blob from the shared App Group user defaults and deserializes it with secure coding disabled. This potentially allows a compromised peer extension to trigger arbitrary class instantiation inside the WidgetKit extension process via NSKeyedUnarchiver. A counterpart implementation in the main app was previously hardened, but this Swift-based reader was missed.

Affected files:

  • ios/chrome/widget_kit_extension/shortcuts_widget.swift

Estimated timestamp from git blame: 2024-12-02

Summary

A potential insecure deserialization vulnerability exists in the Swift reader within the WidgetKit extension in iOS Chrome. Specifically, in ios/chrome/widget_kit_extension/shortcuts_widget.swift, the helper function loadMostVisitedSitesEntry() reads an NSData blob from the shared App-Group NSUserDefaults suite (under the key "SuggestedItemsForMIM"), disables secure coding, and decodes the root object without an allowlist of permitted classes.

Affected Files and Line Numbers

  • ios/chrome/widget_kit_extension/shortcuts_widget.swift (lines 221-239)

Vulnerable Code Detail

In shortcuts_widget.swift, the code retrieves the dictionary and decodes it as follows:

  guard let data = sharedDefaults.object(forKey: "SuggestedItemsForMIM") as? [String: Data]
  else { return emptyEntry }
  var unarchiverForAccount: NSKeyedUnarchiver?
  for (key, value) in data {
    if gaia == key {
      unarchiverForAccount = try? NSKeyedUnarchiver(forReadingFrom: value)
    }
  }
  guard let unarchiver = unarchiverForAccount
  else { return emptyEntry }

  unarchiver.requiresSecureCoding = false // <-- Secure coding explicitly disabled

  guard
    let mostVisitedSites = unarchiver.decodeObject(forKey: NSKeyedArchiveRootObjectKey)
      as? [NSURL: NTPTile]
  else {
    return emptyEntry
  }

The type cast to [NSURL: NTPTile] occurs only after the object graph has been fully navigated and deserialized. By the time the cast runs, NSKeyedUnarchiver has already invoked -initWithCoder: on the classes named in the archive, enabling potential arbitrary class instantiation and execution of any available deserialization gadget chains inside the sandboxed WidgetKit extension process.

Root Cause & Context

This is the Swift implementation corresponding to the same NSUserDefaults key that was previously hardened in the main application’s Objective-C counterpart (ios/chrome/browser/content_suggestions/ui/cells/content_suggestions_tile_saver.mm line 125). While the main app’s reader enforces secure coding and uses an allowlist of permitted classes (NSDictionary, NSURL, and NTPTile), this Swift reader in the WidgetKit extension was overlooked.

Potential Attack Scenario

(Note: These are potential steps; we do not have a working proof of concept successfully executed on a live device)

  1. An attacker gains code execution in a peer app extension belonging to the same App Group (e.g., the Share Extension, which processes untrusted user-provided content).
  2. The compromised peer extension writes a malicious serialized payload mapped to the active account’s GAIA ID into the shared NSUserDefaults suite under the key "SuggestedItemsForMIM".
  3. To bypass expiration validation, the compromised peer extension also writes a current timestamp under the key "SuggestedItemsLastModificationDateForMIM".
  4. When a Shortcuts widget refresh occurs (either triggered by the OS or manually by the user), the WidgetKit extension loads the timeline and invokes loadMostVisitedSitesEntry().
  5. The extension retrieves the malicious payload from the shared defaults, instantiates NSKeyedUnarchiver, disables secure coding, and decodes the root object.
  6. The unarchiver executes the gadget chain embedded in the payload during deserialization, potentially leading to arbitrary code execution inside the sandboxed WidgetKit extension process.

Suggested Fix

Update the Swift deserialization logic in shortcuts_widget.swift to enforce secure coding and specify a restricted set of allowed classes during decoding, matching the secure implementation used in the main app:

  unarchiver.requiresSecureCoding = true
  let allowedClasses = [NSDictionary.self, NSURL.self, NTPTile.self]
  guard
    let mostVisitedSites = unarchiver.decodeObject(of: allowedClasses, forKey: NSKeyedArchiveRootObjectKey)
      as? [NSURL: NTPTile]
  else {
    return emptyEntry
  }

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