Commit dda5f291 authored by Maks Orlovich's avatar Maks Orlovich Committed by Commit Bot

Instrument extension content script injection, to see if it's being used for fingerprinting

(And to measure the entropy if so).

Or rather, to measure if it could be used for it, since we can't actually tell if the page observes the side effects.

Bug: 1103288
Change-Id: Ia94184d373d84ca9d4ce00d41dd5e67136a6936f
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2330002
Commit-Queue: Maksim Orlovich <morlovich@chromium.org>
Reviewed-by: default avatarKaran Bhatia <karandeepb@chromium.org>
Reviewed-by: default avatarAsanka Herath <asanka@chromium.org>
Reviewed-by: default avatarRobert Kaplow <rkaplow@chromium.org>
Cr-Commit-Position: refs/heads/master@{#795671}
parent 5d60623e
...@@ -19,6 +19,7 @@ ...@@ -19,6 +19,7 @@
#include "chrome/browser/extensions/extension_management_test_util.h" #include "chrome/browser/extensions/extension_management_test_util.h"
#include "chrome/browser/extensions/extension_service.h" #include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/extensions/extension_with_management_policy_apitest.h" #include "chrome/browser/extensions/extension_with_management_policy_apitest.h"
#include "chrome/browser/extensions/identifiability_metrics_test_util.h"
#include "chrome/browser/search/search.h" #include "chrome/browser/search/search.h"
#include "chrome/browser/ui/browser.h" #include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_navigator.h" #include "chrome/browser/ui/browser_navigator.h"
...@@ -44,6 +45,7 @@ ...@@ -44,6 +45,7 @@
#include "extensions/browser/notification_types.h" #include "extensions/browser/notification_types.h"
#include "extensions/common/extension.h" #include "extensions/common/extension.h"
#include "extensions/common/extension_features.h" #include "extensions/common/extension_features.h"
#include "extensions/common/identifiability_metrics.h"
#include "extensions/test/extension_test_message_listener.h" #include "extensions/test/extension_test_message_listener.h"
#include "extensions/test/result_catcher.h" #include "extensions/test/result_catcher.h"
#include "extensions/test/test_extension_dir.h" #include "extensions/test/test_extension_dir.h"
...@@ -51,6 +53,7 @@ ...@@ -51,6 +53,7 @@
#include "net/test/embedded_test_server/embedded_test_server.h" #include "net/test/embedded_test_server/embedded_test_server.h"
#include "net/test/embedded_test_server/http_request.h" #include "net/test/embedded_test_server/http_request.h"
#include "net/test/embedded_test_server/http_response.h" #include "net/test/embedded_test_server/http_response.h"
#include "third_party/blink/public/common/privacy_budget/identifiable_surface.h"
#include "ui/base/page_transition_types.h" #include "ui/base/page_transition_types.h"
#include "url/gurl.h" #include "url/gurl.h"
...@@ -1609,4 +1612,84 @@ IN_PROC_BROWSER_TEST_F(ContentScriptApiTest, CoepFrameTest) { ...@@ -1609,4 +1612,84 @@ IN_PROC_BROWSER_TEST_F(ContentScriptApiTest, CoepFrameTest) {
ASSERT_EQ(kPassed, watcher.WaitAndGetTitle()); ASSERT_EQ(kPassed, watcher.WaitAndGetTitle());
} }
class ContentScriptApiIdentifiabilityTest : public ContentScriptApiTest {
public:
void SetUpOnMainThread() override {
identifiability_metrics_test_helper_.SetUpOnMainThread();
ContentScriptApiTest::SetUpOnMainThread();
}
protected:
IdentifiabilityMetricsTestHelper identifiability_metrics_test_helper_;
};
// Test that identifiability study of content script injection produces the
// expected UKM events.
IN_PROC_BROWSER_TEST_F(ContentScriptApiIdentifiabilityTest, InjectionRecorded) {
base::RunLoop run_loop;
identifiability_metrics_test_helper_.PrepareForTest(&run_loop);
ASSERT_TRUE(StartEmbeddedTestServer());
ASSERT_TRUE(RunExtensionTest("content_scripts/all_frames")) << message_;
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
std::map<ukm::SourceId, ukm::mojom::UkmEntryPtr> merged_entries =
identifiability_metrics_test_helper_.NavigateToBlankAndWaitForMetrics(
web_contents, &run_loop);
// Right now the instrumentation infra doesn't track all of the sources that
// reported a particular surface, so we merely look for if one had it.
// Eventually both frames should report it.
bool found = false;
for (const auto& entry : merged_entries) {
const auto& metrics = entry.second->metrics;
if (metrics.contains(
SurfaceForExtension(
blink::IdentifiableSurface::Type::kExtensionContentScript,
GetSingleLoadedExtension()->id())
.ToUkmMetricHash())) {
found = true;
}
}
EXPECT_TRUE(found);
}
// Test that where a page doesn't get a content script injected, no
// such event is recorded.
IN_PROC_BROWSER_TEST_F(ContentScriptApiIdentifiabilityTest,
NoInjectionRecorded) {
base::RunLoop run_loop;
identifiability_metrics_test_helper_.PrepareForTest(&run_loop);
ASSERT_TRUE(StartEmbeddedTestServer());
ui_test_utils::NavigateToURL(browser(), GURL("about:blank"));
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
// Create a canvas and serialize it to force at least one event to happen,
// since otherwise there is no way to synchronize with the renderer.
constexpr char kForceMetricScript[] =
R"(
var c = document.createElement("canvas");
document.body.appendChild(c);
var ctx = c.getContext("2d");
var url = c.toDataURL();
"ok";
)";
EXPECT_EQ("ok", content::EvalJs(web_contents, kForceMetricScript));
std::map<ukm::SourceId, ukm::mojom::UkmEntryPtr> merged_entries =
identifiability_metrics_test_helper_.NavigateToBlankAndWaitForMetrics(
web_contents, &run_loop);
for (const auto& entry : merged_entries) {
const auto& metrics = entry.second->metrics;
for (const auto& surface_value : metrics) {
EXPECT_NE(blink::IdentifiableSurface::Type::kExtensionContentScript,
blink::IdentifiableSurface::FromMetricHash(surface_value.first)
.GetType());
}
}
}
} // namespace extensions } // namespace extensions
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/extensions/identifiability_metrics_test_util.h"
#include "base/run_loop.h"
#include "chrome/common/privacy_budget/scoped_privacy_budget_config.h"
#include "components/ukm/test_ukm_recorder.h"
#include "content/public/test/browser_test_utils.h"
#include "services/metrics/public/cpp/ukm_builders.h"
namespace extensions {
IdentifiabilityMetricsTestHelper::IdentifiabilityMetricsTestHelper() {
privacy_budget_config_.Apply(test::ScopedPrivacyBudgetConfig::Parameters());
}
IdentifiabilityMetricsTestHelper::~IdentifiabilityMetricsTestHelper() = default;
void IdentifiabilityMetricsTestHelper::SetUpOnMainThread() {
ukm_recorder_ = std::make_unique<ukm::TestAutoSetUkmRecorder>();
}
void IdentifiabilityMetricsTestHelper::PrepareForTest(base::RunLoop* run_loop) {
DCHECK(ukm_recorder_) << "IdentifiabilityMetricsTestHelper::"
"SetUpOnMainThread hasn't been called";
ukm_recorder_->SetOnAddEntryCallback(
ukm::builders::Identifiability::kEntryName, run_loop->QuitClosure());
}
std::map<ukm::SourceId, ukm::mojom::UkmEntryPtr>
IdentifiabilityMetricsTestHelper::NavigateToBlankAndWaitForMetrics(
content::WebContents* contents,
base::RunLoop* run_loop) {
DCHECK(ukm_recorder_) << "IdentifiabilityMetricsTestHelper::"
"SetUpOnMainThread hasn't been called";
// Need to navigate away to force a metrics flush; otherwise it would be
// dependent on periodic flush heuristics.
content::NavigateToURLBlockUntilNavigationsComplete(contents,
GURL("about:blank"), 1);
run_loop->Run();
return ukm_recorder_->GetMergedEntriesByName(
ukm::builders::Identifiability::kEntryName);
}
} // namespace extensions
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_BROWSER_EXTENSIONS_IDENTIFIABILITY_METRICS_TEST_UTIL_H_
#define CHROME_BROWSER_EXTENSIONS_IDENTIFIABILITY_METRICS_TEST_UTIL_H_
#include "base/run_loop.h"
#include "chrome/common/privacy_budget/scoped_privacy_budget_config.h"
#include "components/ukm/test_ukm_recorder.h"
namespace content {
class WebContents;
}
namespace extensions {
// This can be incorporated into an in-process browser test to help test
// which identifiability metrics got collected.
//
// Usage:
// 1) include as a member of test fixture, e.g.
// identifiability_metrics_test_helper_
// 2) Call SetUpOnMainThread() from fixture's SetUpOnMainThread().
// 3) In the test:
// base::RunLoop run_loop;
// identifiability_metrics_test_helper_.PrepareForTest(&run_loop);
// /* do stuff */
// auto metrics =
// identifiability_metrics_test_helper_.NavigateToBlankAndWaitForMetrics(
// web_contents, &run_loop);
// /* check that metrics has the right stuff.
// extensions::SurfaceForExtension may be useful here. */
class IdentifiabilityMetricsTestHelper {
public:
IdentifiabilityMetricsTestHelper();
~IdentifiabilityMetricsTestHelper();
void SetUpOnMainThread();
void PrepareForTest(base::RunLoop* run_loop);
// Navigates to about:blank and returns metrics from the page that is
// replaced.
std::map<ukm::SourceId, ukm::mojom::UkmEntryPtr>
NavigateToBlankAndWaitForMetrics(content::WebContents* contents,
base::RunLoop* run_loop);
private:
test::ScopedPrivacyBudgetConfig privacy_budget_config_;
std::unique_ptr<ukm::TestAutoSetUkmRecorder> ukm_recorder_;
};
} // namespace extensions
#endif // CHROME_BROWSER_EXTENSIONS_IDENTIFIABILITY_METRICS_TEST_UTIL_H_
...@@ -1856,6 +1856,8 @@ if (!is_android) { ...@@ -1856,6 +1856,8 @@ if (!is_android) {
"../browser/extensions/file_iframe_apitest.cc", "../browser/extensions/file_iframe_apitest.cc",
"../browser/extensions/google_docs_offline_ukm_browsertest.cc", "../browser/extensions/google_docs_offline_ukm_browsertest.cc",
"../browser/extensions/gpu_browsertest.cc", "../browser/extensions/gpu_browsertest.cc",
"../browser/extensions/identifiability_metrics_test_util.cc",
"../browser/extensions/identifiability_metrics_test_util.h",
"../browser/extensions/isolated_app_browsertest.cc", "../browser/extensions/isolated_app_browsertest.cc",
"../browser/extensions/lazy_background_page_apitest.cc", "../browser/extensions/lazy_background_page_apitest.cc",
"../browser/extensions/lazy_background_page_test_util.h", "../browser/extensions/lazy_background_page_test_util.h",
......
...@@ -34,4 +34,16 @@ void RecordExtensionResourceAccessResult(base::UkmSourceId ukm_source_id, ...@@ -34,4 +34,16 @@ void RecordExtensionResourceAccessResult(base::UkmSourceId ukm_source_id,
.Record(ukm::UkmRecorder::Get()); .Record(ukm::UkmRecorder::Get());
} }
void RecordContentScriptInjection(base::UkmSourceId ukm_source_id,
const ExtensionId& extension_id) {
if (ukm_source_id == base::kInvalidUkmSourceId)
return;
blink::IdentifiabilityMetricBuilder(ukm_source_id)
.Set(SurfaceForExtension(
blink::IdentifiableSurface::Type::kExtensionContentScript,
extension_id),
blink::IdentifiabilityDigestHelper(true))
.Record(ukm::UkmRecorder::Get());
}
} // namespace extensions } // namespace extensions
...@@ -5,12 +5,21 @@ ...@@ -5,12 +5,21 @@
#ifndef EXTENSIONS_COMMON_IDENTIFIABILITY_METRICS_H_ #ifndef EXTENSIONS_COMMON_IDENTIFIABILITY_METRICS_H_
#define EXTENSIONS_COMMON_IDENTIFIABILITY_METRICS_H_ #define EXTENSIONS_COMMON_IDENTIFIABILITY_METRICS_H_
#include <string>
#include "base/metrics/ukm_source_id.h" #include "base/metrics/ukm_source_id.h"
#include "extensions/common/extension_id.h"
#include "third_party/blink/public/common/privacy_budget/identifiable_surface.h"
class GURL; class GURL;
namespace extensions { namespace extensions {
// Encodes |type| and |extension_id| as an identifiability surface.
blink::IdentifiableSurface SurfaceForExtension(
blink::IdentifiableSurface::Type type,
const ExtensionId& extension_id);
// Used for histograms. Do not reorder. // Used for histograms. Do not reorder.
enum class ExtensionResourceAccessResult : int { enum class ExtensionResourceAccessResult : int {
kSuccess, kSuccess,
...@@ -26,6 +35,11 @@ void RecordExtensionResourceAccessResult(base::UkmSourceId ukm_source_id, ...@@ -26,6 +35,11 @@ void RecordExtensionResourceAccessResult(base::UkmSourceId ukm_source_id,
const GURL& gurl, const GURL& gurl,
ExtensionResourceAccessResult result); ExtensionResourceAccessResult result);
// Records that the extension |extension_id| has injected a content script into
// page identified by |ukm_source_id|.
void RecordContentScriptInjection(base::UkmSourceId ukm_source_id,
const ExtensionId& extension_id);
} // namespace extensions } // namespace extensions
#endif // EXTENSIONS_COMMON_IDENTIFIABILITY_METRICS_H_ #endif // EXTENSIONS_COMMON_IDENTIFIABILITY_METRICS_H_
...@@ -19,6 +19,7 @@ ...@@ -19,6 +19,7 @@
#include "extensions/common/extension_features.h" #include "extensions/common/extension_features.h"
#include "extensions/common/extension_messages.h" #include "extensions/common/extension_messages.h"
#include "extensions/common/host_id.h" #include "extensions/common/host_id.h"
#include "extensions/common/identifiability_metrics.h"
#include "extensions/renderer/dom_activity_logger.h" #include "extensions/renderer/dom_activity_logger.h"
#include "extensions/renderer/extension_frame_helper.h" #include "extensions/renderer/extension_frame_helper.h"
#include "extensions/renderer/extensions_renderer_client.h" #include "extensions/renderer/extensions_renderer_client.h"
...@@ -164,6 +165,8 @@ ScriptInjection::ScriptInjection( ...@@ -164,6 +165,8 @@ ScriptInjection::ScriptInjection(
injection_host_(std::move(injection_host)), injection_host_(std::move(injection_host)),
run_location_(run_location), run_location_(run_location),
request_id_(kInvalidRequestId), request_id_(kInvalidRequestId),
ukm_source_id_(base::UkmSourceId::FromInt64(
render_frame_->GetWebFrame()->GetDocument().GetUkmSourceId())),
complete_(false), complete_(false),
did_inject_js_(false), did_inject_js_(false),
log_activity_(log_activity), log_activity_(log_activity),
...@@ -272,6 +275,8 @@ ScriptInjection::InjectionResult ScriptInjection::Inject( ...@@ -272,6 +275,8 @@ ScriptInjection::InjectionResult ScriptInjection::Inject(
complete_ = did_inject_js_ || !should_inject_js; complete_ = did_inject_js_ || !should_inject_js;
if (complete_) { if (complete_) {
if (host_id().type() == HostID::EXTENSIONS)
RecordContentScriptInjection(ukm_source_id_, host_id().id());
injector_->OnInjectionComplete(std::move(execution_result_), run_location_, injector_->OnInjectionComplete(std::move(execution_result_), run_location_,
render_frame_); render_frame_);
} else { } else {
...@@ -360,6 +365,8 @@ void ScriptInjection::OnJsInjectionCompleted( ...@@ -360,6 +365,8 @@ void ScriptInjection::OnJsInjectionCompleted(
execution_result_ = std::make_unique<base::Value>(); execution_result_ = std::make_unique<base::Value>();
} }
did_inject_js_ = true; did_inject_js_ = true;
if (host_id().type() == HostID::EXTENSIONS)
RecordContentScriptInjection(ukm_source_id_, host_id().id());
// If |async_completion_callback_| is set, it means the script finished // If |async_completion_callback_| is set, it means the script finished
// asynchronously, and we should run it. // asynchronously, and we should run it.
......
...@@ -13,6 +13,7 @@ ...@@ -13,6 +13,7 @@
#include "base/callback.h" #include "base/callback.h"
#include "base/macros.h" #include "base/macros.h"
#include "base/memory/weak_ptr.h" #include "base/memory/weak_ptr.h"
#include "base/metrics/ukm_source_id.h"
#include "base/optional.h" #include "base/optional.h"
#include "extensions/common/user_script.h" #include "extensions/common/user_script.h"
#include "extensions/renderer/injection_host.h" #include "extensions/renderer/injection_host.h"
...@@ -127,6 +128,9 @@ class ScriptInjection { ...@@ -127,6 +128,9 @@ class ScriptInjection {
// currently waiting on permission. // currently waiting on permission.
int64_t request_id_; int64_t request_id_;
// Identifies the frame we're injecting into.
base::UkmSourceId ukm_source_id_;
// Whether or not the injection is complete, either via injecting the script // Whether or not the injection is complete, either via injecting the script
// or because it will never complete. // or because it will never complete.
bool complete_; bool complete_;
......
...@@ -93,6 +93,9 @@ class IdentifiableSurface { ...@@ -93,6 +93,9 @@ class IdentifiableSurface {
// Attempt to access extension URLs. // Attempt to access extension URLs.
kExtensionFileAccess = 5, kExtensionFileAccess = 5,
// Extension running content-script.
kExtensionContentScript = 6,
// We can use values up to and including |kMax|. // We can use values up to and including |kMax|.
kMax = (1 << kTypeBits) - 1 kMax = (1 << kTypeBits) - 1
}; };
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment