Chrome · Speech
CVE-2026-17985
Logic Error in Speech
Overview
Low
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
CanRenderFrameHostUseOnDeviceSpeechRecognitionchrome/browser/speech/on_device_speech_recognition_impl.cc |
modified | |
IN_PROC_BROWSER_TEST_Fchrome/browser/speech/on_device_speech_recognition_impl_browsertest.cc |
modified |
Files Changed
chrome/browser/speech/on_device_speech_recognition_impl.ccchrome/browser/speech/on_device_speech_recognition_impl_browsertest.cccontent/browser/speech/speech_recognition_dispatcher_host.cc
Patch
From c04177aa318139ed030371f07be7c30b479b01ec Mon Sep 17 00:00:00 2001 From: Evan Liu <[email protected]> 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 <[email protected]> Commit-Queue: Evan Liu <[email protected]> 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.
References
On This Page