Skip to content

Commit

Permalink
PrefetchProxy Sends Decoy Requests when prefetch isn't eligible
Browse files Browse the repository at this point in the history
This CL adds behavior to send decoy prefetch requests when a url is not
eligible because of some private user state. For example, if a user has
cookies on an origin and it isn't prefetched, Google could learn that
the user has cookies on that origin. Now, a "decoy" request will be sent
that looks identical to a normal prefetch, even when the user has
cookies for the site. These decoys will not be cached or used, just
silently discarded.

A variation param is also added to set a random ratio of decoy requests
that are sent, in hopes of reducing some of the unused data usage. By
default, decoy requests are always sent.

(cherry picked from commit 2bae35f)

Bug: 1181441
Change-Id: I23ce731e1aaf433ccce0bb45209eaf9d46be2f81
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2718584
Commit-Queue: Robert Ogden <robertogden@chromium.org>
Reviewed-by: Ryan Sturm <ryansturm@chromium.org>
Cr-Original-Commit-Position: refs/heads/master@{#858834}
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2733747
Auto-Submit: Robert Ogden <robertogden@chromium.org>
Commit-Queue: Ryan Sturm <ryansturm@chromium.org>
Cr-Commit-Position: refs/branch-heads/4430@{#168}
Cr-Branched-From: e5ce7dc-refs/heads/master@{#857950}
  • Loading branch information
Robert Ogden authored and Chromium LUCI CQ committed Mar 5, 2021
1 parent ee4a5f0 commit ad11795
Show file tree
Hide file tree
Showing 12 changed files with 767 additions and 54 deletions.
223 changes: 220 additions & 3 deletions chrome/browser/prefetch/prefetch_proxy/prefetch_proxy_browsertest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,10 @@ class TestTabHelperObserver : public PrefetchProxyTabHelper::Observer {
}
~TestTabHelperObserver() { tab_helper_->RemoveObserverForTesting(this); }

void SetDecoyPrefetchClosure(base::OnceClosure closure) {
on_decoy_prefetch_closure_ = std::move(closure);
}

void SetOnPrefetchSuccessfulClosure(base::OnceClosure closure) {
on_successful_prefetch_closure_ = std::move(closure);
}
Expand All @@ -218,6 +222,12 @@ class TestTabHelperObserver : public PrefetchProxyTabHelper::Observer {
}

// PrefetchProxyTabHelper::Observer:
void OnDecoyPrefetchCompleted(const GURL& url) override {
if (on_decoy_prefetch_closure_) {
std::move(on_decoy_prefetch_closure_).Run();
}
}

void OnPrefetchCompletedSuccessfully(const GURL& url) override {
auto it = expected_successful_prefetch_urls_.find(url);
if (it != expected_successful_prefetch_urls_.end()) {
Expand Down Expand Up @@ -258,6 +268,8 @@ class TestTabHelperObserver : public PrefetchProxyTabHelper::Observer {
private:
PrefetchProxyTabHelper* tab_helper_;

base::OnceClosure on_decoy_prefetch_closure_;

base::OnceClosure on_successful_prefetch_closure_;
std::set<GURL> expected_successful_prefetch_urls_;

Expand Down Expand Up @@ -494,6 +506,7 @@ class PrefetchProxyBrowserTest

void SetUpCommandLine(base::CommandLine* cmd) override {
InProcessBrowserTest::SetUpCommandLine(cmd);
cmd->AppendSwitch("prefetch-proxy-never-send-decoy-requests-for-testing");
// For the proxy.
cmd->AppendSwitch("ignore-certificate-errors");
cmd->AppendSwitch("force-enable-metrics-reporting");
Expand Down Expand Up @@ -2018,6 +2031,193 @@ IN_PROC_BROWSER_TEST_F(PrefetchProxyBrowserTest,
run_loop.Run();
}

class PrefetchProxyWithDecoyRequestsBrowserTest
: public PrefetchProxyBrowserTest {
public:
PrefetchProxyWithDecoyRequestsBrowserTest() = default;
~PrefetchProxyWithDecoyRequestsBrowserTest() override = default;

// PrefetchProxyBrowserTest:
void SetUpCommandLine(base::CommandLine* cmd) override {
PrefetchProxyBrowserTest::SetUpCommandLine(cmd);
cmd->RemoveSwitch("prefetch-proxy-never-send-decoy-requests-for-testing");
cmd->AppendSwitch("prefetch-proxy-always-send-decoy-requests-for-testing");
}
};

IN_PROC_BROWSER_TEST_F(PrefetchProxyWithDecoyRequestsBrowserTest,
DISABLE_ON_WIN_MAC_CHROMEOS(ServiceWorker)) {
SetDataSaverEnabled(true);

GURL starting_page =
GetOriginServerURL("/service_worker/create_service_worker.html");

// Load a page that registers a service worker.
ui_test_utils::NavigateToURL(browser(), starting_page);
EXPECT_EQ("DONE", EvalJs(GetWebContents(),
"register('network_fallback_worker.js');"));

content::ServiceWorkerContext* service_worker_context_ =
content::BrowserContext::GetDefaultStoragePartition(browser()->profile())
->GetServiceWorkerContext();
ASSERT_TRUE(service_worker_context_->MaybeHasRegistrationForOrigin(
url::Origin::Create(starting_page)));

ukm::SourceId srp_source_id =
GetWebContents()->GetMainFrame()->GetPageUkmSourceId();

base::RunLoop run_loop;
TestTabHelperObserver tab_helper_observer(
PrefetchProxyTabHelper::FromWebContents(GetWebContents()));
tab_helper_observer.SetDecoyPrefetchClosure(run_loop.QuitClosure());

size_t starting_origin_request_count = origin_server_requests().size();

GURL prefetch_url = GetOriginServerURL("/title2.html");
GURL doc_url("https://www.google.com/search?q=test");
MakeNavigationPrediction(doc_url, {prefetch_url});
run_loop.Run();

size_t after_prefetch_origin_request_count = origin_server_requests().size();
EXPECT_EQ(starting_origin_request_count + 1,
after_prefetch_origin_request_count);

ui_test_utils::NavigateToURL(browser(), prefetch_url);

// The prefetch should not have been used, so the webpage should have been
// requested again.
EXPECT_GT(origin_server_requests().size(),
after_prefetch_origin_request_count);

// Navigate again to trigger UKM recording.
ui_test_utils::NavigateToURL(browser(), GURL("about:blank"));

using UkmEntry = ukm::TestUkmRecorder::HumanReadableUkmEntry;
auto expected_entries = std::vector<UkmEntry>{
// prefetch_url
UkmEntry{
srp_source_id,
{
{"DataLength", 0}, /* only checked for > 0 */
{"FetchDurationMS", 0}, /* only checked for > 0 */
{"NavigationStartToFetchStartMS", 0}, /* only checked for > 0 */
{"LinkClicked", 1},
{"LinkPosition", 0},
{"ResourceType", 1},
{"Status", 29},
}},
};
auto actual_entries =
GetAndVerifyPrefetchedResourceUKM(starting_page, srp_source_id);
EXPECT_THAT(actual_entries,
testing::UnorderedElementsAreArray(
BuildPrefetchResourceMatchers(expected_entries)))
<< ActualHumanReadableMetricsToDebugString(actual_entries);

// 29 = |kPrefetchIsPrivacyDecoy|
EXPECT_EQ(base::Optional<int64_t>(29),
GetUKMMetric(prefetch_url,
ukm::builders::PrefetchProxy_AfterSRPClick::kEntryName,
ukm::builders::PrefetchProxy_AfterSRPClick::
kSRPClickPrefetchStatusName));
VerifyUKMAfterSRP(
prefetch_url,
ukm::builders::PrefetchProxy_AfterSRPClick::kClickedLinkSRPPositionName,
0);
VerifyUKMAfterSRP(
prefetch_url,
ukm::builders::PrefetchProxy_AfterSRPClick::kSRPPrefetchEligibleCountName,
0);
EXPECT_EQ(
base::nullopt,
GetUKMMetric(
prefetch_url, ukm::builders::PrefetchProxy_AfterSRPClick::kEntryName,
ukm::builders::PrefetchProxy_AfterSRPClick::kProbeLatencyMsName));
}

IN_PROC_BROWSER_TEST_F(PrefetchProxyWithDecoyRequestsBrowserTest,
DISABLE_ON_WIN_MAC_CHROMEOS(Cookie)) {
GURL starting_page = GetOriginServerURL("/simple.html");

SetDataSaverEnabled(true);
ui_test_utils::NavigateToURL(browser(), starting_page);
WaitForUpdatedCustomProxyConfig();

ASSERT_TRUE(content::SetCookie(browser()->profile(), GetOriginServerURL("/"),
"cookietype=ChocolateChip"));

ukm::SourceId srp_source_id =
GetWebContents()->GetMainFrame()->GetPageUkmSourceId();

base::RunLoop run_loop;
TestTabHelperObserver tab_helper_observer(
PrefetchProxyTabHelper::FromWebContents(GetWebContents()));
tab_helper_observer.SetDecoyPrefetchClosure(run_loop.QuitClosure());

size_t starting_origin_request_count = origin_server_requests().size();

GURL prefetch_url = GetOriginServerURL("/title2.html");
GURL doc_url("https://www.google.com/search?q=test");
MakeNavigationPrediction(doc_url, {prefetch_url});
run_loop.Run();

size_t after_prefetch_origin_request_count = origin_server_requests().size();
EXPECT_EQ(starting_origin_request_count + 1,
after_prefetch_origin_request_count);

ui_test_utils::NavigateToURL(browser(), prefetch_url);

// The prefetch should not have been used, so the webpage should have been
// requested again.
EXPECT_GT(origin_server_requests().size(),
after_prefetch_origin_request_count);

// Navigate again to trigger UKM recording.
ui_test_utils::NavigateToURL(browser(), GURL("about:blank"));

using UkmEntry = ukm::TestUkmRecorder::HumanReadableUkmEntry;
auto expected_entries = std::vector<UkmEntry>{
// prefetch_url
UkmEntry{
srp_source_id,
{
{"DataLength", 0}, /* only checked for > 0 */
{"FetchDurationMS", 0}, /* only checked for > 0 */
{"NavigationStartToFetchStartMS", 0}, /* only checked for > 0 */
{"LinkClicked", 1},
{"LinkPosition", 0},
{"ResourceType", 1},
{"Status", 29},
}},
};
auto actual_entries =
GetAndVerifyPrefetchedResourceUKM(starting_page, srp_source_id);
EXPECT_THAT(actual_entries,
testing::UnorderedElementsAreArray(
BuildPrefetchResourceMatchers(expected_entries)))
<< ActualHumanReadableMetricsToDebugString(actual_entries);

// 29 = |kPrefetchIsPrivacyDecoy|
EXPECT_EQ(base::Optional<int64_t>(29),
GetUKMMetric(prefetch_url,
ukm::builders::PrefetchProxy_AfterSRPClick::kEntryName,
ukm::builders::PrefetchProxy_AfterSRPClick::
kSRPClickPrefetchStatusName));
VerifyUKMAfterSRP(
prefetch_url,
ukm::builders::PrefetchProxy_AfterSRPClick::kClickedLinkSRPPositionName,
0);
VerifyUKMAfterSRP(
prefetch_url,
ukm::builders::PrefetchProxy_AfterSRPClick::kSRPPrefetchEligibleCountName,
0);
EXPECT_EQ(
base::nullopt,
GetUKMMetric(
prefetch_url, ukm::builders::PrefetchProxy_AfterSRPClick::kEntryName,
ukm::builders::PrefetchProxy_AfterSRPClick::kProbeLatencyMsName));
}

class PolicyTestPrefetchProxyBrowserTest : public policy::PolicyTest {
public:
void SetUp() override {
Expand Down Expand Up @@ -2269,7 +2469,12 @@ class ProbingEnabled_CanaryOff_HTTPHead_PrefetchProxyBrowserTest
PrefetchProxyBrowserTest::SetFeatures();
scoped_feature_list_.InitAndEnableFeatureWithParameters(
features::kIsolatePrerendersMustProbeOrigin,
{{"do_canary", "false"}, {"replace_tls_with_http", "true"}});
{
{"do_canary", "false"},
{"replace_tls_with_http", "true"},
{"ineligible_decoy_request_probability", "0"},
{"ineligible_decoy_request_probability", "0"},
});
}

private:
Expand Down Expand Up @@ -2527,6 +2732,7 @@ class ProbingEnabled_CanaryOn_BothCanaryGood_PrefetchProxyBrowserTest
{"do_canary", "true"},
{"tls_canary_url", GetCanaryServerURL().spec()},
{"dns_canary_url", GetCanaryServerURL().spec()},
{"ineligible_decoy_request_probability", "0"},
});
}

Expand All @@ -2545,6 +2751,7 @@ class ProbingEnabled_CanaryOn_TLSCanaryBad_DNSCanaryBad_PrefetchProxyBrowserTest
{"do_canary", "true"},
{"tls_canary_url", "http://invalid.com"},
{"dns_canary_url", "http://invalid.com"},
{"ineligible_decoy_request_probability", "0"},
});
}

Expand All @@ -2564,6 +2771,7 @@ class
{"do_canary", "true"},
{"tls_canary_url", "http://invalid.com"},
{"dns_canary_url", GetCanaryServerURL().spec()},
{"ineligible_decoy_request_probability", "0"},
});
}

