Medium watch CVSS 3.1 chrome Input Validation 📄 Reporter bug report 🔧 Commit mapped

Overview

Medium
Severity
3.1
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactImproper input validation in DevTools
DescriptionImproper input validation in DevTools
ComponentDevTools
Bug ClassInput Validation
Action tierwatch — logic/policy bug — security-relevant, no memory-unsafety
Computed by PatchGap's deterministic exploitability ruler from the fixing commit. patch-now = RCE-class & attacker-reachable in a default config, or exploited in the wild; watch = real security signal below that bar; noise = no security relevance.
Tracker532182486
Fix commita4da52b13493 (chromium/src) +12/-6
CWECWE-20 (Improper input validation)
CVSS vectorCVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:L/I:N/A:N
CISA KEVNot listed
CreditedGoogle
Reported2026-07-07 (per vendor advisory)
Fix landed2026-07-08 (public git)
Disclosed2026-08-25
Patch gap48 days — fix public in source before the advisory

Background

DevTools session / `sessionId`
the DevTools protocol multiplexes multiple debugging sessions over one channel; the root session is identified by the absence of a sessionId, while child (auto-attached) sessions carry a specific sessionId.
CBOR message validation
the changed code inspects a CBOR-encoded protocol message (span_message) to confirm its sessionId matches the expected_session_id before routing it.
`crdtp::cbor::GetString8ValueFromMap()`
extracts a string value for a map key, but returns an empty span both when the key is absent and when the map contains duplicate entries for that key.
`crdtp::cbor::HasKeyInMap()`
returns whether a key is present in the CBOR map, independent of duplicate handling.

Root Cause Analysis

The changed function (name not shown in the hunk; located in content/browser/devtools/devtools_session.cc) validates an incoming message’s sessionId against the expected one. For a root session (expected_session_id.empty()), it enforced “no sessionId present” solely by checking that extracted_session_id (from GetString8ValueFromMap) was empty. But GetString8ValueFromMap returns an empty span not only when sessionId is absent but also when the map contains duplicate sessionId entries, so a message carrying two sessionId keys yielded an empty extracted_session_id and passed the root-session check even though it actually contained a session identifier. This violated the invariant that a message accepted for the root session truly contains no sessionId.

The fix adds an explicit if (crdtp::cbor::HasKeyInMap(span_message, SpanFrom("sessionId"))) check in the root-session branch that returns false when the key is present at all, closing the duplicate-key bypass; it also reorders the sessionId extraction to occur after the id-presence check (a benign cleanup that keeps the extraction next to its use).

Key insight
Emptiness of GetString8ValueFromMap’s result was wrongly treated as “no sessionId”, but it also signals a duplicate-key parse; the fix adds a direct HasKeyInMap("sessionId") check so a root session rejects any message that actually contains a sessionId, even duplicated.

Attack Path

  1. Craft a duplicate-key message A party able to inject CBOR DevTools messages builds a response/notification containing two sessionId entries in the map.
  2. Defeat the extraction check GetString8ValueFromMap returns an empty span for the duplicated key, so extracted_session_id.empty() is true and the pre-patch root-session branch accepts the message as having no sessionId.
  3. Route as root session The message is validated and processed as belonging to the root session despite carrying a session identifier, bypassing the intended session-scoping check (advisory: improper input validation in DevTools).
  4. Cross-session confusion A message intended for or asserting a child session is admitted into the root session’s handling. (inferred, as downstream routing consequences are not shown in the diff)

Impact Assessment

An attacker who can supply crafted DevTools protocol messages can bypass the root-session sessionId-absence validation via duplicate sessionId keys, causing a message that carries a session identifier to be accepted for the root session in the browser process. The advisory classifies this as improper input validation in DevTools; no exploited-in-the-wild status is stated, and any further escalation from the session-scope confusion is (inferred).

Files Changed

  • content/browser/devtools/devtools_session.cc

Audit Directions

  • Emptiness-as-absence assumptions
    Audit callers of GetString8ValueFromMap and similar extractors that treat an empty result as “key absent”; duplicate or malformed entries can also yield empty, hiding an actually-present key.
  • CBOR duplicate-key handling
    Review protocol/message parsers for whether duplicate map keys are validated or rejected before value-based decisions are made.
  • Session/identity gating on optional fields
    Look for authorization or routing decisions keyed on the presence/absence of an optional field, and verify both an explicit presence check and a value check are used rather than value-only.
