Low CVSS 4.3 chrome Policy Bypass 🔧 Commit mapped

Overview

Low
Severity
4.3
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInsufficient policy enforcement in Speech
DescriptionInsufficient policy enforcement in Speech
ComponentSpeech
Bug ClassPolicy Bypass
Tracker522280805
Fix commit3691a4c77539 (chromium/src) +45/-0
CWECWE-284
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:L/I:N/A:N
CISA KEVNot listed
CreditedGoogle
Reported2026-06-10 (per vendor advisory)
Fix landed2026-06-25 (public git)
Disclosed2026-07-29
Patch gap34 days — fix public in source before the advisory

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
From 3691a4c77539e2b7224a8d7771ac73f3ccd9b88d Mon Sep 17 00:00:00 2001
From: Evan Liu <[email protected]>
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 <[email protected]>
Commit-Queue: Evan Liu <[email protected]>
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.