Low chrome Memory Corruption 🔧 Commit mapped

Overview

Low
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactMemory corruption in DevTools
DescriptionMemory corruption in DevTools
ComponentDevTools
Sub-componentDevTools
Bug ClassMemory Corruption
Tracker563814519
Fix commitd76b46e67426 (chromium/src) +380/-79
CISA KEVNot listed
CreditedGoogle
Disclosed2026-10-20

Changed Functions

FunctionChangeNotes
buffer_usage_reporting_interval_
content/browser/devtools/protocol/tracing_handler.cc
modified
if
content/browser/devtools/protocol/tracing_handler.cc
modified
if
content/browser/devtools/protocol/devtools_protocol_browsertest.cc
modified

Files Changed

  • content/browser/devtools/protocol/tracing_handler_unittest.cc
  • content/browser/devtools/browser_devtools_agent_host.cc
  • content/browser/devtools/protocol/tracing_handler.cc
  • content/browser/devtools/devtools_session.h
  • content/browser/devtools/protocol/devtools_protocol_browsertest.cc
  • content/browser/devtools/protocol/tracing_handler.h
From d76b46e67426616a8da0e61ccf3c016e6fd0de93 Mon Sep 17 00:00:00 2001
From: Nikolay Vitkov <[email protected]>
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 <[email protected]>
> Reviewed-by: Danil Somsikov <[email protected]>
> Reviewed-by: Eric Seckler <[email protected]>
> 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 <[email protected]>
Reviewed-by: Danil Somsikov <[email protected]>
Commit-Queue: Danil Somsikov <[email protected]>
Reviewed-by: Eric Seckler <[email protected]>
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.