From a4da52b13493517489a31a03a7f3fc8744422544 Mon Sep 17 00:00:00 2001
From: Johannes Henkel <johannes@chromium.org>
Date: Wed, 08 Jul 2026 09:01:10 -0700
Subject: [PATCH] Add an explicit check for the absence of sessionId for root session.

Bug: 532182486
Change-Id: Iae09d971b7c2f895f8f7269e26a568b0f4c236e4
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8058008
Commit-Queue: Johannes Henkel <johannes@chromium.org>
Reviewed-by: Alex Rudenko <alexrudenko@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1658807}
---
diff --git a/content/browser/devtools/devtools_session.cc b/content/browser/devtools/devtools_session.cc
index 2ebf82b5..b3f7a67c 100644
--- a/content/browser/devtools/devtools_session.cc
+++ b/content/browser/devtools/devtools_session.cc
@@ -794,18 +794,18 @@
     }
   }
 
-  // Do NOT use crdtp::Dispatchable here. It enforces the presence of both
-  // 'id' and 'method', which are not guaranteed in Responses and Notifications.
-  crdtp::span<uint8_t> extracted_session_id =
-      crdtp::cbor::GetString8ValueFromMap(span_message,
-                                          crdtp::SpanFrom("sessionId"));
-
   if (!expected_has_id &&
       crdtp::cbor::HasKeyInMap(span_message, crdtp::SpanFrom("id"))) {
     DLOG(ERROR) << "Expected no id in the message but received one";
     return false;
   }
 
+  // Do NOT use crdtp::Dispatchable here. It enforces the presence of both
+  // 'id' and 'method', which are not guaranteed in Responses and Notifications.
+  crdtp::span<uint8_t> extracted_session_id =
+      crdtp::cbor::GetString8ValueFromMap(span_message,
+                                          crdtp::SpanFrom("sessionId"));
+
   if (expected_session_id.empty()) {
     if (!extracted_session_id.empty()) {
       DLOG(ERROR) << "Root session expected no sessionId but received one: "
@@ -815,6 +815,12 @@
                                         extracted_session_id.end()));
       return false;
     }
