CVE-2026-17837
Overview
Files Changed
content/browser/devtools/devtools_session.cc
Patch
From f8c68496fbf1159de67a5875c3c56635b594311a Mon Sep 17 00:00:00 2001
From: Andrey Kosyakov <caseq@chromium.org>
Date: Fri, 26 Jun 2026 17:29:02 -0700
Subject: [PATCH] Validate enevelope size of CBOR CDP messages from renderer
Fixed: 517978932
Change-Id: Iad8a5ee4ec49e116ce30259603f5084bca5d77a8
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8013188
Commit-Queue: Andrey Kosyakov <caseq@chromium.org>
Reviewed-by: Peter Kvitek <kvitekp@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1653552}
---
diff --git a/content/browser/devtools/devtools_session.cc b/content/browser/devtools/devtools_session.cc
index 46a5220..2ebf82b5 100644
--- a/content/browser/devtools/devtools_session.cc
+++ b/content/browser/devtools/devtools_session.cc
@@ -780,6 +780,18 @@
return false; // Safely terminate renderer on malformed JSON
}
span_message = crdtp::SpanFrom(cbor_message);
+ } else {
+ // Perform top-level validation of CBOR envelope.
+ crdtp::StatusOr<size_t> status_or_outer_size =
+ crdtp::cbor::CheckCBORMessage(span_message);
+ if (!status_or_outer_size.ok()) {
+ DLOG(ERROR) << status_or_outer_size.status().ToASCIIString();
+ return false;
+ }
+ if (status_or_outer_size.value() != message.size()) {
+ DLOG(ERROR) << "Unexpected trailing data in CBOR message from child";
+ return false;
+ }
}
// Do NOT use crdtp::Dispatchable here. It enforces the presence of both
@@ -804,20 +816,19 @@
return false;
}
return true;
- } else {
- if (extracted_session_id.empty() ||
- !crdtp::SpanEquals(crdtp::SpanFrom(expected_session_id),
- extracted_session_id)) {
- DLOG(ERROR) << "Child session expected sessionId: " << expected_session_id
- << ", but got: "
- << (extracted_session_id.empty()
- ? ""
- : std::string(extracted_session_id.begin(),
- extracted_session_id.end()));
- return false;
- }
- return true;
}
+ if (extracted_session_id.empty() ||
+ !crdtp::SpanEquals(crdtp::SpanFrom(expected_session_id),
+ extracted_session_id)) {
+ DLOG(ERROR) << "Child session expected sessionId: " << expected_session_id
+ << ", but got: "
+ << (extracted_session_id.empty()
+ ? ""
+ : std::string(extracted_session_id.begin(),
+ extracted_session_id.end()));
+ return false;
+ }
+ return true;
}
} // namespace content
Original Bug Report
DevTools session ID validation bypass via trailing-bytes CBOR envelope injection
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: DevToolsSession::ValidateMessage fails to ensure that a CBOR-serialized message has no trailing bytes after the first envelope. When running with a remote debugging pipe, a compromised renderer can potentially append extra CBOR envelopes to a benign message. Downstream automation clients will parse these trailing bytes as separate, highly-privileged root-session commands, potentially bypassing security boundaries.
Affected files:
content/browser/devtools/devtools_session.ccthird_party/inspector_protocol/crdtp/cbor.cc
Estimated timestamp from git blame: 2026-05-04
Overview
A potential session ID validation bypass exists in the Chromium DevTools session validation logic (DevToolsSession::ValidateMessage). A compromised renderer can potentially construct a multi-envelope CBOR payload to bypass the RFH_INCONSISTENT_DEVTOOLS_MESSAGE security check. This allows the renderer to inject arbitrary Chrome DevTools Protocol (CDP) commands into the root browser session or sibling sessions via the remote debugging pipe.
Root Cause Analysis
The validation of renderer-supplied messages is performed in DevToolsSession::ValidateMessage (located in content/browser/devtools/devtools_session.cc):
bool DevToolsSession::ValidateMessage(const std::string& expected_session_id,
const bool expected_has_id,
base::span<const uint8_t> message) {
std::vector<uint8_t> cbor_message;
crdtp::span<uint8_t> span_message = crdtp::SpanFrom(message);
if (!crdtp::cbor::IsCBORMessage(span_message)) {
if (!crdtp::json::ConvertJSONToCBOR(span_message, &cbor_message).ok()) {
return false;
}
span_message = crdtp::SpanFrom(cbor_message);
}
crdtp::span<uint8_t> extracted_session_id =
crdtp::cbor::GetString8ValueFromMap(span_message,
crdtp::SpanFrom("sessionId"));
...
}
If the input buffer is recognized as a CBOR message by IsCBORMessage (which only checks the first few bytes of the envelope header), the browser extracts the sessionId using crdtp::cbor::GetString8ValueFromMap on the raw span_message buffer.
However, GetString8ValueFromMap and HasKeyInMap only parse the first outer map inside the first envelope. They return as soon as they process the map’s trailing STOP token and never verify if there are trailing bytes remaining in span_message (i.e., whether the entire buffer was consumed).
When a message successfully validates, the entire original contiguous buffer (including any unvalidated trailing bytes) is forwarded verbatim to the client in DevToolsSession::DispatchProtocolResponseOrNotification:
client->DispatchProtocolMessage(agent_host, message->data);
Potential Attack Mechanism
If Chrome is run with --remote-debugging-pipe=cbor, the bytes flow through PipeWriterCBOR::WriteIntoPipe which writes the entire buffer verbatim into the pipe without modification:
void WriteIntoPipe(std::string message) override {
DCHECK(crdtp::cbor::IsCBORMessage(crdtp::SpanFrom(message)));
WriteBytes(message.data(), message.size());
}
External automation clients (e.g., Puppeteer, Playwright) read sequentially from the pipe. They determine the boundaries of each incoming CBOR frame by parsing the outer_size() declared in each envelope’s header. If the browser writes a buffer containing two back-to-back envelopes, the external client reads and parses them as two separate, subsequent root-session CDP frames.
By appending a second, smuggled envelope with no sessionId (directing it to the root session) after a benign envelope that carries a valid matching sessionId, a compromised renderer can potentially bypass session boundaries and execute arbitrary commands in the root session.
Note: These steps are based on a source-level code analysis. Our tooling does not currently have the capability to run a live POC to confirm the exploit.
Suggested Fix
Ensure that the incoming CBOR message consists of exactly one envelope and contains no trailing bytes. This can be achieved by utilizing crdtp::Dispatchable or parsing the message structure fully using a validator that enforces that the entire buffer is consumed (reporting CBOR_TRAILING_JUNK if there are leftover bytes past the parsed envelope length).
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.