Low chrome Logic Error 🔧 Commit mapped

Overview

Low
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInsufficient policy enforcement in Speech
DescriptionInsufficient policy enforcement in Speech
ComponentSpeech
Bug ClassLogic Error
Tracker519981430
Fix commitc04177aa3181 (chromium/src) +62/-7
CISA KEVNot listed
CreditedGoogle
Disclosed2026-07-29

Changed Functions

FunctionChangeNotes
CanRenderFrameHostUseOnDeviceSpeechRecognition
chrome/browser/speech/on_device_speech_recognition_impl.cc
modified
IN_PROC_BROWSER_TEST_F
chrome/browser/speech/on_device_speech_recognition_impl_browsertest.cc
modified

Files Changed

  • chrome/browser/speech/on_device_speech_recognition_impl.cc
  • chrome/browser/speech/on_device_speech_recognition_impl_browsertest.cc
  • content/browser/speech/speech_recognition_dispatcher_host.cc
From c04177aa318139ed030371f07be7c30b479b01ec Mon Sep 17 00:00:00 2001
From: Evan Liu <evliu@google.com>
Date: Mon, 08 Jun 2026 17:11:31 -0700
Subject: [PATCH] Fix StoragePartition bypass in OnDeviceSpeechRecognition

This CL fixes a vulnerability where guest views (e.g. <webview>) could
bypass StoragePartition isolation for on-device Web Speech recognition
by using an `about:blank` iframe to spoof a non-HTTP/HTTPS scheme.

The security checks in `SpeechRecognitionDispatcherHost` and
`OnDeviceSpeechRecognitionImpl` now correctly validate the main frame's
URL and StoragePartition rather than the requesting frame's.
Additionally, guest contexts are explicitly blocked via the main frame's
SecurityPrincipal.

Fixed: 519981430
Change-Id: Ie32e8b2d8b56b1410929429dea6c13c359c19944
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7904795
Reviewed-by: Tsuyoshi Horo <horo@chromium.org>
Commit-Queue: Evan Liu <evliu@google.com>
Cr-Commit-Position: refs/heads/main@{#1643545}
---

diff --git a/chrome/browser/speech/on_device_speech_recognition_impl.cc b/chrome/browser/speech/on_device_speech_recognition_impl.cc
index cac2651..bcbb89f8 100644
--- a/chrome/browser/speech/on_device_speech_recognition_impl.cc
+++ b/chrome/browser/speech/on_device_speech_recognition_impl.cc
@@ -18,6 +18,8 @@
 #include "content/public/browser/document_user_data.h"
 #include "content/public/browser/render_frame_host.h"
 #include "content/public/browser/render_process_host.h"
+#include "content/public/browser/security_principal.h"
+#include "content/public/browser/site_instance.h"
 #include "media/base/media_switches.h"
 #include "media/mojo/mojom/speech_recognizer.mojom.h"
 #include "mojo/public/cpp/bindings/receiver_set.h"
@@ -285,9 +287,16 @@
 
 bool OnDeviceSpeechRecognitionImpl::
     CanRenderFrameHostUseOnDeviceSpeechRecognition() {
-  if (render_frame_host().GetStoragePartition() !=
-      render_frame_host().GetBrowserContext()->GetDefaultStoragePartition()) {
-    return !render_frame_host().GetLastCommittedURL().SchemeIsHTTPOrHTTPS();
+  content::RenderFrameHost* main_frame = render_frame_host().GetMainFrame();
+  if (main_frame->GetSiteInstance()->GetSecurityPrincipal().IsGuest()) {
+    return false;
+  }
+
+  // Allow trusted/special app contexts (like Chrome Extensions and Isolated Web
+  // Apps) that use non-HTTP/HTTPS schemes within custom StoragePartitions.
+  if (main_frame->GetStoragePartition() !=
+      main_frame->GetBrowserContext()->GetDefaultStoragePartition()) {
+    return !main_frame->GetLastCommittedURL().SchemeIsHTTPOrHTTPS();
   }
 
   return true;
diff --git a/chrome/browser/speech/on_device_speech_recognition_impl_browsertest.cc b/chrome/browser/speech/on_device_speech_recognition_impl_browsertest.cc
index 97306f3d..ed73190 100644
--- a/chrome/browser/speech/on_device_speech_recognition_impl_browsertest.cc
+++ b/chrome/browser/speech/on_device_speech_recognition_impl_browsertest.cc
@@ -19,8 +19,10 @@
 #include "chrome/test/base/ui_test_utils.h"
 #include "components/soda/soda_installer.h"
 #include "content/public/browser/document_user_data.h"
+#include "content/public/browser/storage_partition_config.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/test/browser_test.h"
+#include "content/public/test/browser_test_utils.h"
 #include "content/public/test/browsing_data_remover_test_util.h"
 #include "media/base/media_switches.h"
 #include "media/mojo/mojom/speech_recognizer.mojom.h"
@@ -169,6 +171,47 @@
                      media::mojom::AvailabilityStatus::kDownloadable));
 }
 
