CVE-2026-13025
Overview
Files Changed
content/browser/devtools/devtools_session.cc
Patch
From 59621765eb49f2895805f256243ddb1f30297e55 Mon Sep 17 00:00:00 2001 From: Andrey Kosyakov <[email protected]> Date: Mon, 15 Jun 2026 11:11:15 -0700 Subject: [PATCH] Copy devtools messages from renderer when backed by shmem Fixed: 518043569 Change-Id: I9910fd3801de94f6e142d85d9e2c59ca098ddb49 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7935128 Commit-Queue: Andrey Kosyakov <[email protected]> Reviewed-by: Peter Kvitek <[email protected]> Cr-Commit-Position: refs/heads/main@{#1646948} --- diff --git a/content/browser/devtools/devtools_session.cc b/content/browser/devtools/devtools_session.cc index fb29bca..846ae74e 100644 --- a/content/browser/devtools/devtools_session.cc +++ b/content/browser/devtools/devtools_session.cc @@ -573,8 +573,26 @@ blink::mojom::DevToolsMessagePtr message, const std::string& session_id, const bool& is_notification) { - base::span<const uint8_t> message_span = message->data; - if (!ValidateMessage(session_id, /*expected_has_id=*/!is_notification, + // If BigBuffer is backed by shared memory, make a copy so that a compromised + // renderer wouldn't be able to mess with the message as we validate it. + std::vector<uint8_t> message_bytes; + base::span<const uint8_t> message_span; + switch (message->data.storage_type()) { + case mojo_base::BigBuffer::StorageType::kBytes: + message_span = message->data.byte_span(); + break; + case mojo_base::BigBuffer::StorageType::kSharedMemory: + message_bytes.assign(message->data.begin(), message->data.end()); + message_span = message_bytes; + break; + default: + // just keep span empty, this will cause renderer killed for invalid + // message below. + break; + } + + if (message_span.empty() || + !ValidateMessage(session_id, /*expected_has_id=*/!is_notification, message_span)) { if (RenderProcessHost* process_host = agent_host->GetProcessHost()) { bad_message::ReceivedBadMessage(
Original Bug Report
BigBuffer TOCTOU in DevToolsSession allows compromised renderer to spoof CDP sessionId
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. Please see https://chromium.googlesource.com/chromium/src/+/main/docs/security/ai-generated-security-bugs-faq.md for more information.
Overview: A potential Time-of-Check Time-of-Use (TOCTOU) vulnerability exists in DevToolsSession::DispatchProtocolResponseOrNotification where incoming renderer-supplied messages are validated but not copied out of shared memory. A compromised renderer can pass a message backed by Mojo shared memory, satisfy the validation check, and then mutate the payload before the browser forwards it to the DevTools client. This could allow a compromised renderer to bypass validation checks and impersonate other DevTools sessions or the root session.
Affected files:
content/browser/devtools/devtools_session.cc
Estimated timestamp from git blame: 2026-05-04
Summary
A potential Time-of-Check Time-of-Use (TOCTOU) vulnerability exists in DevToolsSession::DispatchProtocolResponseOrNotification that could allow a compromised renderer to bypass security checks and spoof the sessionId of flattened-mode Chrome DevTools Protocol (CDP) messages. This potentially defeats the protections introduced in ValidateMessage.
Root Cause Analysis
The browser-side DevToolsSession implements blink::mojom::DevToolsSessionHost inside content/browser/devtools/devtools_session.cc. When a response or notification message is received from the renderer, the browser validates the message’s sessionId before forwarding it to the DevTools client:
void DevToolsSession::DispatchProtocolResponseOrNotification(
DevToolsAgentHostClient* client,
DevToolsAgentHostImpl* agent_host,
blink::mojom::DevToolsMessagePtr message,
const std::string& session_id,
const bool& is_notification) {
base::span<const uint8_t> message_span = message->data;
if (!ValidateMessage(session_id, /*expected_has_id=*/!is_notification,
message_span)) {
if (RenderProcessHost* process_host = agent_host->GetProcessHost()) {
bad_message::ReceivedBadMessage(
process_host, bad_message::RFH_INCONSISTENT_DEVTOOLS_MESSAGE);
}
return;
}
client->DispatchProtocolMessage(agent_host, message->data);
}
The issue is that message->data is a mojo_base::BigBuffer. When a BigBuffer is deserialized, it can be backed by a shared memory region (kSharedMemory). The implicit conversion of message->data to base::span<const uint8_t> yields message_span, which points directly to the mapped shared memory.
No copy is performed prior to or during the call to ValidateMessage. Because the memory is shared and remains writable by the sending process, a compromised renderer can maintain a writable mapping to this memory buffer and modify its contents after the validation check has passed but before the message is forwarded via client->DispatchProtocolMessage(agent_host, message->data).
As warned in the Mojo documentation (mojo/public/cpp/base/big_buffer.h):
> SECURITY NOTE: When shmem is backing the message, it may be writable in the sending process while being read in the receiving process. If a BigBuffer is received from an untrustworthy process, you should make a copy of the data before processing it to avoid time-of-check time-of-use (TOCTOU) bugs.
Potential Attack Vector & Steps
Note: These are potential steps as we do not have a working proof of concept that has been successfully executed on running code.
An attacker with an already compromised renderer could potentially execute this race condition as follows:
- Ensure a flattened-mode DevTools client is attached to the tab (e.g., standard DevTools, Puppeteer, or an extension using debugger APIs).
- Create a Mojo shared buffer and write a valid JSON/CBOR message payload containing the renderer’s own legitimate
sessionId. - Send the message to the browser via
DispatchProtocolNotificationchoosing the shared memory storage backing. - Concurrently, a background thread in the compromised renderer continuously attempts to overwrite the
sessionIdfield in the shared memory mapping (or remove it entirely to spoof the root session) immediately after the validation begins. - In the browser,
ValidateMessageparses the initial valid session ID and returnstrue. The browser then forwards the modified, post-TOCTOU message payload to the DevTools client. - The DevTools client (e.g., standard frontend, Puppeteer, or debugger extension) processes the spoofed message as if it originated from the targeted session.
Security Impact
This is a potential bypass of the ValidateMessage mitigation. A compromised renderer with a DevTools session attached could spoof other DevTools sessions or impersonate the root session to perform privileged actions, resulting in a sandbox escape or origin bypass.
Suggested Fix
To secure the handler, copy the incoming message->data into a private, browser-owned memory buffer before performing any validation or processing:
void DevToolsSession::DispatchProtocolResponseOrNotification(
DevToolsAgentHostClient* client,
DevToolsAgentHostImpl* agent_host,
blink::mojom::DevToolsMessagePtr message,
const std::string& session_id,
const bool& is_notification) {
// Make a browser-side copy of the message data to prevent TOCTOU modifications
std::vector<uint8_t> safe_copy(message->data.begin(), message->data.end());
if (!ValidateMessage(session_id, /*expected_has_id=*/!is_notification,
safe_copy)) {
if (RenderProcessHost* process_host = agent_host->GetProcessHost()) {
bad_message::ReceivedBadMessage(
process_host, bad_message::RFH_INCONSISTENT_DEVTOOLS_MESSAGE);
}
return;
}
client->DispatchProtocolMessage(agent_host, safe_copy);
}
Evaluated with Chrome root at commit: fb72408a8493c46bc75fae1c70d03daec96b3040
Results so far have been promising, but there can be wrong deductions. Feel free to adjust as follows:
- If you are familiar with the severity guidelines, you may adjust the severity.
- If this is a false positive, and there’s no work to be done, please close as WAI.
- If there is work to do here but not a vulnerability, please change the issue type to Task/Bug/FR.
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.