blob: c336a50cbd6ec15b6c068ff85b5f6c0b9384b83d [file] [log] [blame]
// Copyright 2015 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/safe_browsing/srt_fetcher_win.h"
#include <iterator>
#include <memory>
#include <set>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/files/file_path.h"
#include "base/run_loop.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/test_simple_task_runner.h"
#include "base/time/time.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/safe_browsing/srt_client_info_win.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_finder.h"
#include "chrome/common/pref_names.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "components/component_updater/pref_names.h"
#include "components/prefs/pref_service.h"
#include "components/safe_browsing_db/safe_browsing_prefs.h"
#include "content/public/test/test_browser_thread_bundle.h"
namespace safe_browsing {
namespace {
const char* const kExpectedSwitches[] = {kExtendedSafeBrowsingEnabledSwitch,
kChromeVersionSwitch,
kChromeChannelSwitch};
class SRTFetcherTest : public InProcessBrowserTest,
public SwReporterTestingDelegate {
public:
void SetUpInProcessBrowserTestFixture() override {
task_runner_ = new base::TestSimpleTaskRunner;
SetSwReporterTestingDelegate(this);
}
void SetUpOnMainThread() override {
InProcessBrowserTest::SetUpOnMainThread();
ClearLastTimeSentReport();
}
void TearDownInProcessBrowserTestFixture() override {
SetSwReporterTestingDelegate(nullptr);
}
void RunReporter(const base::FilePath& exe_path = base::FilePath()) {
auto invocation = SwReporterInvocation::FromFilePath(exe_path);
invocation.supported_behaviours =
SwReporterInvocation::BEHAVIOUR_LOG_TO_RAPPOR |
SwReporterInvocation::BEHAVIOUR_LOG_EXIT_CODE_TO_PREFS |
SwReporterInvocation::BEHAVIOUR_TRIGGER_PROMPT |
SwReporterInvocation::BEHAVIOUR_ALLOW_SEND_REPORTER_LOGS;
SwReporterQueue invocations;
invocations.push(invocation);
RunSwReporters(invocations, base::Version("1.2.3"), task_runner_,
task_runner_);
}
void RunReporterQueue(const SwReporterQueue& invocations) {
RunSwReporters(invocations, base::Version("1.2.3"), task_runner_,
task_runner_);
}
void TriggerPrompt(Browser* browser, const std::string& version) override {
prompt_trigger_called_ = true;
}
int LaunchReporter(const SwReporterInvocation& invocation) override {
++reporter_launch_count_;
reporter_launch_parameters_ = invocation;
return exit_code_to_report_;
}
void NotifyLaunchReady() override { launch_ready_notified_ = true; }
void NotifyReporterDone() override { reporter_done_notified_ = true; }
// Sets |path| in the local state to a date corresponding to |days| days ago.
void SetDateInLocalState(const std::string& path, int days) {
PrefService* local_state = g_browser_process->local_state();
DCHECK_NE(local_state, nullptr);
local_state->SetInt64(path,
(base::Time::Now() - base::TimeDelta::FromDays(days))
.ToInternalValue());
}
void SetDaysSinceLastReport(int days) {
SetDateInLocalState(prefs::kSwReporterLastTimeTriggered, days);
}
void ExpectToRunAgain(int days) {
ASSERT_TRUE(task_runner_->HasPendingTask());
EXPECT_LE(task_runner_->NextPendingTaskDelay(),
base::TimeDelta::FromDays(days));
EXPECT_GT(task_runner_->NextPendingTaskDelay(),
base::TimeDelta::FromDays(days) - base::TimeDelta::FromHours(1));
}
// Clears local state for last time the software reporter sent logs to |days|
// days ago. This prevents potential false positives that could arise from
// state not properly cleaned between successive tests.
void ClearLastTimeSentReport() {
DCHECK_NE(g_browser_process, nullptr);
PrefService* local_state = g_browser_process->local_state();
DCHECK_NE(local_state, nullptr);
local_state->ClearPref(prefs::kSwReporterLastTimeSentReport);
}
// Sets local state for last time the software reporter sent logs to |days|
// days ago.
void SetLastTimeSentReport(int days) {
SetDateInLocalState(prefs::kSwReporterLastTimeSentReport, days);
}
int64_t GetLastTimeSentReport() {
const PrefService* local_state = g_browser_process->local_state();
DCHECK_NE(local_state, nullptr);
DCHECK(local_state->HasPrefPath(prefs::kSwReporterLastTimeSentReport));
return local_state->GetInt64(prefs::kSwReporterLastTimeSentReport);
}
void ExpectLastTimeSentReportNotSet() {
PrefService* local_state = g_browser_process->local_state();
DCHECK_NE(local_state, nullptr);
EXPECT_FALSE(
local_state->HasPrefPath(prefs::kSwReporterLastTimeSentReport));
}
void ExpectLastReportSentInTheLastHour() {
const PrefService* local_state = g_browser_process->local_state();
DCHECK_NE(local_state, nullptr);
const base::Time now = base::Time::Now();
const base::Time last_time_sent_logs = base::Time::FromInternalValue(
local_state->GetInt64(prefs::kSwReporterLastTimeSentReport));
// Checks if the last time sent logs is set as no more than one hour ago,
// which should be enough time if the execution does not fail.
EXPECT_LT(now - base::TimeDelta::FromHours(1), last_time_sent_logs);
EXPECT_LT(last_time_sent_logs, now);
}
// Run through the steps needed to launch the reporter, as many times as
// needed to launch all the reporters given in |expected_launch_paths|. Test
// that each of those launches succeeded. But do not test that ONLY those
// launches succeeded.
//
// After this, if more launches are expected you can call
// |TestPartialLaunchCycle| again with another list of paths, to test that
// the launch cycle will continue with those paths.
//
// To test that a list of paths are launched AND NO OTHERS, use
// |TestReporterLaunchCycle|.
void TestPartialLaunchCycle(
const std::vector<base::FilePath>& expected_launch_paths) {
// This test has an unfortunate amount of knowledge of the internals of
// ReporterRunner, because it needs to pump the right message loops at the
// right time so that all its internal messages are delivered. This
// function might need to be updated if the internals change.
//
// The basic sequence is:
//
// 1. TryToRun kicks the whole thing off. If the reporter should not be
// launched now (eg. DaysSinceLastReport is too low) it posts a call to
// itself again. (In a regular task runner this will be scheduled with a
// delay, but the test task runner ignores delays so TryToRun will be
// called again on the next call to RunPendingTasks.)
//
// 2. When it is time to run a reporter, TryToRun calls NotifyLaunchReady
// and then posts a call to LaunchAndWait.
//
// 3. When the reporter returns, a call to ReporterDone is posted on the UI
// thread.
//
// 4. ReporterDone calls NotifyReporterDone and then posts another call to
// TryToRun, which starts the whole process over for the next run.
//
// Each call to RunPendingTasks only handles messages already on the queue.
// It doesn't handle messages posted by those messages. So, we need to call
// it in a loop to make sure we're past all pending TryToRun calls before
// LaunchAndWaitForExit will be called.
//
// Once a call to LaunchAndWaitForExit has been posted, TryToRun won't be
// called again until we pump the UI message loop in order to run
// ReporterDone.
ASSERT_TRUE(task_runner_->HasPendingTask());
ASSERT_FALSE(reporter_done_notified_);
reporter_launch_count_ = 0;
reporter_launch_parameters_ = SwReporterInvocation();
int current_launch_count = reporter_launch_count_;
for (const auto& expected_launch_path : expected_launch_paths) {
// If RunReporter was called with no pending messages, and it was already
// time to launch the reporter, then |launch_ready_notified_| will
// already be true. Otherwise there will be a TryToRun message pending,
// which must be processed first.
if (!launch_ready_notified_) {
task_runner_->RunPendingTasks();
// Since we're expecting a launch here, we expect it to schedule
// LaunchAndWaitForExit. So NOW |launch_ready_notified_| should be
// true.
ASSERT_TRUE(task_runner_->HasPendingTask());
}
ASSERT_TRUE(launch_ready_notified_);
ASSERT_EQ(current_launch_count, reporter_launch_count_);
// Reset |launch_ready_notified_| so that we can tell if TryToRun gets
// called again unexpectedly.
launch_ready_notified_ = false;
// Call the pending LaunchAndWaitForExit.
task_runner_->RunPendingTasks();
ASSERT_FALSE(launch_ready_notified_);
ASSERT_FALSE(reporter_done_notified_);
// At this point LaunchAndWaitForExit has definitely been called if
// it's going to be called at all. (If not, TryToRun will have been
// scheduled again.)
EXPECT_EQ(current_launch_count + 1, reporter_launch_count_);
EXPECT_EQ(expected_launch_path,
reporter_launch_parameters_.command_line.GetProgram());
// Pump the UI message loop to process the ReporterDone call (which
// will schedule the next TryToRun.) If LaunchAndWaitForExit wasn't
// called, this does nothing.
base::RunLoop().RunUntilIdle();
// At this point there are three things that could have happened:
//
// 1. LaunchAndWaitForExit was not called. There should be a TryToRun
// scheduled.
//
// 2. ReporterDone was called and there was nothing left in the queue
// of SwReporterInvocation's. There should be a TryToRun scheduled.
//
// 3. ReporterDone was called and there were more
// SwReporterInvocation's in the queue to run immediately. There should
// be a LaunchAndWaitForExit scheduled.
//
// So in all cases there should be a pending task, and if we are expecting
// more launches in this loop, |launch_ready_notified_| will already be
// true.
ASSERT_TRUE(task_runner_->HasPendingTask());
// The test task runner does not actually advance the clock. Pretend that
// one day has passed. (Otherwise, when we launch the last
// SwReporterInvocation in the queue, the next call to TryToRun will
// start a whole new launch cycle.)
SetDaysSinceLastReport(1);
reporter_done_notified_ = false;
current_launch_count = reporter_launch_count_;
}
}
// Run through the steps needed to launch the reporter, as many times as
// needed to launch all the reporters given in |expected_launch_paths|. Test
// that each of those launches succeeded. Then, run through the steps needed
// to launch the reporter again, to test that the launch cycle is complete
// (no more reporters will be launched).
void TestReporterLaunchCycle(
const std::vector<base::FilePath>& expected_launch_paths) {
TestPartialLaunchCycle(expected_launch_paths);
// Now that all expected launches have been tested, run the cycle once more
// to make sure no more launches happen.
ASSERT_TRUE(task_runner_->HasPendingTask());
ASSERT_FALSE(reporter_done_notified_);
ASSERT_FALSE(launch_ready_notified_);
int current_launch_count = reporter_launch_count_;
// Call the pending TryToRun.
task_runner_->RunPendingTasks();
// We expect that this scheduled another TryToRun. If it scheduled
// LaunchAndWaitForExit an unexpected launch is about to happen.
ASSERT_TRUE(task_runner_->HasPendingTask());
ASSERT_FALSE(launch_ready_notified_);
ASSERT_FALSE(reporter_done_notified_);
ASSERT_EQ(current_launch_count, reporter_launch_count_);
}
// Expects |reporter_launch_parameters_| to contain exactly the command line
// switches specified in |expected_switches|.
void ExpectLoggingSwitches(const std::set<std::string>& expected_switches) {
const base::CommandLine::SwitchMap& invocation_switches =
reporter_launch_parameters_.command_line.GetSwitches();
EXPECT_EQ(expected_switches.size(), invocation_switches.size());
// Checks if all expected switches are in the invocation switches. It's not
// necessary to check if all invocation switches are expected, since we
// checked if both sets should have the same size.
for (const std::string& expected_switch : expected_switches) {
EXPECT_NE(invocation_switches.find(expected_switch),
invocation_switches.end());
}
}
void EnableSBExtendedReporting() {
Browser* browser = chrome::FindLastActive();
ASSERT_NE(browser, nullptr);
Profile* profile = browser->profile();
ASSERT_NE(profile, nullptr);
SetExtendedReportingPref(profile->GetPrefs(), true);
}
scoped_refptr<base::TestSimpleTaskRunner> task_runner_;
bool prompt_trigger_called_ = false;
int reporter_launch_count_ = 0;
SwReporterInvocation reporter_launch_parameters_;
int exit_code_to_report_ = kReporterFailureExitCode;
// This will be set to true when a call to |LaunchAndWaitForExit| is next in
// the task queue.
bool launch_ready_notified_ = false;
bool reporter_done_notified_ = false;
};
} // namespace
IN_PROC_BROWSER_TEST_F(SRTFetcherTest, NothingFound) {
exit_code_to_report_ = kSwReporterNothingFound;
RunReporter();
task_runner_->RunPendingTasks();
EXPECT_EQ(1, reporter_launch_count_);
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(prompt_trigger_called_);
ExpectToRunAgain(kDaysBetweenSuccessfulSwReporterRuns);
}
IN_PROC_BROWSER_TEST_F(SRTFetcherTest, CleanupNeeded) {
exit_code_to_report_ = kSwReporterCleanupNeeded;
RunReporter();
task_runner_->RunPendingTasks();
EXPECT_EQ(1, reporter_launch_count_);
// The reply task from the task posted to run the reporter is run on a
// specific thread, as opposed to a specific task runner, and that thread is
// the current message loop's thread.
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(prompt_trigger_called_);
ExpectToRunAgain(kDaysBetweenSuccessfulSwReporterRuns);
}
IN_PROC_BROWSER_TEST_F(SRTFetcherTest, RanRecently) {
constexpr int kDaysLeft = 1;
SetDaysSinceLastReport(kDaysBetweenSuccessfulSwReporterRuns - kDaysLeft);
RunReporter();
// Here we can't run until idle since the ReporterRunner will re-post
// infinitely.
task_runner_->RunPendingTasks();
EXPECT_EQ(0, reporter_launch_count_);
ExpectToRunAgain(kDaysLeft);
task_runner_->ClearPendingTasks();
}
IN_PROC_BROWSER_TEST_F(SRTFetcherTest, WaitForBrowser) {
Profile* profile = browser()->profile();
CloseAllBrowsers();
exit_code_to_report_ = kSwReporterCleanupNeeded;
RunReporter();
task_runner_->RunPendingTasks();
EXPECT_EQ(1, reporter_launch_count_);
CreateBrowser(profile);
EXPECT_TRUE(prompt_trigger_called_);
ExpectToRunAgain(kDaysBetweenSuccessfulSwReporterRuns);
}
IN_PROC_BROWSER_TEST_F(SRTFetcherTest, Failure) {
exit_code_to_report_ = kReporterFailureExitCode;
RunReporter();
task_runner_->RunPendingTasks();
EXPECT_EQ(1, reporter_launch_count_);
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(prompt_trigger_called_);
ExpectToRunAgain(kDaysBetweenSuccessfulSwReporterRuns);
}
IN_PROC_BROWSER_TEST_F(SRTFetcherTest, RunDaily) {
exit_code_to_report_ = kSwReporterNothingFound;
PrefService* local_state = g_browser_process->local_state();
local_state->SetBoolean(prefs::kSwReporterPendingPrompt, true);
SetDaysSinceLastReport(kDaysBetweenSuccessfulSwReporterRuns - 1);
DCHECK_GT(kDaysBetweenSuccessfulSwReporterRuns - 1,
kDaysBetweenSwReporterRunsForPendingPrompt);
RunReporter();
task_runner_->RunPendingTasks();
EXPECT_EQ(1, reporter_launch_count_);
reporter_launch_count_ = 0;
base::RunLoop().RunUntilIdle();
ExpectToRunAgain(kDaysBetweenSwReporterRunsForPendingPrompt);
local_state->SetBoolean(prefs::kSwReporterPendingPrompt, false);
task_runner_->RunPendingTasks();
EXPECT_EQ(0, reporter_launch_count_);
base::RunLoop().RunUntilIdle();
ExpectToRunAgain(kDaysBetweenSuccessfulSwReporterRuns);
}
IN_PROC_BROWSER_TEST_F(SRTFetcherTest, ParameterChange) {
exit_code_to_report_ = kSwReporterNothingFound;
// If the reporter is run several times with different parameters, it should
// only be launched once, with the last parameter set.
const base::FilePath path1(L"path1");
const base::FilePath path2(L"path2");
const base::FilePath path3(L"path3");
// Schedule path1 with a day left in the reporting period.
// The reporter should not launch.
constexpr int kDaysLeft = 1;
{
SCOPED_TRACE("N days left until next reporter run");
SetDaysSinceLastReport(kDaysBetweenSuccessfulSwReporterRuns - kDaysLeft);
RunReporter(path1);
TestReporterLaunchCycle({});
}
// Schedule path2 just as we enter the next reporting period.
// Now the reporter should launch, just once, using path2.
{
SCOPED_TRACE("Reporter runs now");
SetDaysSinceLastReport(kDaysBetweenSuccessfulSwReporterRuns);
RunReporter(path2);
// Schedule it twice; it should only actually run once.
RunReporter(path2);
TestReporterLaunchCycle({path2});
}
// Schedule path3 before any more time has passed.
// The reporter should not launch.
{
SCOPED_TRACE("No more time passed");
SetDaysSinceLastReport(0);
RunReporter(path3);
TestReporterLaunchCycle({});
}
// Enter the next reporting period as path3 is still scheduled.
// Now the reporter should launch again using path3. (Tests that the
// parameters from the first launch aren't reused.)
{
SCOPED_TRACE("Previous run still scheduled");
SetDaysSinceLastReport(kDaysBetweenSuccessfulSwReporterRuns);
TestReporterLaunchCycle({path3});
}
// Schedule path3 again in the next reporting period.
// The reporter should launch again using path3, since enough time has
// passed, even though the parameters haven't changed.
{
SCOPED_TRACE("Run with same parameters");
SetDaysSinceLastReport(kDaysBetweenSuccessfulSwReporterRuns);
RunReporter(path3);
TestReporterLaunchCycle({path3});
}
}
IN_PROC_BROWSER_TEST_F(SRTFetcherTest, MultipleLaunches) {
exit_code_to_report_ = kSwReporterNothingFound;
const base::FilePath path1(L"path1");
const base::FilePath path2(L"path2");
const base::FilePath path3(L"path3");
SwReporterQueue invocations;
invocations.push(SwReporterInvocation::FromFilePath(path1));
invocations.push(SwReporterInvocation::FromFilePath(path2));
{
SCOPED_TRACE("Launch 2 times");
SetDaysSinceLastReport(kDaysBetweenSuccessfulSwReporterRuns);
RunReporterQueue(invocations);
TestReporterLaunchCycle({path1, path2});
}
// Schedule a launch with 2 elements, then another with the same 2. It should
// just run 2 times, not 4.
{
SCOPED_TRACE("Launch 2 times with retry");
SetDaysSinceLastReport(kDaysBetweenSuccessfulSwReporterRuns);
RunReporterQueue(invocations);
RunReporterQueue(invocations);
TestReporterLaunchCycle({path1, path2});
}
// Schedule a launch with 2 elements, then add a third while the queue is
// running.
{
SCOPED_TRACE("Add third launch while running");
SetDaysSinceLastReport(kDaysBetweenSuccessfulSwReporterRuns);
RunReporterQueue(invocations);
// Only test the cycle once, to process the first element in queue.
TestPartialLaunchCycle({path1});
invocations.push(SwReporterInvocation::FromFilePath(path3));
RunReporterQueue(invocations);
// There is still a 2nd element on the queue - that should execute, and
// nothing more.
TestReporterLaunchCycle({path2});
// Time passes... Now the 3-element queue should run.
SetDaysSinceLastReport(kDaysBetweenSuccessfulSwReporterRuns);
TestReporterLaunchCycle({path1, path2, path3});
}
// Second launch should not occur after a failure.
{
SCOPED_TRACE("Launch multiple times with failure");
exit_code_to_report_ = kReporterFailureExitCode;
SetDaysSinceLastReport(kDaysBetweenSuccessfulSwReporterRuns);
RunReporterQueue(invocations);
TestReporterLaunchCycle({path1});
// If we try again before the reporting period is up, it should not do
// anything.
TestReporterLaunchCycle({});
// After enough time has passed, should try the queue again.
SetDaysSinceLastReport(kDaysBetweenSuccessfulSwReporterRuns);
TestReporterLaunchCycle({path1});
}
}
IN_PROC_BROWSER_TEST_F(SRTFetcherTest, ReporterLogging_FeatureDisabled) {
exit_code_to_report_ = kSwReporterNothingFound;
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitAndDisableFeature(
kSwReporterExtendedSafeBrowsingFeature);
RunReporter();
TestReporterLaunchCycle({base::FilePath()});
ExpectLoggingSwitches({/*expect no switches*/});
ExpectLastTimeSentReportNotSet();
}
IN_PROC_BROWSER_TEST_F(SRTFetcherTest, ReporterLogging_NoSBExtendedReporting) {
exit_code_to_report_ = kSwReporterNothingFound;
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitAndEnableFeature(
kSwReporterExtendedSafeBrowsingFeature);
RunReporter();
TestReporterLaunchCycle({base::FilePath()});
ExpectLoggingSwitches({/*expect no switches*/});
ExpectLastTimeSentReportNotSet();
}
IN_PROC_BROWSER_TEST_F(SRTFetcherTest, ReporterLogging_EnabledFirstRun) {
exit_code_to_report_ = kSwReporterNothingFound;
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitAndEnableFeature(
kSwReporterExtendedSafeBrowsingFeature);
EnableSBExtendedReporting();
// Note: don't set last time sent logs in the local state.
// SBER is enabled and there is no record in the local state of the last time
// logs have been sent, so we should send logs in this run.
RunReporter();
TestReporterLaunchCycle({base::FilePath()});
ExpectLoggingSwitches(std::set<std::string>(std::begin(kExpectedSwitches),
std::end(kExpectedSwitches)));
ExpectLastReportSentInTheLastHour();
}
IN_PROC_BROWSER_TEST_F(SRTFetcherTest, ReporterLogging_EnabledNoRecentLogging) {
exit_code_to_report_ = kSwReporterNothingFound;
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitAndEnableFeature(
kSwReporterExtendedSafeBrowsingFeature);
// SBER is enabled and last time logs were sent was more than
// |kDaysBetweenReporterLogsSent| day ago, so we should send logs in this run.
EnableSBExtendedReporting();
SetLastTimeSentReport(kDaysBetweenReporterLogsSent + 3);
RunReporter();
TestReporterLaunchCycle({base::FilePath()});
ExpectLoggingSwitches(std::set<std::string>(std::begin(kExpectedSwitches),
std::end(kExpectedSwitches)));
ExpectLastReportSentInTheLastHour();
}
IN_PROC_BROWSER_TEST_F(SRTFetcherTest, ReporterLogging_EnabledRecentlyLogged) {
exit_code_to_report_ = kSwReporterNothingFound;
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitAndEnableFeature(
kSwReporterExtendedSafeBrowsingFeature);
// SBER is enabled, but logs have been sent less than
// |kDaysBetweenReporterLogsSent| day ago, so we shouldn't send any logs in
// this run.
EnableSBExtendedReporting();
SetLastTimeSentReport(kDaysBetweenReporterLogsSent - 1);
int64_t last_time_sent_logs = GetLastTimeSentReport();
RunReporter();
TestReporterLaunchCycle({base::FilePath()});
ExpectLoggingSwitches(std::set<std::string>{/*expect no switches*/});
EXPECT_EQ(last_time_sent_logs, GetLastTimeSentReport());
}
} // namespace safe_browsing