Expand All @@ -2583,6 +2791,7 @@ class
{"do_canary", "true"},
{"tls_canary_url", GetCanaryServerURL().spec()},
{"dns_canary_url", "http://invalid.com"},
{"ineligible_decoy_request_probability", "0"},
});
}

Expand All @@ -2597,7 +2806,11 @@ class ProbingEnabled_CanaryOn_CanaryBad_PrefetchProxyBrowserTest
PrefetchProxyBaseProbingBrowserTest::SetFeatures();
scoped_feature_list_.InitAndEnableFeatureWithParameters(
features::kIsolatePrerendersMustProbeOrigin,
{{"do_canary", "true"}, {"canary_url", "http://invalid.com"}});
{
{"do_canary", "true"},
{"canary_url", "http://invalid.com"},
{"ineligible_decoy_request_probability", "0"},
});
}

private:
Expand Down Expand Up @@ -3397,7 +3610,11 @@ class ProbingAndNSPEnabledPrefetchProxyBrowserTest
scoped_feature_list_.InitAndEnableFeature(
blink::features::kLightweightNoStatePrefetch);
probing_scoped_feature_list_.InitAndEnableFeatureWithParameters(
features::kIsolatePrerendersMustProbeOrigin, {{"do_canary", "false"}});
features::kIsolatePrerendersMustProbeOrigin,
{
{"do_canary", "false"},
{"ineligible_decoy_request_probability", "0"},
});
}

