Medium chrome Logic Error 📄 Reporter bug report 🔧 Commit mapped

Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactScript injection in Accessibility
DescriptionScript injection in Accessibility
ComponentAccessibility
Bug ClassLogic Error
Tracker501823385
Fix commiteb38552f18b3 (chromium/src) +4/-2
CISA KEVNot listed
CreditedGoogle
Disclosed2026-06-02

Files Changed

  • chrome/renderer/accessibility/read_anything/read_anything_app_controller.cc
From eb38552f18b37b1806a0e874de905322f5c7fab5 Mon Sep 17 00:00:00 2001
From: Eitan Goldberger <[email protected]>
Date: Tue, 21 Apr 2026 13:10:33 -0700
Subject: [PATCH] Fix UXSS in Reading Mode via ttsEngine API

A malicious extension could achieve UXSS in the read-anything WebUI via
an unsanitized language string passed to the ttsEngine API.

The attached bug has steps to reproduce the vulnerability. I confirmed
that the previous code was vulnerable, and this fix fixes the
vulnerability.

This CL uses base::GetQuotedJSONString to safely escape the language and
status strings in ReadAnythingAppController::OnGetVoicePackInfo.

Bug: 501823385
Change-Id: Ie90b659165148015e403613547df729831a28a4f
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7781841
Commit-Queue: Eitan Goldberger <[email protected]>
Auto-Submit: Eitan Goldberger <[email protected]>
Commit-Queue: Kristi Saney <[email protected]>
Reviewed-by: Kristi Saney <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1618392}
---

diff --git a/chrome/renderer/accessibility/read_anything/read_anything_app_controller.cc b/chrome/renderer/accessibility/read_anything/read_anything_app_controller.cc
index 144dd61..e14e962 100644
--- a/chrome/renderer/accessibility/read_anything/read_anything_app_controller.cc
+++ b/chrome/renderer/accessibility/read_anything/read_anything_app_controller.cc
@@ -17,6 +17,7 @@
 #include "base/check_deref.h"
 #include "base/compiler_specific.h"
 #include "base/containers/fixed_flat_map.h"
+#include "base/json/string_escape.h"
 #include "base/metrics/histogram_functions.h"
 #include "base/metrics/metrics_hashes.h"
 #include "base/notreached.h"
@@ -1932,8 +1933,9 @@
                 voice_pack_info->pack_state->get_installation_state())
           : base::ToString(voice_pack_info->pack_state->get_error_code());
 
