diff --git a/content/browser/devtools/devtools_session.cc b/content/browser/devtools/devtools_session.cc index b473017975c32..ce9a71c9a3037 100644 --- a/content/browser/devtools/devtools_session.cc +++ b/content/browser/devtools/devtools_session.cc @@ -9,6 +9,8 @@ #include "base/containers/flat_set.h" #include "base/functional/bind.h" +#include "base/strings/string_util.h" +#include "mojo/public/cpp/bindings/message.h" #include "base/trace_event/trace_event.h" #include "content/browser/devtools/devtools_manager.h" #include "content/browser/devtools/protocol/devtools_domain_handler.h" @@ -608,6 +610,30 @@ void DevToolsSession::ApplySessionStateUpdates( if (!session_state_cookie_) session_state_cookie_ = blink::mojom::DevToolsSessionState::New(); for (auto& entry : updates->entries) { + // SECURITY: A compromised renderer can supply arbitrary entries here. + // session_state_cookie_ is forwarded verbatim to a *different* renderer + // process on cross-process navigation (see AttachToAgent). Entries under + // the "Page." domain prefix populate InspectorPageAgent fields including + // scripts_to_evaluate_on_load_ (Page.7/), enabled_ (Page.2/), and + // bypass_csp_enabled_ (Page.6/). Allowing a renderer to set these on a + // future cross-origin renderer is a Site-Isolation bypass / UXSS. + // + // The browser is the only entity entitled to control script injection + // into renderers. Reject any attempt by a renderer to write Page-domain + // state. Legitimate Page state is set by the *browser-side* PageHandler + // in response to client commands and persisted there — the renderer + // never needs to round-trip it. + // + // TODO(security): replace this denylist with an allowlist of keys the + // renderer is permitted to persist (e.g. "v8.0/"), or stop forwarding + // renderer-supplied state across processes entirely. + if (base::StartsWith(entry.first, "Page.", + base::CompareCase::SENSITIVE)) { + mojo::ReportBadMessage( + "DevToolsSessionHost: renderer attempted to set Page-domain " + "session state (potential UXSS via cross-process reattach)"); + return; + } if (entry.second.has_value()) session_state_cookie_->entries[entry.first] = std::move(*entry.second); else