private:
Expand Down
24 changes: 24 additions & 0 deletions chrome/browser/prefetch/prefetch_proxy/prefetch_proxy_params.cc
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include "base/containers/contains.h"
#include "base/feature_list.h"
#include "base/metrics/field_trial_params.h"
#include "base/rand_util.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "chrome/browser/prefetch/prefetch_proxy/prefetch_proxy_features.h"
Expand Down Expand Up @@ -222,3 +223,26 @@ base::TimeDelta PrefetchProxyMaxRetryAfterDelta() {
1 * 60 * 60 * 24 * 7 /* 1 week */);
return base::TimeDelta::FromSeconds(max_seconds);
}

bool PrefetchProxySendDecoyRequestForIneligiblePrefetch() {
if (base::CommandLine::ForCurrentProcess()->HasSwitch(
"prefetch-proxy-never-send-decoy-requests-for-testing")) {
return false;
}
if (base::CommandLine::ForCurrentProcess()->HasSwitch(
"prefetch-proxy-always-send-decoy-requests-for-testing")) {
return true;
}

double probability = base::GetFieldTrialParamByFeatureAsDouble(
features::kIsolatePrerenders, "ineligible_decoy_request_probability",
1.0);

// Clamp to [0.0, 1.0].
probability = std::max(0.0, probability);
probability = std::min(1.0, probability);

// RandDouble returns [0.0, 1.0) so don't use <= here since that may return
// true when the probability is supposed to be 0 (i.e.: always false).
return base::RandDouble() < probability;
}
Original file line number Diff line number Diff line change
Expand Up @@ -91,4 +91,10 @@ bool PrefetchProxyShouldPrefetchPosition(size_t position);
// The maximum retry-after header value that will be persisted.
base::TimeDelta PrefetchProxyMaxRetryAfterDelta();