+    // This additional check is necessary since GetString8ValueFromMap returns
+    // the empty span when encountering duplicate entries.
+    if (crdtp::cbor::HasKeyInMap(span_message, crdtp::SpanFrom("sessionId"))) {
+      DLOG(ERROR) << "Root session expected no sessionId but received dupes.";
+      return false;
+    }
     return true;
   }
   if (extracted_session_id.empty() ||
Loading diff…

Original Bug Report

reported by rj...@google.com

Protocol Smuggling in DevTools via Duplicate CBOR Keys

Flapjack has identified a security issue and generated a PoC.

Build variant: Default

Build arguments: v8_enable_sandbox=true v8_enable_memory_corruption_api=true

Shell command: d8 --run-as-security-poc --enable-inspector --omit-quit --disable-in-process-stack-traces

Return code: 134

<details>

<summary>stdout</summary>

</details>

<details>

<summary>stderr</summary>

#
# Fatal error in ../../v8/src/inspector/v8-inspector-session-impl.cc, line 193
# Check failed: ConvertCBORToJSON(SpanFrom(cbor), &json).ok().
#
#
#
#FailureMessage Object: 0x7ffd7ab0fad0
==== C stack trace ===============================

    /home/rjlothian_google_com/chromium_ro/src/out/Default/libv8_libbase.so(v8::base::debug::StackTrace::StackTrace()+0x1e) [0x7e696560c08e]
    /home/rjlothian_google_com/chromium_ro/src/out/Default/libv8_libplatform.so(+0x16c6d) [0x7e6965594c6d]
    /home/rjlothian_google_com/chromium_ro/src/out/Default/libv8_libbase.so(V8_Fatal(char const*, int, char const*, ...)+0x194) [0x7e69655ee074]
    /home/rjlothian_google_com/chromium_ro/src/out/Default/libv8.so(+0x46c8bf2) [0x7e69632c8bf2]
    /home/rjlothian_google_com/chromium_ro/src/out/Default/libv8.so(+0x46c8c59) [0x7e69632c8c59]
    /home/rjlothian_google_com/chromium_ro/src/out/Default/libv8.so(+0x46f3bcc) [0x7e69632f3bcc]
    /home/rjlothian_google_com/chromium_ro/src/out/Default/libv8.so(+0x46f3aa9) [0x7e69632f3aa9]
    /home/rjlothian_google_com/chromium_ro/src/out/Default/libv8.so(+0x4663a06) [0x7e6963263a06]
    /home/rjlothian_google_com/chromium_ro/src/out/Default/libv8.so(+0x46d19de) [0x7e69632d19de]
    /home/rjlothian_google_com/chromium_ro/src/out/Default/libv8.so(+0x465f501) [0x7e696325f501]
    /home/rjlothian_google_com/chromium_ro/src/out/Default/libv8.so(+0x465e263) [0x7e696325e263]
    /home/rjlothian_google_com/chromium_ro/src/out/Default/libv8.so(+0x46f4396) [0x7e69632f4396]
    /home/rjlothian_google_com/chromium_ro/src/out/Default/libv8.so(+0x46c9d78) [0x7e69632c9d78]
    /home/rjlothian_google_com/chromium_ro/src/out/Default/d8(+0xaa288) [0x5f05ebf05288]
    /home/rjlothian_google_com/chromium_ro/src/out/Default/libv8.so(+0x140ef60) [0x7e696000ef60]

</details>

Overview: A logic error in the CRDTP CBOR parser allows a compromised renderer to bypass DevTools session ID validation by using duplicate keys. The browser treats duplicate sessionId keys as absent, while downstream consumers apply a “last-key-wins” policy. This enables the renderer to spoof messages for other multiplexed DevTools sessions.

Affected files:

  • third_party/inspector_protocol/crdtp/cbor.cc
  • content/browser/devtools/devtools_session.cc
  • v8/third_party/inspector_protocol/crdtp/cbor.cc

Estimated timestamp from git blame: 2026-05-18

Root Cause

In third_party/inspector_protocol/crdtp/cbor.cc, the utility function GetString8ValueFromMap is used to quickly extract a string value from a CBOR map. If it encounters the target key more than once, it returns an empty span:

    if (is_key) {
      if (tokenizer.TokenTag() == CBORTokenTag::STRING8 &&
          SpanEquals(tokenizer.GetString8(), string8_key)) {
        if (found_key) {
          return {};  // Duplicate key returns empty span
        }
        found_key = true;
      }
    }

In content/browser/devtools/devtools_session.cc, DevToolsSession::ValidateMessage uses this function to extract the sessionId from incoming protocol messages from the renderer. For the root DevTools session, the expected_session_id is empty. If the extracted session ID is empty, validation passes:

  if (expected_session_id.empty()) {
    if (!extracted_session_id.empty()) {
      return false;
    }
    return true; // Passes if empty span is returned
  }

Impact

An attacker in a compromised renderer (acting as the root DevTools session) can craft a message with duplicate sessionId keys. Because GetString8ValueFromMap returns an empty span upon encountering the duplicate, the browser’s validator incorrectly assumes the message lacks a session ID and accepts it as valid for the root session.

The browser then forwards the unmodified message to the DevTools frontend (or an automation client). These downstream consumers typically parse the message using JSON (JSON.parse()) or other parsers that follow a “last-key-wins” policy. Consequently, the client processes the message using the smuggled sessionId, allowing the compromised renderer to spoof responses and notifications for arbitrary DevTools sessions (such as cross-origin iframes) multiplexed on the same connection. This completely breaks the DevTools session isolation boundary.

Suggested Fix

Modify GetString8ValueFromMap to explicitly signal a parsing error when duplicate keys are encountered, rather than silently returning an empty span (which is indistinguishable from a missing key). This could be achieved by changing its return type to crdtp::StatusOr<span<uint8_t>>, allowing ValidateMessage to fail validation and drop the message if a Status indicating a duplicate key is returned.

Evaluated with Chrome root at commit: e90946bb1b0c143a02180dd070c9ce75d4d0b935


The description of the vuln is LLM-generated and can contain mistakes. Your feedback is appreciated, and will help us make improvement over time. The PoC was run in a VM and it seemed to be legit - if not, let us know and we can strengthen our checker. Please see https://chromium.googlesource.com/chromium/src/+/main/docs/security/ai-generated-security-bugs-faq.md for more information.

View on issue tracker