blob: 48490da306ce16952c91ff11a78d070ffbbd4d25 [file] [log] [blame]
// Copyright 2018 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 "components/safe_browsing/triggers/suspicious_site_trigger.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/test_simple_task_runner.h"
#include "components/prefs/testing_pref_service.h"
#include "components/safe_browsing/common/safe_browsing_prefs.h"
#include "components/safe_browsing/triggers/mock_trigger_manager.h"
#include "content/public/test/navigation_simulator.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "content/public/test/test_renderer_host.h"
#include "testing/gtest/include/gtest/gtest.h"
using content::NavigationSimulator;
using content::RenderFrameHost;
using content::RenderFrameHostTester;
using testing::_;
using testing::DoAll;
using testing::Return;
using testing::SetArgPointee;
namespace safe_browsing {
namespace {
const char kSuspiciousUrl[] = "https://suspicious.com/";
const char kCleanUrl[] = "https://foo.com/";
} // namespace
class SuspiciousSiteTriggerTest : public content::RenderViewHostTestHarness {
public:
SuspiciousSiteTriggerTest() : task_runner_(new base::TestSimpleTaskRunner) {}
~SuspiciousSiteTriggerTest() override {}
void SetUp() override {
content::RenderViewHostTestHarness::SetUp();
// Enable any prefs required for the trigger to run.
safe_browsing::RegisterProfilePrefs(prefs_.registry());
prefs_.SetBoolean(prefs::kSafeBrowsingExtendedReportingOptInAllowed, true);
prefs_.SetBoolean(prefs::kSafeBrowsingScoutReportingEnabled, true);
}
void CreateTrigger(bool monitor_mode) {
safe_browsing::SuspiciousSiteTrigger::CreateForWebContents(
web_contents(), &trigger_manager_, &prefs_, nullptr, nullptr,
monitor_mode);
safe_browsing::SuspiciousSiteTrigger* trigger =
safe_browsing::SuspiciousSiteTrigger::FromWebContents(web_contents());
// Give the trigger a test task runner that we can synchronize on.
trigger->SetTaskRunnerForTest(task_runner_);
}
// Returns the final RenderFrameHost after navigation commits.
RenderFrameHost* NavigateFrame(const std::string& url,
RenderFrameHost* frame) {
GURL gurl(url);
auto navigation_simulator =
NavigationSimulator::CreateRendererInitiated(gurl, frame);
navigation_simulator->Commit();
RenderFrameHost* final_frame_host =
navigation_simulator->GetFinalRenderFrameHost();
return final_frame_host;
}
// Returns the final RenderFrameHost after navigation commits.
RenderFrameHost* NavigateMainFrame(const std::string& url) {
return NavigateFrame(url, web_contents()->GetMainFrame());
}
// Returns the final RenderFrameHost after navigation commits.
RenderFrameHost* CreateAndNavigateSubFrame(const std::string& url,
RenderFrameHost* parent) {
RenderFrameHost* subframe =
RenderFrameHostTester::For(parent)->AppendChild("subframe");
return NavigateFrame(url, subframe);
}
void StartNewFakeLoad() {
// This fakes a new LoadStart event in the trigger, since the navigation
// simulator doesn't restart the load when we start a new navigation.
safe_browsing::SuspiciousSiteTrigger::FromWebContents(web_contents())
->DidStartLoading();
}
void FinishAllNavigations() {
// Call the trigger's DidStopLoading event handler directly since it is not
// called as part of the navigating individual frames.
safe_browsing::SuspiciousSiteTrigger::FromWebContents(web_contents())
->DidStopLoading();
}
void TriggerSuspiciousSite() {
// Notify the trigger that a suspicious site was detected.
safe_browsing::SuspiciousSiteTrigger::FromWebContents(web_contents())
->SuspiciousSiteDetected();
}
void WaitForTaskRunnerIdle() {
task_runner_->RunUntilIdle();
base::RunLoop().RunUntilIdle();
}
// Checks the trigger event histogram and ensures that |event| happened
// |count| times.
void ExpectEventHistogramCount(const SuspiciousSiteTriggerEvent event,
int count) {
histograms_.ExpectBucketCount(kSuspiciousSiteTriggerEventMetricName,
static_cast<int>(event), count);
}
// Checks the histogram that tracks what state the trigger was in when the
// delay timer fired. Ensures that the trigger was in |state| and occured
// |count| times.
void ExpectDelayStateHistogramCount(
const SuspiciousSiteTrigger::TriggerState state,
int count) {
histograms_.ExpectBucketCount(
kSuspiciousSiteTriggerReportDelayStateMetricName,
static_cast<int>(state), count);
}
// Checks the report rejection histogram and makes sure that |count| reports
// were rejected for |reason|.
void ExpectReportRejectionHistogramCount(const TriggerManagerReason reason,
int count) {
histograms_.ExpectBucketCount(
kSuspiciousSiteTriggerReportRejectionMetricName,
static_cast<int>(reason), count);
}
// Checks the report rejection histogram and makes sure it was empty,
// indicating no errors occurred.
void ExpectNoReportRejection() {
histograms_.ExpectTotalCount(
kSuspiciousSiteTriggerReportRejectionMetricName, 0);
}
MockTriggerManager* get_trigger_manager() { return &trigger_manager_; }
private:
TestingPrefServiceSimple prefs_;
MockTriggerManager trigger_manager_;
base::HistogramTester histograms_;
scoped_refptr<base::TestSimpleTaskRunner> task_runner_;
};
TEST_F(SuspiciousSiteTriggerTest, RegularPageNonSuspicious) {
// In a normal case where there are no suspicious URLs on the page, the
// trigger should not fire.
CreateTrigger(/*monitor_mode=*/false);
EXPECT_CALL(*get_trigger_manager(),
StartCollectingThreatDetailsWithReason(_, _, _, _, _, _, _))
.Times(0);
EXPECT_CALL(*get_trigger_manager(),
FinishCollectingThreatDetails(_, _, _, _, _, _))
.Times(0);
RenderFrameHost* main_frame = NavigateMainFrame(kCleanUrl);
CreateAndNavigateSubFrame(kCleanUrl, main_frame);
CreateAndNavigateSubFrame(kCleanUrl, main_frame);
FinishAllNavigations();
// One page load start and finish. No suspicious sites and no reports sent.
ExpectEventHistogramCount(SuspiciousSiteTriggerEvent::PAGE_LOAD_START, 1);
ExpectEventHistogramCount(SuspiciousSiteTriggerEvent::PAGE_LOAD_FINISH, 1);
ExpectEventHistogramCount(
SuspiciousSiteTriggerEvent::SUSPICIOUS_SITE_DETECTED, 0);
ExpectEventHistogramCount(SuspiciousSiteTriggerEvent::REPORT_STARTED, 0);
ExpectNoReportRejection();
}
TEST_F(SuspiciousSiteTriggerTest, SuspiciousHitDuringLoad) {
// When a suspicious site is detected in the middle of a page load, a report
// is created after the page load has finished.
CreateTrigger(/*monitor_mode=*/false);
EXPECT_CALL(*get_trigger_manager(),
StartCollectingThreatDetailsWithReason(_, _, _, _, _, _, _))
.Times(1)
.WillOnce(Return(true));
EXPECT_CALL(*get_trigger_manager(),
FinishCollectingThreatDetails(_, _, _, _, _, _))
.Times(1)
.WillOnce(Return(true));
RenderFrameHost* main_frame = NavigateMainFrame(kCleanUrl);
CreateAndNavigateSubFrame(kSuspiciousUrl, main_frame);
TriggerSuspiciousSite();
CreateAndNavigateSubFrame(kCleanUrl, main_frame);
FinishAllNavigations();
WaitForTaskRunnerIdle();
// One page load start and finish. One suspicious site detected and one
// report started and sent after the page finished loading.
ExpectEventHistogramCount(SuspiciousSiteTriggerEvent::PAGE_LOAD_START, 1);
ExpectEventHistogramCount(SuspiciousSiteTriggerEvent::PAGE_LOAD_FINISH, 1);
ExpectEventHistogramCount(
SuspiciousSiteTriggerEvent::SUSPICIOUS_SITE_DETECTED, 1);
ExpectEventHistogramCount(SuspiciousSiteTriggerEvent::REPORT_STARTED, 1);
// Ensure the delay timer fired and it happened in the REPORT_STARTED state
ExpectEventHistogramCount(SuspiciousSiteTriggerEvent::REPORT_DELAY_TIMER, 1);
ExpectDelayStateHistogramCount(
SuspiciousSiteTrigger::TriggerState::REPORT_STARTED, 1);
ExpectEventHistogramCount(SuspiciousSiteTriggerEvent::REPORT_FINISHED, 1);
ExpectNoReportRejection();
}
TEST_F(SuspiciousSiteTriggerTest, SuspiciousHitAfterLoad) {
// When a suspicious site is detected in after a page load, a report is
// created immediately.
CreateTrigger(/*monitor_mode=*/false);
EXPECT_CALL(*get_trigger_manager(),
StartCollectingThreatDetailsWithReason(_, _, _, _, _, _, _))
.Times(1)
.WillOnce(Return(true));
EXPECT_CALL(*get_trigger_manager(),
FinishCollectingThreatDetails(_, _, _, _, _, _))
.Times(1)
.WillOnce(Return(true));
RenderFrameHost* main_frame = NavigateMainFrame(kCleanUrl);
CreateAndNavigateSubFrame(kSuspiciousUrl, main_frame);
CreateAndNavigateSubFrame(kCleanUrl, main_frame);
FinishAllNavigations();
TriggerSuspiciousSite();
WaitForTaskRunnerIdle();
// One page load start and finish. One suspicious site detected and one
// report started and sent.
ExpectEventHistogramCount(SuspiciousSiteTriggerEvent::PAGE_LOAD_START, 1);
ExpectEventHistogramCount(SuspiciousSiteTriggerEvent::PAGE_LOAD_FINISH, 1);
ExpectEventHistogramCount(
SuspiciousSiteTriggerEvent::SUSPICIOUS_SITE_DETECTED, 1);
ExpectEventHistogramCount(SuspiciousSiteTriggerEvent::REPORT_STARTED, 1);
// Ensure the delay timer fired and it happened in the REPORT_STARTED state
ExpectEventHistogramCount(SuspiciousSiteTriggerEvent::REPORT_DELAY_TIMER, 1);
ExpectDelayStateHistogramCount(
SuspiciousSiteTrigger::TriggerState::REPORT_STARTED, 1);
ExpectEventHistogramCount(SuspiciousSiteTriggerEvent::REPORT_FINISHED, 1);
ExpectNoReportRejection();
}
TEST_F(SuspiciousSiteTriggerTest, ReportRejectedByTriggerManager) {
// If the trigger manager rejects the report then no report is sent.
CreateTrigger(/*monitor_mode=*/false);
EXPECT_CALL(*get_trigger_manager(),
StartCollectingThreatDetailsWithReason(_, _, _, _, _, _, _))
.Times(1)
.WillOnce(
DoAll(SetArgPointee<6>(TriggerManagerReason::DAILY_QUOTA_EXCEEDED),
Return(false)));
EXPECT_CALL(*get_trigger_manager(),
FinishCollectingThreatDetails(_, _, _, _, _, _))
.Times(0);
RenderFrameHost* main_frame = NavigateMainFrame(kCleanUrl);
CreateAndNavigateSubFrame(kSuspiciousUrl, main_frame);
TriggerSuspiciousSite();
CreateAndNavigateSubFrame(kCleanUrl, main_frame);
FinishAllNavigations();
WaitForTaskRunnerIdle();
// One page load start and finish. One suspicious site detected but no report
// is sent because it's rejected. Error stats should reflect the rejection.
ExpectEventHistogramCount(SuspiciousSiteTriggerEvent::PAGE_LOAD_START, 1);
ExpectEventHistogramCount(SuspiciousSiteTriggerEvent::PAGE_LOAD_FINISH, 1);
ExpectEventHistogramCount(
SuspiciousSiteTriggerEvent::SUSPICIOUS_SITE_DETECTED, 1);
// Ensure no report was started or finished, and no delay timer fired.
ExpectEventHistogramCount(SuspiciousSiteTriggerEvent::REPORT_STARTED, 0);
ExpectEventHistogramCount(SuspiciousSiteTriggerEvent::REPORT_DELAY_TIMER, 0);
ExpectEventHistogramCount(SuspiciousSiteTriggerEvent::REPORT_FINISHED, 0);
// Ensure that starting a report failed, and it was rejected for the
// expected reason (quota).
ExpectEventHistogramCount(SuspiciousSiteTriggerEvent::REPORT_START_FAILED, 1);
ExpectReportRejectionHistogramCount(
TriggerManagerReason::DAILY_QUOTA_EXCEEDED, 1);
}
TEST_F(SuspiciousSiteTriggerTest, NewNavigationMidLoad_NotSuspicious) {
// Exercise what happens when a new navigation begins in the middle of a page
// load when no suspicious site is detected.
CreateTrigger(/*monitor_mode=*/false);
EXPECT_CALL(*get_trigger_manager(),
StartCollectingThreatDetailsWithReason(_, _, _, _, _, _, _))
.Times(0);
EXPECT_CALL(*get_trigger_manager(),
FinishCollectingThreatDetails(_, _, _, _, _, _))
.Times(0);
RenderFrameHost* main_frame = NavigateMainFrame(kCleanUrl);
CreateAndNavigateSubFrame(kCleanUrl, main_frame);
CreateAndNavigateSubFrame(kCleanUrl, main_frame);
// Begin a brand new load before the first one is finished.
StartNewFakeLoad();
FinishAllNavigations();
// Two page load start events, but only one finish. No suspicious sites
// detected and no reports sent.
ExpectEventHistogramCount(SuspiciousSiteTriggerEvent::PAGE_LOAD_START, 2);
ExpectEventHistogramCount(SuspiciousSiteTriggerEvent::PAGE_LOAD_FINISH, 1);
ExpectEventHistogramCount(
SuspiciousSiteTriggerEvent::SUSPICIOUS_SITE_DETECTED, 0);
ExpectEventHistogramCount(SuspiciousSiteTriggerEvent::REPORT_STARTED, 0);
ExpectNoReportRejection();
}
TEST_F(SuspiciousSiteTriggerTest, NewNavigationMidLoad_Suspicious) {
// Exercise what happens when a new navigation begins in the middle of a page
// load when a suspicious site was detected. The report of the first site
// must be cancelled because we were waiting for the first load to finish
// before beginning the report.
CreateTrigger(/*monitor_mode=*/false);
EXPECT_CALL(*get_trigger_manager(),
StartCollectingThreatDetailsWithReason(_, _, _, _, _, _, _))
.Times(0);
EXPECT_CALL(*get_trigger_manager(),
FinishCollectingThreatDetails(_, _, _, _, _, _))
.Times(0);
RenderFrameHost* main_frame = NavigateMainFrame(kCleanUrl);
CreateAndNavigateSubFrame(kCleanUrl, main_frame);
// Trigger a suspicious site. We wait for this page load to finish before
// creating the report.
TriggerSuspiciousSite();
CreateAndNavigateSubFrame(kCleanUrl, main_frame);
// Begin a brand new load before the first one is finished. This will cancel
// the report that is queued.
StartNewFakeLoad();
FinishAllNavigations();
// Two page load start events, but only one finish. One suspicious site
// detected but no reports created because the report gets cancelled.
ExpectEventHistogramCount(SuspiciousSiteTriggerEvent::PAGE_LOAD_START, 2);
ExpectEventHistogramCount(SuspiciousSiteTriggerEvent::PAGE_LOAD_FINISH, 1);
ExpectEventHistogramCount(
SuspiciousSiteTriggerEvent::SUSPICIOUS_SITE_DETECTED, 1);
ExpectEventHistogramCount(SuspiciousSiteTriggerEvent::REPORT_STARTED, 0);
ExpectNoReportRejection();
// Ensure that the repot got cancelled by the second load.
ExpectEventHistogramCount(
SuspiciousSiteTriggerEvent::PENDING_REPORT_CANCELLED_BY_LOAD, 1);
}
TEST_F(SuspiciousSiteTriggerTest, MonitorMode_NotSuspicious) {
// Testing the trigger in monitoring mode, it should never send reports.
// In a normal case where there are no suspicious URLs on the page, the
// trigger should not fire.
CreateTrigger(/*monitor_mode=*/true);
EXPECT_CALL(*get_trigger_manager(),
StartCollectingThreatDetailsWithReason(_, _, _, _, _, _, _))
.Times(0);
EXPECT_CALL(*get_trigger_manager(),
FinishCollectingThreatDetails(_, _, _, _, _, _))
.Times(0);
RenderFrameHost* main_frame = NavigateMainFrame(kCleanUrl);
CreateAndNavigateSubFrame(kCleanUrl, main_frame);
CreateAndNavigateSubFrame(kCleanUrl, main_frame);
FinishAllNavigations();
// One page load start and finish. No suspicious sites and no reports sent.
ExpectEventHistogramCount(SuspiciousSiteTriggerEvent::PAGE_LOAD_START, 1);
ExpectEventHistogramCount(SuspiciousSiteTriggerEvent::PAGE_LOAD_FINISH, 1);
ExpectEventHistogramCount(
SuspiciousSiteTriggerEvent::SUSPICIOUS_SITE_DETECTED, 0);
ExpectEventHistogramCount(SuspiciousSiteTriggerEvent::REPORT_STARTED, 0);
ExpectNoReportRejection();
}
TEST_F(SuspiciousSiteTriggerTest, MonitorMode_SuspiciousHitDuringLoad) {
// Testing the trigger in monitoring mode, it should never send reports.
// When a suspicious site is detected in the middle of a page load, a report
// is created after the page load has finished.
CreateTrigger(/*monitor_mode=*/true);
EXPECT_CALL(*get_trigger_manager(),
StartCollectingThreatDetailsWithReason(_, _, _, _, _, _, _))
.Times(0);
EXPECT_CALL(*get_trigger_manager(),
FinishCollectingThreatDetails(_, _, _, _, _, _))
.Times(0);
RenderFrameHost* main_frame = NavigateMainFrame(kCleanUrl);
CreateAndNavigateSubFrame(kSuspiciousUrl, main_frame);
TriggerSuspiciousSite();
CreateAndNavigateSubFrame(kCleanUrl, main_frame);
FinishAllNavigations();
WaitForTaskRunnerIdle();
// One page load start and finish. One suspicious site detected and one
// possible report that gets skipped.
ExpectEventHistogramCount(SuspiciousSiteTriggerEvent::PAGE_LOAD_START, 1);
ExpectEventHistogramCount(SuspiciousSiteTriggerEvent::PAGE_LOAD_FINISH, 1);
ExpectEventHistogramCount(
SuspiciousSiteTriggerEvent::SUSPICIOUS_SITE_DETECTED, 1);
ExpectEventHistogramCount(
SuspiciousSiteTriggerEvent::REPORT_POSSIBLE_BUT_SKIPPED, 1);
// No reports are started or finished, no delay timer fired.
ExpectEventHistogramCount(SuspiciousSiteTriggerEvent::REPORT_STARTED, 0);
ExpectEventHistogramCount(SuspiciousSiteTriggerEvent::REPORT_FINISHED, 0);
ExpectEventHistogramCount(SuspiciousSiteTriggerEvent::REPORT_DELAY_TIMER, 0);
ExpectNoReportRejection();
}
} // namespace safe_browsing