Chrome · DevTools › DevTools
CVE-2026-113013
Memory Corruption in DevTools (DevTools)
Overview
Low
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
buffer_usage_reporting_interval_content/browser/devtools/protocol/tracing_handler.cc |
modified | |
ifcontent/browser/devtools/protocol/tracing_handler.cc |
modified | |
ifcontent/browser/devtools/protocol/devtools_protocol_browsertest.cc |
modified |
Files Changed
content/browser/devtools/protocol/tracing_handler_unittest.cccontent/browser/devtools/browser_devtools_agent_host.cccontent/browser/devtools/protocol/tracing_handler.cccontent/browser/devtools/devtools_session.hcontent/browser/devtools/protocol/devtools_protocol_browsertest.cccontent/browser/devtools/protocol/tracing_handler.h
Patch
From d76b46e67426616a8da0e61ccf3c016e6fd0de93 Mon Sep 17 00:00:00 2001
From: Nikolay Vitkov <nvitkov@chromium.org>
Date: Mon, 28 Sep 2026 08:24:59 -0700
Subject: [PATCH] Reland "[DevTools] Restrict browser-process tracing and memory dumps to trusted clients"
This is a reland of commit 68cfb6437ec8add0f36b9122d1047f3fe43454ce
Original change's description:
> [DevTools] Restrict browser-process tracing and memory dumps to trusted clients
>
> Previously, untrusted DevTools clients (such as extensions attached to a
> single tab via `chrome.debugger`) could use the `Tracing` domain to
> collect trace events from the browser and GPU processes or trigger global
> memory dumps, potentially leaking cross-tab navigation URLs and
> cross-process memory data.
> This CL restricts `TracingHandler` when `!is_trusted_`:
> - Excludes the browser process and GPU process PIDs from the Perfetto
> process filter, and prevents the browser PID from being dynamically
> added via `AddProcessToFilter()`.
> - Clears any client-supplied `producer_name_filter` and
> `producer_name_regex_filter` on Chrome data sources so untrusted
> `perfettoConfig` payloads cannot bypass the process filter (using
> `kNullProcessId` when `pids` is empty so Perfetto does not default to
> matching all producers).
> - Strips `org.chromium.memory_instrumentation` and
> `org.chromium.native_heap_profiler` data sources from the trace config
> and rejects `Tracing.requestMemoryDump` calls with `"Not allowed"`.
> - Skips browser-side timeline frame/navigation trace event emission
> (`EmitFrameTree`, `WillInitiatePrerender`, `ReadyToCommitNavigation`,
> `FrameDeleted`) for untrusted sessions.
>
> Bug: 563814519, 514508203
> Change-Id: Id1d1a14522d6248046b6c150191da102407a4779
> Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8425446
> Commit-Queue: Nikolay Vitkov <nvitkov@chromium.org>
> Reviewed-by: Danil Somsikov <dsv@chromium.org>
> Reviewed-by: Eric Seckler <eseckler@chromium.org>
> Cr-Commit-Position: refs/heads/main@{#1704813}
Include-Ci-Only-Tests: chromium.fuchsia:fuchsia-x64-cast-receiver-rel|content_browsertests
Bug: 563814519, 514508203
Change-Id: I514bba64577b2e42a0210011d17871cfcddff860
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8458647
Auto-Submit: Nikolay Vitkov <nvitkov@chromium.org>
Reviewed-by: Danil Somsikov <dsv@chromium.org>
Commit-Queue: Danil Somsikov <dsv@chromium.org>
Reviewed-by: Eric Seckler <eseckler@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1706139}
---
diff --git a/content/browser/devtools/browser_devtools_agent_host.cc b/content/browser/devtools/browser_devtools_agent_host.cc
index 5587e0f..639deec4 100644
--- a/content/browser/devtools/browser_devtools_agent_host.cc
+++ b/content/browser/devtools/browser_devtools_agent_host.cc
@@ -257,7 +257,8 @@
}
session->CreateAndAddHandler<protocol::TracingHandler>(
this, GetIOContext(), /* root_session */ nullptr,
- session->GetClient()->IsTrusted());
+ // Browser-target sessions are always trusted (enforced above).
+ /*is_trusted=*/true);
#if BUILDFLAG(CLANG_PROFILING_INSIDE_SANDBOX) && BUILDFLAG(CLANG_PGO_PROFILING)
session->CreateAndAddHandler<protocol::NativeProfilingHandler>();
diff --git a/content/browser/devtools/devtools_session.h b/content/browser/devtools/devtools_session.h
index 0e95a1d..5235078 100644
--- a/content/browser/devtools/devtools_session.h
+++ b/content/browser/devtools/devtools_session.h
@@ -87,8 +87,8 @@
};
// For root sessions (see also private constructor for children).
- DevToolsSession(DevToolsAgentHostClient* client, Mode mode);
- ~DevToolsSession() override;
+ CONTENT_EXPORT DevToolsSession(DevToolsAgentHostClient* client, Mode mode);
+ CONTENT_EXPORT ~DevToolsSession() override;
void SetAgentHost(DevToolsAgentHostImpl* agent_host);
void SetRuntimeResumeCallback(base::OnceClosure runtime_resume);
diff --git a/content/browser/devtools/protocol/tracing_handler.cc b/content/browser/devtools/protocol/tracing_handler.cc
index 52ca4b8..da05b1d9 100644
--- a/content/browser/devtools/protocol/tracing_handler.cc
+++ b/content/browser/devtools/protocol/tracing_handler.cc
@@ -253,6 +253,8 @@
return {};
}
+const char kNeverMatchingProducerName[] = "org.chromium-0";
+
bool IsChromeDataSource(const std::string& data_source_name) {
return base::StartsWith(data_source_name, "org.chromium.") ||
data_source_name == "track_event";
@@ -571,6 +573,7 @@
return_as_stream_(false),
gzip_compression_(false),
buffer_usage_reporting_interval_(0) {
+ CHECK(is_trusted_ || session_for_process_filter_);
video_consumer_ = std::make_unique<DevToolsVideoConsumer>(base::BindRepeating(
&TracingHandler::OnFrameFromVideoConsumer, base::Unretained(this)));
}
@@ -789,6 +792,16 @@
proto_format);
}
+ if (!is_trusted_) {
+ FilterUntrustedDataSources(trace_config);
+ if (!trace_config.data_sources_size()) {
+ callback->sendFailure(Response::InvalidParams(
+ "Supplied perfettoConfig doesn't have any allowed data sources "
+ "specified"));
+ return;
+ }
+ }
+
std::optional<perfetto::BackendType> backend = GetBackendTypeFromParameters(
tracing_backend.value_or(Tracing::TracingBackendEnum::Auto),
trace_config);
@@ -857,21 +870,34 @@
screenshot_max_count_ = resolved_screenshot_max_count;
if (session_for_process_filter_) {
+ std::erase_if(*trace_config_.mutable_data_sources(),
+ [](const perfetto::TraceConfig::DataSource& data_source) {
+ return !IsChromeDataSource(data_source.config().name());
+ });
process_set_monitor_ = TracingProcessSetMonitor::Start(
*session_for_process_filter_,
base::BindRepeating(&TracingHandler::AddProcessToFilter,
base::Unretained(this)));
std::unordered_set<base::ProcessId> pids = process_set_monitor_->GetPids();
- base::ProcessId browser_pid = base::Process::Current().Pid();
- pids.insert(browser_pid);
- if (auto* gpu_process_host =
- GpuProcessHost::Get(GPU_PROCESS_KIND_SANDBOXED,
- /* force_create */ false)) {
- base::ProcessId gpu_pid = gpu_process_host->process_id();
- if (gpu_pid != base::kNullProcessId) {
- pids.insert(gpu_pid);
+ if (is_trusted_) {
+ base::ProcessId browser_pid = base::Process::Current().Pid();
+ pids.insert(browser_pid);
+ if (auto* gpu_process_host =
+ GpuProcessHost::Get(GPU_PROCESS_KIND_SANDBOXED,
+ /* force_create */ false)) {
+ base::ProcessId gpu_pid = gpu_process_host->process_id();
+ if (gpu_pid != base::kNullProcessId) {
+ pids.insert(gpu_pid);
+ }
}
+ } else {
+ pids.erase(base::Process::Current().Pid());
+ }
+
+ for (auto& data_source : *trace_config_.mutable_data_sources()) {
+ data_source.clear_producer_name_regex_filter();
+ data_source.clear_producer_name_filter();
}
AddPidsToProcessFilter(pids, trace_config_);
}
@@ -900,6 +926,9 @@
void TracingHandler::AddProcessToFilter(base::ProcessId pid) {
CHECK(did_initiate_recording_);
CHECK(session_);
+ if (!is_trusted_ && pid == base::Process::Current().Pid()) {
+ return;
+ }
AddPidsToProcessFilter({pid}, trace_config_);
session_->ChangeTraceConfig(trace_config_);
}
@@ -1045,6 +1074,10 @@
std::optional<bool> deterministic,
std::optional<std::string> level_of_detail,
std::unique_ptr<RequestMemoryDumpCallback> callback) {
+ if (!is_trusted_) {
+ callback->sendFailure(Response::ServerError("Not allowed"));
+ return;
+ }
if (!IsTracing()) {
callback->sendFailure(Response::ServerError("Tracing is not started"));
return;
@@ -1193,8 +1226,9 @@
void TracingHandler::ReadyToCommitNavigation(
NavigationRequest* navigation_request) {
- if (!did_initiate_recording_)
+ if (!did_initiate_recording_) {
return;
+ }
auto data = std::make_unique<base::trace_event::TracedValue>();
RenderFrameHostImpl* frame_host = navigation_request->GetRenderFrameHost();
FillFrameData(data.get(), frame_host, navigation_request->GetURL());
@@ -1208,8 +1242,9 @@
}
void TracingHandler::FrameDeleted(FrameTreeNodeId frame_tree_node_id) {
- if (!did_initiate_recording_)
+ if (!did_initiate_recording_) {
return;
+ }
FrameTreeNode* node = FrameTreeNode::GloballyFindByID(frame_tree_node_id);
if (!node->current_frame_host()) {
@@ -1225,16 +1260,21 @@
}
// static
+void TracingHandler::FilterUntrustedDataSources(
+ perfetto::TraceConfig& trace_config) {
+ std::erase_if(
+ *trace_config.mutable_data_sources(),
+ [](const perfetto::TraceConfig::DataSource& data_source) {
+ return data_source.config().name() != tracing::kMetaData2SourceName &&
+ data_source.config().name() != kTrackEventDataSourceName;
+ });
+}
+
+// static
void TracingHandler::AddPidsToProcessFilter(
const std::unordered_set<base::ProcessId>& included_process_ids,
perfetto::TraceConfig& trace_config) {
for (auto& data_source : *(trace_config.mutable_data_sources())) {
- auto* source_config = data_source.mutable_config();
- if (IsChromeDataSource(source_config->name())) {
- if (data_source.producer_name_regex_filter_size() > 0) {
- data_source.clear_producer_name_regex_filter();
- data_source.clear_producer_name_filter();
- }
std::unordered_set<std::string> existing_filters(
data_source.producer_name_filter().begin(),
data_source.producer_name_filter().end());
@@ -1246,7 +1286,14 @@
*data_source.add_producer_name_filter() = std::move(new_filter);
}
}
- }
+
+ if (data_source.producer_name_filter_size() == 0) {
+ // An empty `producer_name_filter` in Perfetto matches all
+ // producers. Insert a never-matching producer name so the filter
+ // remains non-empty and matches no processes until a valid process
+ // is added.
+ *data_source.add_producer_name_filter() = kNeverMatchingProducerName;
+ }
}
}
diff --git a/content/browser/devtools/protocol/tracing_handler.h b/content/browser/devtools/protocol/tracing_handler.h
index 5a793bb..ae21dc94 100644
--- a/content/browser/devtools/protocol/tracing_handler.h
+++ b/content/browser/devtools/protocol/tracing_handler.h
@@ -146,6 +146,8 @@
CONTENT_EXPORT static void AddPidsToProcessFilter(
const std::unordered_set<base::ProcessId>& included_process_ids,
perfetto::TraceConfig& trace_config);
+ CONTENT_EXPORT static void FilterUntrustedDataSources(
+ perfetto::TraceConfig& trace_config);
// Resolves the screenshot-capture parameters supplied to `Tracing.start`,
// applying defaults when unset and validating that the combined memory
// budget (`size * size * 4 * count`) does not exceed the per-session cap.
@@ -201,6 +203,7 @@
FRIEND_TEST_ALL_PREFIXES(TracingHandlerTest, ProcessFilterClearsRegex);
FRIEND_TEST_ALL_PREFIXES(TracingHandlerTest, ProcessFilterAppendsPids);
FRIEND_TEST_ALL_PREFIXES(TracingHandlerTest, ResolveScreenshotParams);
+ FRIEND_TEST_ALL_PREFIXES(TracingHandlerTest, FilterUntrustedDataSources);
};
} // namespace protocol
diff --git a/content/browser/devtools/protocol/devtools_protocol_browsertest.cc b/content/browser/devtools/protocol/devtools_protocol_browsertest.cc
index d3729e6..7a957bb 100644
--- a/content/browser/devtools/protocol/devtools_protocol_browsertest.cc
+++ b/content/browser/devtools/protocol/devtools_protocol_browsertest.cc
@@ -101,6 +101,7 @@
#include "net/test/test_doh_server.h"
#include "services/network/public/cpp/features.h"
#include "services/tracing/public/cpp/perfetto/perfetto_config.h"
+#include "services/tracing/public/cpp/perfetto/perfetto_data_source_names.h"
#include "services/tracing/public/cpp/perfetto/perfetto_traced_process.h"
#include "services/tracing/public/cpp/tracing_features.h"
#include "testing/gmock/include/gmock/gmock.h"
@@ -4708,6 +4709,189 @@
WaitForNotification("Tracing.tracingComplete", true);
}
+IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest,
+ TracingMemoryDumpRequiresTrustedClient) {
+ NavigateToURLBlockUntilNavigationsComplete(shell(), GURL("about:blank"), 1);
+
+ SetIsTrusted(false);
+ Attach();
+
+ // Starting with a perfettoConfig containing only memory dump data sources
+ // should fail for an untrusted client because the disallowed data sources
+ // are stripped.
+ perfetto::TraceConfig memory_only_perfetto_config;
+ memory_only_perfetto_config.add_buffers()->set_size_kb(1024);
+ memory_only_perfetto_config.add_data_sources()->mutable_config()->set_name(
+ tracing::kMemoryInstrumentationDataSourceName);
+ memory_only_perfetto_config.add_data_sources()->mutable_config()->set_name(
+ tracing::kNativeHeapProfilerSourceName);
+ base::DictValue perfetto_start_params;
+ perfetto_start_params.Set(
+ "perfettoConfig",
+ base::Base64Encode(memory_only_perfetto_config.SerializeAsString()));
+ perfetto_start_params.Set("transferMode", "ReturnAsStream");
+ EXPECT_FALSE(
+ SendCommandSync("Tracing.start", std::move(perfetto_start_params)));
+ EXPECT_THAT(
+ error()->FindInt("code"),
+ testing::Optional(static_cast<int>(crdtp::DispatchCode::INVALID_PARAMS)));
+
+ base::DictValue trace_config;
+ base::ListValue categories;
+ categories.Append("disabled-by-default-memory-infra");
+ trace_config.Set("includedCategories", std::move(categories));
+ base::DictValue start_params;
+ start_params.Set("traceConfig", std::move(trace_config));
+ start_params.Set("transferMode", "ReturnAsStream");
+ EXPECT_TRUE(SendCommandSync("Tracing.start", start_params.Clone()));
+
+ ASSERT_FALSE(SendCommandSync("Tracing.requestMemoryDump"));
+ EXPECT_THAT(
+ error()->FindInt("code"),
+ testing::Optional(static_cast<int>(crdtp::DispatchCode::SERVER_ERROR)));
+ EXPECT_EQ(*error()->FindString("message"), "Not allowed");
+
+ EXPECT_TRUE(SendCommandSync("Tracing.end"));
+ WaitForNotification("Tracing.tracingComplete", true);
+
+ Detach();
+ SetIsTrusted(true);
+ Attach();
+
+ EXPECT_TRUE(SendCommandSync("Tracing.start", std::move(start_params)));
+ // Request a "background" dump rather than the default "detailed" dump,
+ // because "detailed" dumps require OSMetrics::GetProcessMemoryMaps(), which
+ // is not implemented on Fuchsia (https://crbug.com/40720107).
+ base::DictValue dump_params;
+ dump_params.Set("levelOfDetail", "background");
+ const base::DictValue* dump_result =
+ SendCommandSync("Tracing.requestMemoryDump", std::move(dump_params));
+ ASSERT_TRUE(dump_result);
+ EXPECT_THAT(dump_result->FindBool("success"), testing::Optional(true));
+ EXPECT_TRUE(SendCommandSync("Tracing.end"));
+ WaitForNotification("Tracing.tracingComplete", true);
+}
+
+IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest,
+ TracingBrowserProcessRequiresTrustedClient) {
+ content::SetupCrossSiteRedirector(embedded_test_server());
+ ASSERT_TRUE(embedded_test_server()->Start());
+
+ GURL url_a = embedded_test_server()->GetURL("a.com", "/title1.html");
+ GURL url_b =
+ embedded_test_server()->GetURL("b.com", "/title1.html?secret_tab_b");
+
+ NavigateToURLBlockUntilNavigationsComplete(shell(), url_a, 1);
+
+ auto run_trace_and_read_json = [&](bool is_trusted, const GURL& url_b,
+ Shell** out_shell_b) -> std::string {
+ Detach();
+ SetIsTrusted(is_trusted);
+ Attach();
+
+ base::trace_event::TraceConfig chrome_config(
+ "navigation,blink.user_timing," TRACE_DISABLED_BY_DEFAULT(
+ "devtools.timeline"),
+ "");
+ perfetto::TraceConfig perfetto_config =
+ tracing::GetDefaultPerfettoConfig(chrome_config,
+ /*privacy_filtering_enabled=*/false,
+ /*convert_to_legacy_json=*/true);
+ // Attempt to explicitly request the browser process PID in
+ // producer_name_filter to verify that untrusted sessions cannot bypass
+ // the process filter.
+ std::string browser_producer =
+ base::StrCat({tracing::kPerfettoProducerNamePrefix,
+ base::NumberToString(static_cast<uint32_t>(
+ base::Process::Current().Pid()))});
+ for (auto& ds : *perfetto_config.mutable_data_sources()) {
+ *ds.add_producer_name_filter() = browser_producer;
+ ds.add_producer_name_regex_filter(".*");
+ }
+
+ base::DictValue params;
+ params.Set("perfettoConfig",
+ base::Base64Encode(perfetto_config.SerializeAsString()));
+ params.Set("transferMode", "ReturnAsStream");
+
+ EXPECT_TRUE(SendCommandSync("Tracing.start", std::move(params)));
+
+ // Create a new renderer mid-session to trigger AddProcessToFilter
+ Shell* shell_b = CreateBrowser();
+ if (out_shell_b) {
+ *out_shell_b = shell_b;
+ }
+
+ NavigateToURLBlockUntilNavigationsComplete(shell(), url_a, 1);
+ NavigateToURLBlockUntilNavigationsComplete(shell_b, url_b, 1);
+ EXPECT_TRUE(content::ExecJs(shell(), "performance.mark('mark_a');"));
+ EXPECT_TRUE(content::ExecJs(shell_b, "performance.mark('mark_b');"));
... (file diff truncated)
Loading diff…
Regression Test / PoC
shipped with the fix
diff --git a/content/browser/devtools/protocol/devtools_protocol_browsertest.cc b/content/browser/devtools/protocol/devtools_protocol_browsertest.cc
index d3729e6..7a957bb 100644
--- a/content/browser/devtools/protocol/devtools_protocol_browsertest.cc
+++ b/content/browser/devtools/protocol/devtools_protocol_browsertest.cc
@@ -101,6 +101,7 @@
#include "net/test/test_doh_server.h"
#include "services/network/public/cpp/features.h"
#include "services/tracing/public/cpp/perfetto/perfetto_config.h"
+#include "services/tracing/public/cpp/perfetto/perfetto_data_source_names.h"
#include "services/tracing/public/cpp/perfetto/perfetto_traced_process.h"
#include "services/tracing/public/cpp/tracing_features.h"
#include "testing/gmock/include/gmock/gmock.h"
@@ -4708,6 +4709,189 @@
WaitForNotification("Tracing.tracingComplete", true);
}
+IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest,
+ TracingMemoryDumpRequiresTrustedClient) {
+ NavigateToURLBlockUntilNavigationsComplete(shell(), GURL("about:blank"), 1);
+
+ SetIsTrusted(false);
+ Attach();
+
+ // Starting with a perfettoConfig containing only memory dump data sources
+ // should fail for an untrusted client because the disallowed data sources
+ // are stripped.
+ perfetto::TraceConfig memory_only_perfetto_config;
+ memory_only_perfetto_config.add_buffers()->set_size_kb(1024);
+ memory_only_perfetto_config.add_data_sources()->mutable_config()->set_name(
+ tracing::kMemoryInstrumentationDataSourceName);
+ memory_only_perfetto_config.add_data_sources()->mutable_config()->set_name(
+ tracing::kNativeHeapProfilerSourceName);
+ base::DictValue perfetto_start_params;
+ perfetto_start_params.Set(
+ "perfettoConfig",
+ base::Base64Encode(memory_only_perfetto_config.SerializeAsString()));
+ perfetto_start_params.Set("transferMode", "ReturnAsStream");
+ EXPECT_FALSE(
+ SendCommandSync("Tracing.start", std::move(perfetto_start_params)));
+ EXPECT_THAT(
+ error()->FindInt("code"),
+ testing::Optional(static_cast<int>(crdtp::DispatchCode::INVALID_PARAMS)));
+
+ base::DictValue trace_config;
+ base::ListValue categories;
+ categories.Append("disabled-by-default-memory-infra");
+ trace_config.Set("includedCategories", std::move(categories));
+ base::DictValue start_params;
+ start_params.Set("traceConfig", std::move(trace_config));
+ start_params.Set("transferMode", "ReturnAsStream");
+ EXPECT_TRUE(SendCommandSync("Tracing.start", start_params.Clone()));
+
+ ASSERT_FALSE(SendCommandSync("Tracing.requestMemoryDump"));
+ EXPECT_THAT(
+ error()->FindInt("code"),
+ testing::Optional(static_cast<int>(crdtp::DispatchCode::SERVER_ERROR)));
+ EXPECT_EQ(*error()->FindString("message"), "Not allowed");
+
+ EXPECT_TRUE(SendCommandSync("Tracing.end"));
+ WaitForNotification("Tracing.tracingComplete", true);
+
+ Detach();
+ SetIsTrusted(true);
+ Attach();
+
+ EXPECT_TRUE(SendCommandSync("Tracing.start", std::move(start_params)));
+ // Request a "background" dump rather than the default "detailed" dump,
+ // because "detailed" dumps require OSMetrics::GetProcessMemoryMaps(), which
+ // is not implemented on Fuchsia (https://crbug.com/40720107).
+ base::DictValue dump_params;
+ dump_params.Set("levelOfDetail", "background");
+ const base::DictValue* dump_result =
+ SendCommandSync("Tracing.requestMemoryDump", std::move(dump_params));
+ ASSERT_TRUE(dump_result);
+ EXPECT_THAT(dump_result->FindBool("success"), testing::Optional(true));
+ EXPECT_TRUE(SendCommandSync("Tracing.end"));
+ WaitForNotification("Tracing.tracingComplete", true);
+}
+
+IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest,
+ TracingBrowserProcessRequiresTrustedClient) {
+ content::SetupCrossSiteRedirector(embedded_test_server());
+ ASSERT_TRUE(embedded_test_server()->Start());
+
+ GURL url_a = embedded_test_server()->GetURL("a.com", "/title1.html");
+ GURL url_b =
+ embedded_test_server()->GetURL("b.com", "/title1.html?secret_tab_b");
+
+ NavigateToURLBlockUntilNavigationsComplete(shell(), url_a, 1);
+
+ auto run_trace_and_read_json = [&](bool is_trusted, const GURL& url_b,
+ Shell** out_shell_b) -> std::string {
+ Detach();
+ SetIsTrusted(is_trusted);
+ Attach();
+
+ base::trace_event::TraceConfig chrome_config(
+ "navigation,blink.user_timing," TRACE_DISABLED_BY_DEFAULT(
+ "devtools.timeline"),
+ "");
+ perfetto::TraceConfig perfetto_config =
+ tracing::GetDefaultPerfettoConfig(chrome_config,
+ /*privacy_filtering_enabled=*/false,
+ /*convert_to_legacy_json=*/true);
+ // Attempt to explicitly request the browser process PID in
+ // producer_name_filter to verify that untrusted sessions cannot bypass
+ // the process filter.
+ std::string browser_producer =
+ base::StrCat({tracing::kPerfettoProducerNamePrefix,
+ base::NumberToString(static_cast<uint32_t>(
+ base::Process::Current().Pid()))});
+ for (auto& ds : *perfetto_config.mutable_data_sources()) {
+ *ds.add_producer_name_filter() = browser_producer;
+ ds.add_producer_name_regex_filter(".*");
+ }
+
+ base::DictValue params;
+ params.Set("perfettoConfig",
+ base::Base64Encode(perfetto_config.SerializeAsString()));
+ params.Set("transferMode", "ReturnAsStream");
+
+ EXPECT_TRUE(SendCommandSync("Tracing.start", std::move(params)));
+
+ // Create a new renderer mid-session to trigger AddProcessToFilter
+ Shell* shell_b = CreateBrowser();
+ if (out_shell_b) {
+ *out_shell_b = shell_b;
+ }
+
+ NavigateToURLBlockUntilNavigationsComplete(shell(), url_a, 1);
+ NavigateToURLBlockUntilNavigationsComplete(shell_b, url_b, 1);
+ EXPECT_TRUE(content::ExecJs(shell(), "performance.mark('mark_a');"));
+ EXPECT_TRUE(content::ExecJs(shell_b, "performance.mark('mark_b');"));
+
+ EXPECT_TRUE(SendCommandSync("Tracing.end"));
+
+ base::DictValue complete_notification =
+ WaitForNotification("Tracing.tracingComplete", true);
+ const std::string* stream_handle_ptr =
+ complete_notification.FindString("stream");
+ EXPECT_TRUE(stream_handle_ptr);
+ std::string stream_handle = stream_handle_ptr ? *stream_handle_ptr : "";
+
+ std::string trace_json;
+ bool eof = false;
+ while (!eof) {
+ base::DictValue read_params;
+ read_params.Set("handle", stream_handle);
+ const base::DictValue* response =
+ SendCommandSync("IO.read", std::move(read_params));
+ EXPECT_TRUE(response);
+ if (!response) {
+ break;
+ }
+ const std::string* data = response->FindString("data");
+ EXPECT_TRUE(data);
+ if (data) {
+ if (response->FindBool("base64Encoded").value_or(false)) {
+ std::string decoded;
+ EXPECT_TRUE(base::Base64Decode(*data, &decoded));
+ trace_json += decoded;
+ } else {
+ trace_json += *data;
+ }
+ }
+ eof = response->FindBool("eof").value_or(false);
+ }
+ return trace_json;
+ };
+
+ Shell* shell_b_untrusted = nullptr;
+ std::string untrusted_trace =
+ run_trace_and_read_json(/*is_trusted=*/false, url_b, &shell_b_untrusted);
+ EXPECT_THAT(untrusted_trace, testing::HasSubstr("mark_a"));
+ EXPECT_THAT(untrusted_trace, testing::Not(testing::HasSubstr("mark_b")));
+ EXPECT_THAT(untrusted_trace,
+ testing::Not(testing::HasSubstr("secret_tab_b")));
+ EXPECT_THAT(untrusted_trace,
+ testing::Not(testing::HasSubstr("TracingStartedInBrowser")));
+ EXPECT_THAT(untrusted_trace,
+ testing::Not(testing::HasSubstr("FrameCommittedInBrowser")));
+ if (shell_b_untrusted) {
+ shell_b_untrusted->Close();
+ }
+
+ GURL url_b2 =
+ embedded_test_server()->GetURL("b.com", "/title2.html?secret_tab_b2");
+ Shell* shell_b_trusted = nullptr;
+ std::string trusted_trace =
+ run_trace_and_read_json(/*is_trusted=*/true, url_b2, &shell_b_trusted);
+ EXPECT_THAT(trusted_trace, testing::HasSubstr("mark_a"));
+ EXPECT_THAT(trusted_trace, testing::HasSubstr("secret_tab_b2"));
+ EXPECT_THAT(trusted_trace, testing::HasSubstr("TracingStartedInBrowser"));
+ EXPECT_THAT(trusted_trace, testing::HasSubstr("FrameCommittedInBrowser"));
+ if (shell_b_trusted) {
+ shell_b_trusted->Close();
+ }
+}
+
IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest, TracingPerfettoSiteIsolation) {
content::SetupCrossSiteRedirector(embedded_test_server());
ASSERT_TRUE(embedded_test_server()->Start());
@@ -4841,8 +5025,9 @@
"System backend is not allowed for the current client");
}
-IN_PROC_BROWSER_TEST_F(DevToolsProtocolTest,
- TracingAutoBackendNonChromeSourceRequiresTrustedClient) {
+IN_PROC_BROWSER_TEST_F(
+ DevToolsProtocolTest,
+ TracingUntrustedClientCannotProvideNonAllowlistedDataSource) {
perfetto::TraceConfig perfetto_config;
perfetto_config.add_buffers()->set_size_kb(1024);
perfetto_config.add_data_sources()->mutable_config()->set_name(
@@ -4862,9 +5047,10 @@
EXPECT_FALSE(SendCommandSync("Tracing.start", std::move(params)));
EXPECT_THAT(
error()->FindInt("code"),
- testing::Optional(static_cast<int>(crdtp::DispatchCode::SERVER_ERROR)));
+ testing::Optional(static_cast<int>(crdtp::DispatchCode::INVALID_PARAMS)));
EXPECT_EQ(*error()->FindString("message"),
- "System backend is not allowed for the current client");
+ "Supplied perfettoConfig doesn't have any allowed data sources "
+ "specified");
}
#if BUILDFLAG(IS_POSIX)
diff --git a/content/browser/devtools/protocol/tracing_handler_unittest.cc b/content/browser/devtools/protocol/tracing_handler_unittest.cc
index 2430a117..84dad65 100644
--- a/content/browser/devtools/protocol/tracing_handler_unittest.cc
+++ b/content/browser/devtools/protocol/tracing_handler_unittest.cc
@@ -7,8 +7,11 @@
#include <memory>
#include "base/json/json_reader.h"
+#include "base/process/process.h"
+#include "base/strings/string_number_conversions.h"
#include "base/trace_event/trace_config.h"
#include "base/values.h"
+#include "content/browser/devtools/devtools_session.h"
#include "content/browser/devtools/devtools_traceable_screenshot.h"
#include "services/tracing/public/cpp/perfetto/perfetto_data_source_names.h"
#include "testing/gmock/include/gmock/gmock.h"
@@ -79,12 +82,23 @@
} // namespace
-class TracingHandlerTest : public testing::Test {
+class StubClient : public DevToolsAgentHostClient {
+ public:
+ void DispatchProtocolMessage(DevToolsAgentHost* agent_host,
+ base::span<const uint8_t> message) override {}
+ void AgentHostClosed(DevToolsAgentHost* agent_host) override {}
+};
+
+class TracingHandlerTest : public testing::Test,
+ public testing::WithParamInterface<bool> {
public:
void SetUp() override {
+ client_ = std::make_unique<StubClient>();
+ session_ = std::make_unique<DevToolsSession>(
+ client_.get(), DevToolsSession::Mode::kSupportsTabTarget);
tracing_handler_ =
- std::make_unique<TracingHandler>(nullptr, nullptr, nullptr,
- /*is_trusted=*/true);
+ std::make_unique<TracingHandler>(nullptr, nullptr, session_.get(),
+ /*is_trusted=*/GetParam());
}
void TearDown() override { tracing_handler_.reset(); }
@@ -96,11 +110,33 @@
tracing_handler_->trace_data_buffer_state_.offset);
}
- private:
+ perfetto::TraceConfig GetTraceConfig() {
+ return tracing_handler_->trace_config_;
+ }
+
+ void SetDidInitiateRecording(bool value) {
+ tracing_handler_->did_initiate_recording_ = value;
+ }
+
+ void CallAddProcessToFilter(base::ProcessId pid) {
+ tracing_handler_->AddProcessToFilter(pid);
+ }
+
+ static void CallAddPidsToProcessFilter(
+ const std::unordered_set<base::ProcessId>& pids,
+ perfetto::TraceConfig& config) {
+ TracingHandler::AddPidsToProcessFilter(pids, config);
+ }
+
+ TracingHandler* tracing_handler() { return tracing_handler_.get(); }
+
+ protected:
+ std::unique_ptr<StubClient> client_;
+ std::unique_ptr<DevToolsSession> session_;
std::unique_ptr<TracingHandler> tracing_handler_;
};
... (truncated)
Loading diff…
Original Bug Report
The reporter's bug is still restricted on the tracker. Chrome de-restricts security bugs ~30–90 days after the fix ships; a later run will backfill it here.
References
On This Page