-  ExecuteJavaScript("chrome.readingMode.updateVoicePackStatus(\'" +
-                    voice_pack_info->language + "\', \'" + status + "\');");
+  ExecuteJavaScript("chrome.readingMode.updateVoicePackStatus(" +
+                    base::GetQuotedJSONString(voice_pack_info->language) +
+                    ", " + base::GetQuotedJSONString(status) + ");");
 }
 
 void ReadAnythingAppController::SendInstallVoicePackRequest(
Loading diff…

Original Bug Report

reported by [email protected]

UXSS in Reading Mode via unsanitized language string in ttsEngine API

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 without the Chrome Security team.

Overview: A malicious extension with the ttsEngine permission can potentially achieve Universal Cross-Site Scripting (UXSS) in the chrome-untrusted://read-anything WebUI. This occurs due to unsanitized string concatenation in ReadAnythingAppController::OnGetVoicePackInfo when processing a language string provided by the extension API. The injected script can read cross-origin page content and exfiltrate it back to the extension.

Affected files:

  • chrome/renderer/accessibility/read_anything/read_anything_app_controller.cc
  • chrome/browser/ui/webui/side_panel/read_anything/read_anything_untrusted_page_handler.cc
  • chrome/browser/speech/extension_api/tts_engine_extension_api.cc
  • content/browser/speech/tts_controller_impl.cc

Estimated timestamp from git blame: 2026-01-25

Vulnerability Details

A potential Universal Cross-Site Scripting (UXSS) vulnerability exists in the Reading Mode side panel (chrome-untrusted://read-anything), which could allow a malicious extension to escalate its privileges. An extension with the ttsEngine permission can inject arbitrary JavaScript into the Reading Mode WebUI via an unsanitized string concatenation in the C++ renderer code.

The vulnerability is triggered by the chrome.ttsEngine.updateLanguage API. The lang property of this API is not restricted by the extension schema or validated in the browser process before being passed to the Reading Mode UI.

  1. Source: A malicious extension calls chrome.ttsEngine.updateLanguage({lang: PAYLOAD, installStatus: 'installed'}). The lang parameter is an unconstrained string.

  2. Propagation:

    • ExtensionTtsEngineUpdateLanguageFunction::Run in tts_engine_extension_api.cc forwards the lang string without validation to TtsControllerImpl::UpdateLanguageStatus.
    • On non-ChromeOS platforms (Windows, macOS, Linux), ReadAnythingUntrustedPageHandler is registered as a delegate and receives this status update.
    • It wraps the raw lang string in a VoicePackInfo Mojo struct and sends it to the Reading Mode renderer.
  3. Sink: In the renderer process, ReadAnythingAppController::OnGetVoicePackInfo (chrome/renderer/accessibility/read_anything/read_anything_app_controller.cc) performs the following unsanitized concatenation:

    ExecuteJavaScript("chrome.readingMode.updateVoicePackStatus(\'" +
                      voice_pack_info->language + "\', \'" + status + "\');");
    

    By providing a payload like en','x'); [INJECTED JS]; //, an attacker can break out of the string literal and function call, executing arbitrary JavaScript. Because this execution originates from C++ via ExecuteJavaScript, it bypasses the WebUI’s Content Security Policy (CSP).

Impact

By executing script in the Reading Mode WebUI, a malicious extension gains access to the chrome.readingMode Gin bindings. These bindings provide powerful capabilities over the active tab, including:

  • getTextContent and getChildren: To read the distilled accessibility tree of any cross-origin page opened in Reading Mode.
  • onLinkClicked: To dispatch clicks on arbitrary nodes in the active tab.

The injected script can read the cross-origin page content and then exfiltrate it back to the extension by calling chrome.readingMode.sendGetVoicePackInfoRequest(stolen_data). This triggers the ttsEngine.onLanguageStatusRequest event in the extension’s background script, allowing the attacker to receive the stolen data.

This effectively grants an extension with only the ttsEngine permission (which normally only prompts the user for text-to-speech capabilities) the equivalent of activeTab read access for any page the user views in Reading Mode.

Note: This vulnerability appears to be applicable only on non-ChromeOS platforms, as ReadAnythingUntrustedPageHandler uses a different mechanism (LanguagePackManager) on ChromeOS and does not register as a TTS update delegate.

Potential Reproduction Steps

These steps are theoretical as an end-to-end exploit has not been fully verified by our automated tooling, but the code path strongly supports this capability.

  1. Install an extension with the ttsEngine permission and a background service worker.
  2. In the service worker, add a listener for chrome.ttsEngine.onLanguageStatusRequest to receive exfiltrated data.
  3. Open the Reading Mode side panel on any website.
  4. From the extension, call:
    chrome.ttsEngine.updateLanguage({
      lang: "en','x'); var rm=chrome.readingMode; rm.sendGetVoicePackInfoRequest(rm.getTextContent(rm.rootId)); //",
      installStatus: 'installed'
    });
    
  5. The injected script will execute, read the page content via getTextContent, and send it back to the extension using sendGetVoicePackInfoRequest.

Suggested Fix

Refactor ReadAnythingAppController::OnGetVoicePackInfo to avoid evaluating strings as JavaScript. Instead of using ExecuteJavaScript with string concatenation, the renderer should receive data via Mojo and handle it safely. If JavaScript execution is absolutely necessary, the data should be passed as properly encoded arguments (e.g., using base::Value and a function invocation mechanism that doesn’t involve string evaluation) or rigorous sanitization should be applied to the language string before concatenation.

Evaluated with Chrome root at commit: 096fc8fdbfacf2546485756d03f160a3d04fcc9b


Results so far have been promising, but there can be wrong deductions. If this proves to be a false positive, please close as WAI; 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