+IN_PROC_BROWSER_TEST_F(OnDeviceSpeechRecognitionImplBrowserTest,
+                       BypassStoragePartitionGuestView) {
+  // Create a custom guest site instance, which uses a non-default storage
+  // partition.
+  scoped_refptr<content::SiteInstance> guest_site_instance =
+      content::SiteInstance::CreateForGuest(
+          browser()->profile(),
+          content::StoragePartitionConfig::Create(
+              browser()->profile(), "my_domain", "my_partition", false));
+
+  content::WebContents::CreateParams params(browser()->profile(),
+                                            guest_site_instance);
+  std::unique_ptr<content::WebContents> guest_contents =
+      content::WebContents::Create(params);
+
+  EXPECT_NE(guest_contents->GetPrimaryMainFrame()->GetStoragePartition(),
+            browser()->profile()->GetDefaultStoragePartition());
+
+  // Navigate to about:blank directly.
+  ASSERT_TRUE(
+      content::NavigateToURL(guest_contents.get(), GURL("about:blank")));
+
+  content::RenderFrameHost* main_frame = guest_contents->GetPrimaryMainFrame();
+  EXPECT_EQ(GURL("about:blank"), main_frame->GetLastCommittedURL());
+
+  auto* speech_impl =
+      OnDeviceSpeechRecognitionImpl::GetOrCreateForCurrentDocument(main_frame);
+  ASSERT_TRUE(speech_impl);
+
+  // The vulnerability allows this to be downloadable.
+  // A correct implementation would return kUnavailable.
+  // We expect it to be kUnavailable to make the test FAIL when the bug is NOT
+  // fixed.
+  speech_impl->Available(
+      {kEnglishLanguageCode}, media::mojom::SpeechRecognitionQuality::kCommand,
+      base::BindOnce(&OnDeviceSpeechRecognitionImplBrowserTest::
+                         OnDeviceWebSpeechAvailableCallbackAndAssertStatus,
+                     base::Unretained(this),
+                     media::mojom::AvailabilityStatus::kUnavailable));
+}
+
 IN_PROC_BROWSER_TEST_F(OnDeviceSpeechRecognitionImplBrowserTest, Install) {
   NavigateToUrl("foo.com");
 
diff --git a/content/browser/speech/speech_recognition_dispatcher_host.cc b/content/browser/speech/speech_recognition_dispatcher_host.cc
index 89e48e8..cafc989 100644
--- a/content/browser/speech/speech_recognition_dispatcher_host.cc
+++ b/content/browser/speech/speech_recognition_dispatcher_host.cc
@@ -19,6 +19,8 @@
 #include "content/public/browser/content_browser_client.h"
 #include "content/public/browser/global_routing_id.h"
 #include "content/public/browser/render_frame_host.h"
+#include "content/public/browser/security_principal.h"
+#include "content/public/browser/site_instance.h"
 #include "content/public/browser/speech_recognition_audio_forwarder_config.h"
 #include "content/public/browser/speech_recognition_manager_delegate.h"
 #include "content/public/browser/speech_recognition_session_config.h"
@@ -168,12 +170,13 @@
   }
 
   content::BrowserContext* browser_context = web_contents->GetBrowserContext();
