Chrome · Speech
CVE-2026-18004
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.cc
Patch
From 3691a4c77539e2b7224a8d7771ac73f3ccd9b88d Mon Sep 17 00:00:00 2001
From: Evan Liu <evliu@google.com>
Date: Thu, 25 Jun 2026 14:04:21 -0700
Subject: [PATCH] Enforce Permissions-Policy for SODA in the browser process
The media::mojom::OnDeviceSpeechRecognition interface lacked a
Permissions-Policy check in the browser process, allowing a compromised
renderer to bypass cross-origin iframe restrictions.
This CL adds the missing `IsFeatureEnabled` check and a regression test.
Fixed: 522280805
Change-Id: I84212efbd53b873a2a3ac2b13c991685c900d42a
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7953720
Reviewed-by: Andy Paicu <andypaicu@chromium.org>
Commit-Queue: Evan Liu <evliu@google.com>
Cr-Commit-Position: refs/heads/main@{#1652704}
---
diff --git a/chrome/browser/speech/on_device_speech_recognition_impl.cc b/chrome/browser/speech/on_device_speech_recognition_impl.cc
index 52aed480..719c3c8 100644
--- a/chrome/browser/speech/on_device_speech_recognition_impl.cc
+++ b/chrome/browser/speech/on_device_speech_recognition_impl.cc
@@ -344,6 +344,12 @@
bool OnDeviceSpeechRecognitionImpl::
CanRenderFrameHostUseOnDeviceSpeechRecognition() {
+ if (!render_frame_host().IsFeatureEnabled(
+ network::mojom::PermissionsPolicyFeature::
+ kOnDeviceSpeechRecognition)) {
+ return false;
+ }
+
content::RenderFrameHost* main_frame = render_frame_host().GetMainFrame();
if (main_frame->GetSiteInstance()->GetSecurityPrincipal().IsGuest()) {
return false;
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 903bc19..3c2739a8 100644
--- a/chrome/browser/speech/on_device_speech_recognition_impl_browsertest.cc
+++ b/chrome/browser/speech/on_device_speech_recognition_impl_browsertest.cc
@@ -13,6 +13,7 @@
#include "base/test/mock_callback.h"
#include "base/test/run_until.h"
#include "base/test/scoped_feature_list.h"
+#include "base/test/test_future.h"
#include "chrome/browser/browsing_data/chrome_browsing_data_remover_constants.h"
#include "chrome/browser/content_settings/host_content_settings_map_factory.h"
#include "chrome/browser/profiles/profile.h"
@@ -223,6 +224,44 @@
media::mojom::AvailabilityStatus::kUnavailable));
}
+IN_PROC_BROWSER_TEST_F(OnDeviceSpeechRecognitionImplBrowserTest,
+ BypassPermissionsPolicy) {
+ NavigateToUrl("foo.com");
+
+ content::WebContents* web_contents =
+ browser()->tab_strip_model()->GetActiveWebContents();
+ content::RenderFrameHost* main_frame = web_contents->GetPrimaryMainFrame();
+
+ ASSERT_TRUE(content::ExecJs(
+ main_frame,
+ "new Promise(resolve => {"
+ " let iframe = document.createElement('iframe');"
+ " iframe.src = '/empty.html';"
+ " iframe.allow = \"on-device-speech-recognition 'none'\";"
+ " iframe.onload = resolve;"
+ " document.body.appendChild(iframe);"
+ "});"));
+
+ content::RenderFrameHost* child_frame = content::ChildFrameAt(main_frame, 0);
+ ASSERT_TRUE(child_frame);
+
+ auto* speech_impl =
+ OnDeviceSpeechRecognitionImpl::GetOrCreateForCurrentDocument(child_frame);
+ ASSERT_TRUE(speech_impl);
+
+ base::test::TestFuture<media::mojom::AvailabilityStatus> future;
+
+ // 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,
+ future.GetCallback());
+
+ EXPECT_EQ(future.Get(), media::mojom::AvailabilityStatus::kUnavailable);
+}
+
IN_PROC_BROWSER_TEST_F(OnDeviceSpeechRecognitionImplBrowserTest, Install) {
NavigateToUrl("foo.com");
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 903bc19..3c2739a8 100644
--- a/chrome/browser/speech/on_device_speech_recognition_impl_browsertest.cc
+++ b/chrome/browser/speech/on_device_speech_recognition_impl_browsertest.cc
@@ -13,6 +13,7 @@
#include "base/test/mock_callback.h"
#include "base/test/run_until.h"
#include "base/test/scoped_feature_list.h"
+#include "base/test/test_future.h"
#include "chrome/browser/browsing_data/chrome_browsing_data_remover_constants.h"
#include "chrome/browser/content_settings/host_content_settings_map_factory.h"
#include "chrome/browser/profiles/profile.h"
@@ -223,6 +224,44 @@
media::mojom::AvailabilityStatus::kUnavailable));
}
+IN_PROC_BROWSER_TEST_F(OnDeviceSpeechRecognitionImplBrowserTest,
+ BypassPermissionsPolicy) {
+ NavigateToUrl("foo.com");
+
+ content::WebContents* web_contents =
+ browser()->tab_strip_model()->GetActiveWebContents();
+ content::RenderFrameHost* main_frame = web_contents->GetPrimaryMainFrame();
+
+ ASSERT_TRUE(content::ExecJs(
+ main_frame,
+ "new Promise(resolve => {"
+ " let iframe = document.createElement('iframe');"
+ " iframe.src = '/empty.html';"
+ " iframe.allow = \"on-device-speech-recognition 'none'\";"
+ " iframe.onload = resolve;"
+ " document.body.appendChild(iframe);"
+ "});"));
+
+ content::RenderFrameHost* child_frame = content::ChildFrameAt(main_frame, 0);
+ ASSERT_TRUE(child_frame);
+
+ auto* speech_impl =
+ OnDeviceSpeechRecognitionImpl::GetOrCreateForCurrentDocument(child_frame);
+ ASSERT_TRUE(speech_impl);
+
+ base::test::TestFuture<media::mojom::AvailabilityStatus> future;
+
+ // 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,
+ future.GetCallback());
+
+ EXPECT_EQ(future.Get(), 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