CVE-2026-11157
Overview
Files Changed
chrome/renderer/accessibility/read_anything/read_anything_app_controller.cc
Patch
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(
Original Bug Report
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.ccchrome/browser/ui/webui/side_panel/read_anything/read_anything_untrusted_page_handler.ccchrome/browser/speech/extension_api/tts_engine_extension_api.cccontent/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.
-
Source: A malicious extension calls
chrome.ttsEngine.updateLanguage({lang: PAYLOAD, installStatus: 'installed'}). Thelangparameter is an unconstrained string. -
Propagation:
ExtensionTtsEngineUpdateLanguageFunction::Runintts_engine_extension_api.ccforwards thelangstring without validation toTtsControllerImpl::UpdateLanguageStatus.- On non-ChromeOS platforms (Windows, macOS, Linux),
ReadAnythingUntrustedPageHandleris registered as a delegate and receives this status update. - It wraps the raw
langstring in aVoicePackInfoMojo struct and sends it to the Reading Mode renderer.
-
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++ viaExecuteJavaScript, 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:
getTextContentandgetChildren: 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.
- Install an extension with the
ttsEnginepermission and a background service worker. - In the service worker, add a listener for
chrome.ttsEngine.onLanguageStatusRequestto receive exfiltrated data. - Open the Reading Mode side panel on any website.
- From the extension, call:
chrome.ttsEngine.updateLanguage({ lang: "en','x'); var rm=chrome.readingMode; rm.sendGetVoicePackInfoRequest(rm.getTextContent(rm.rootId)); //", installStatus: 'installed' }); - The injected script will execute, read the page content via
getTextContent, and send it back to the extension usingsendGetVoicePackInfoRequest.
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.