-  StoragePartition* storage_partition =
-      browser_context->GetStoragePartition(web_contents->GetSiteInstance());
+  content::RenderFrameHost* main_frame = rfh->GetMainFrame();
+  StoragePartition* storage_partition = main_frame->GetStoragePartition();
 
   bool is_valid_storage_context =
-      storage_partition == browser_context->GetDefaultStoragePartition() ||
-      !rfh->GetLastCommittedURL().SchemeIsHTTPOrHTTPS();
+      !main_frame->GetSiteInstance()->GetSecurityPrincipal().IsGuest() &&
+      (storage_partition == browser_context->GetDefaultStoragePartition() ||
+       !main_frame->GetLastCommittedURL().SchemeIsHTTPOrHTTPS());
   bool is_policy_enabled = rfh->IsFeatureEnabled(
       network::mojom::PermissionsPolicyFeature::kOnDeviceSpeechRecognition);
 
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/chrome/browser/speech/on_device_speech_recognition_impl_browsertest.cc b/chrome/browser/speech/on_device_speech_recognition_impl_browsertest.cc
index 97306f3d..ed73190 100644
--- a/chrome/browser/speech/on_device_speech_recognition_impl_browsertest.cc
+++ b/chrome/browser/speech/on_device_speech_recognition_impl_browsertest.cc
@@ -19,8 +19,10 @@
 #include "chrome/test/base/ui_test_utils.h"
 #include "components/soda/soda_installer.h"
 #include "content/public/browser/document_user_data.h"
+#include "content/public/browser/storage_partition_config.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/test/browser_test.h"
+#include "content/public/test/browser_test_utils.h"
 #include "content/public/test/browsing_data_remover_test_util.h"
 #include "media/base/media_switches.h"
 #include "media/mojo/mojom/speech_recognizer.mojom.h"
@@ -169,6 +171,47 @@
                      media::mojom::AvailabilityStatus::kDownloadable));
 }
 
+IN_PROC_BROWSER_TEST_F(OnDeviceSpeechRecognitionImplBrowserTest,
+                       BypassStoragePartitionGuestView) {
+  // Create a custom guest site instance, which uses a non-default storage
+  // partition.
+  scoped_refptr<content::SiteInstance> guest_site_instance =
+      content::SiteInstance::CreateForGuest(
+          browser()->profile(),
+          content::StoragePartitionConfig::Create(
+              browser()->profile(), "my_domain", "my_partition", false));
+
+  content::WebContents::CreateParams params(browser()->profile(),
+                                            guest_site_instance);
+  std::unique_ptr<content::WebContents> guest_contents =
+      content::WebContents::Create(params);
+
+  EXPECT_NE(guest_contents->GetPrimaryMainFrame()->GetStoragePartition(),
+            browser()->profile()->GetDefaultStoragePartition());
+
+  // Navigate to about:blank directly.
+  ASSERT_TRUE(
+      content::NavigateToURL(guest_contents.get(), GURL("about:blank")));
+
+  content::RenderFrameHost* main_frame = guest_contents->GetPrimaryMainFrame();
+  EXPECT_EQ(GURL("about:blank"), main_frame->GetLastCommittedURL());
+
+  auto* speech_impl =
+      OnDeviceSpeechRecognitionImpl::GetOrCreateForCurrentDocument(main_frame);
+  ASSERT_TRUE(speech_impl);
+
+  // The vulnerability allows this to be downloadable.
+  // A correct implementation would return kUnavailable.
+  // We expect it to be kUnavailable to make the test FAIL when the bug is NOT
+  // fixed.
+  speech_impl->Available(
+      {kEnglishLanguageCode}, media::mojom::SpeechRecognitionQuality::kCommand,
+      base::BindOnce(&OnDeviceSpeechRecognitionImplBrowserTest::
+                         OnDeviceWebSpeechAvailableCallbackAndAssertStatus,
+                     base::Unretained(this),
+                     media::mojom::AvailabilityStatus::kUnavailable));
+}
+
 IN_PROC_BROWSER_TEST_F(OnDeviceSpeechRecognitionImplBrowserTest, Install) {
   NavigateToUrl("foo.com");
Loading diff…

Original Bug Report

The reporter's bug is still restricted on the tracker. Chrome de-restricts security bugs ~30–90 days after the fix ships; a later run will backfill it here.