// Returns true if an ineligible prefetch request should be put on the network,
// but not cached, to disguise the presence of cookies (or other criteria). The
// return value is randomly decided based on variation params since always
// sending the decoy request is expensive from a data use perspective.
bool PrefetchProxySendDecoyRequestForIneligiblePrefetch();

#endif // CHROME_BROWSER_PREFETCH_PREFETCH_PROXY_PREFETCH_PROXY_PARAMS_H_
Original file line number Diff line number Diff line change
Expand Up @@ -68,3 +68,25 @@ TEST(PrefetchProxyParamsTest, PrefetchPosition_Messy) {
EXPECT_FALSE(PrefetchProxyShouldPrefetchPosition(not_want_position));
}
}

TEST(PrefetchProxyParamsTest, DecoyProbabilityClampedZero) {
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitAndEnableFeatureWithParameters(
features::kIsolatePrerenders,
{{"ineligible_decoy_request_probability", "-1"}});

for (size_t i = 0; i < 100; i++) {
EXPECT_FALSE(PrefetchProxySendDecoyRequestForIneligiblePrefetch());
}
}

TEST(PrefetchProxyParamsTest, DecoyProbabilityClampedOne) {
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitAndEnableFeatureWithParameters(
features::kIsolatePrerenders,
{{"ineligible_decoy_request_probability", "2"}});

for (size_t i = 0; i < 100; i++) {
EXPECT_TRUE(PrefetchProxySendDecoyRequestForIneligiblePrefetch());
}
}

0 comments on commit ad11795

Please sign in to comment.