blob: 7ac980217f6ff14160c4f74d59f06e8e20c03ee5 [file] [log] [blame]
// Copyright 2019 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/chrome_cleaner/engines/controllers/uwe_scanner_wrapper.h"
#include <algorithm>
#include <set>
#include <utility>
#include <vector>
#include "base/bind.h"
#include "base/files/file_path.h"
#include "base/files/scoped_temp_dir.h"
#include "base/run_loop.h"
#include "base/test/task_environment.h"
#include "base/threading/sequenced_task_runner_handle.h"
#include "chrome/chrome_cleaner/chrome_utils/extension_id.h"
#include "chrome/chrome_cleaner/constants/uws_id.h"
#include "chrome/chrome_cleaner/engines/broker/engine_client_mock.h"
#include "chrome/chrome_cleaner/engines/common/engine_result_codes.h"
#include "chrome/chrome_cleaner/engines/controllers/scanner_impl.h"
#include "chrome/chrome_cleaner/logging/logging_service_api.h"
#include "chrome/chrome_cleaner/logging/mock_logging_service.h"
#include "chrome/chrome_cleaner/pup_data/pup_data.h"
#include "chrome/chrome_cleaner/pup_data/test_uws.h"
#include "chrome/chrome_cleaner/settings/settings.h"
#include "chrome/chrome_cleaner/test/test_file_util.h"
#include "chrome/chrome_cleaner/test/test_pup_data.h"
#include "chrome/chrome_cleaner/test/test_settings_util.h"
#include "chrome/chrome_cleaner/test/test_util.h"
#include "chrome/chrome_cleaner/test/test_uws_catalog.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace chrome_cleaner {
namespace {
using ::chrome_cleaner::FoilUwSData_Attribute_FLAGS_STATE_CONFIRMED_UWS;
using ::chrome_cleaner::UwEMatcher;
using testing::_;
using testing::AllOf;
using testing::Contains;
using testing::DoAll;
using testing::Invoke;
using testing::Not;
using testing::Property;
using testing::Return;
using testing::WithArg;
const unsigned int kUwSId1 = kGoogleTestAUwSID;
const unsigned int kUwSId2 = kGoogleTestBUwSID;
// Indices of EngineClient::StartScan's arguments.
constexpr int kFoundUwSCallbackPos = 3;
constexpr int kDoneCallbackPos = 4;
// Functor to call FoundUwSCallback with predefined arguments.
struct ReportUwS {
ReportUwS() = default;
ReportUwS(UwSId pup_id, const wchar_t* file_path) {
pup_id_ = pup_id;
if (file_path) {
file_path_ = base::FilePath(file_path);
pup_.AddDiskFootprint(file_path_);
}
}
void operator()(EngineClient::FoundUwSCallback found_uws_callback) {
base::SequencedTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(found_uws_callback, pup_id_, pup_));
}
base::FilePath file_path_;
UwSId pup_id_ = 0;
PUPData::PUP pup_;
};
struct ReportDone {
explicit ReportDone(uint32_t status) : status_(status) {}
void operator()(EngineClient::DoneCallback* done_callback) {
base::SequencedTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(std::move(*done_callback), status_));
}
uint32_t status_;
};
class UwEScannerWrapperTest : public testing::Test {
public:
UwEScannerWrapperTest() {
test_pup_data_.Reset({&TestUwSCatalog::GetInstance()});
}
void SetUp() override {
ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
ON_CALL(mock_settings_, locations_to_scan())
.WillByDefault(testing::ReturnRef(empty_trace_locations_));
Settings::SetInstanceForTesting(&mock_settings_);
}
void TearDown() override {
// Few tests use mocked logging service. Here we reset it.
LoggingServiceAPI::SetInstanceForTesting(nullptr);
Settings::SetInstanceForTesting(nullptr);
}
template <typename Action>
void ExpectStartScanCall(const Action& action) {
static const std::vector<UwSId> kEnabledUwS{kUwSId1, kUwSId2};
// Every call to StartScan should be preceded by GetEnabledUwS.
::testing::InSequence s;
EXPECT_CALL(*mock_engine_client_, GetEnabledUwS())
.WillOnce(Return(kEnabledUwS));
EXPECT_CALL(*mock_engine_client_,
MockedStartScan(_, _, /*include_details=*/true, _, _))
.WillOnce(action);
}
void RunScanAndExpect(ResultCode expected_result,
const std::vector<UwSId>& expected_uws,
const std::vector<ExtensionID>& expected_uwe) {
expected_result_ = expected_result;
expected_uws_ = expected_uws;
expected_uwe_ = expected_uwe;
RunScanLoop();
}
void RunScanAndStop() {
expected_result_ = RESULT_CODE_CANCELED;
expected_uws_.clear();
RunScanLoop(/*stop_immediately=*/true);
}
void FoundUwS(UwSId new_uws) {
EXPECT_EQ(found_uws_.find(new_uws), found_uws_.end());
found_uws_.insert(new_uws);
PUPData::PUP* pup = PUPData::GetPUP(new_uws);
ASSERT_TRUE(pup);
if (!pup->matched_extensions.empty()) {
ExtensionID new_uwe = pup->matched_extensions.back().id;
EXPECT_EQ(found_uwe_.find(new_uwe), found_uwe_.end());
found_uwe_.insert(new_uwe);
}
}
void Done(ResultCode status, const std::vector<UwSId>& final_found_uws) {
// Done should only be called once.
EXPECT_FALSE(done_called_);
done_called_ = true;
EXPECT_EQ(expected_result_, status);
std::vector<ExtensionID> final_found_uwe;
for (const UwSId& id : final_found_uws) {
PUPData::PUP* pup = PUPData::GetPUP(id);
for (const ForceInstalledExtension& extension : pup->matched_extensions) {
final_found_uwe.push_back(extension.id);
}
}
EXPECT_THAT(final_found_uws,
testing::UnorderedElementsAreArray(expected_uws_));
EXPECT_THAT(final_found_uwe,
testing::UnorderedElementsAreArray(expected_uwe_));
// Some found UwS may be discarded during the final validation stage, so
// the |final_found_uws| reported here should be a subset of |found_uws_|,
// which was accumulated through the callbacks.
for (const UwSId& id : final_found_uws) {
PUPData::PUP* pup = PUPData::GetPUP(id);
for (const ForceInstalledExtension& extension : pup->matched_extensions) {
EXPECT_THAT(found_uwe_, Contains(extension.id));
}
EXPECT_THAT(found_uws_, Contains(id));
}
}
bool ReportUwSWithFile(int uws_id,
const wchar_t* file_name,
ReportUwS* report_functor) {
if (!report_functor) {
return false;
}
base::FilePath file_path = temp_dir_.GetPath().Append(file_name);
if (!CreateEmptyFile(file_path)) {
return false;
}
*report_functor = ReportUwS(uws_id, file_path.value().c_str());
return true;
}
protected:
void RunScanLoop(bool stop_immediately = false) {
done_called_ = false;
scanner_->Start(
base::BindRepeating(&UwEScannerWrapperTest::FoundUwS,
base::Unretained(this)),
base::BindOnce(&UwEScannerWrapperTest::Done, base::Unretained(this)));
if (stop_immediately)
scanner_->Stop();
while (!scanner_->IsCompletelyDone())
base::RunLoop().RunUntilIdle();
}
UwEMatchers matchers_;
TestPUPData test_pup_data_;
base::test::SingleThreadTaskEnvironment task_environment_;
scoped_refptr<StrictMockEngineClient> mock_engine_client_{
base::MakeRefCounted<StrictMockEngineClient>()};
MockSettings mock_settings_;
std::unique_ptr<UwEScannerWrapper> scanner_;
std::set<UwSId> found_uws_;
std::set<ExtensionID> found_uwe_;
bool done_called_ = false;
ResultCode expected_result_;
std::vector<UwSId> expected_uws_;
std::vector<ExtensionID> expected_uwe_;
base::ScopedTempDir temp_dir_;
const std::vector<UwS::TraceLocation> empty_trace_locations_;
};
} // namespace
TEST_F(UwEScannerWrapperTest, NoUwEFound) {
const std::vector<ForceInstalledExtension> extensions;
scanner_ = std::make_unique<UwEScannerWrapper>(
std::make_unique<ScannerImpl>(mock_engine_client_.get()), &matchers_,
extensions);
ExpectStartScanCall(DoAll(
WithArg<kDoneCallbackPos>(Invoke(ReportDone(EngineResultCode::kSuccess))),
Return(EngineResultCode::kSuccess)));
RunScanAndExpect(RESULT_CODE_SUCCESS, {}, {});
}
TEST_F(UwEScannerWrapperTest, FoundUwE) {
ExtensionID report_found_extension1 =
ExtensionID::Create(kGoogleTestAUwEID).value();
ReportUwS report_found_uws1;
ForceInstalledExtension matched1(report_found_extension1,
POLICY_EXTENSION_FORCELIST, "*", "");
ASSERT_TRUE(ReportUwSWithFile(kUwSId1, L"bad_file.exe", &report_found_uws1));
ExtensionID report_found_extension2 =
ExtensionID::Create(kGoogleTestBUwEID).value();
ReportUwS report_found_uws2;
ForceInstalledExtension matched2(report_found_extension2,
POLICY_EXTENSION_FORCELIST, "*", "");
ASSERT_TRUE(ReportUwSWithFile(kUwSId2, L"bad.bat", &report_found_uws2));
UwEMatcher matcher1;
matcher1.add_uws_id(kGoogleTestAUwSID);
matcher1.set_flags(FoilUwSData_Attribute_FLAGS_STATE_CONFIRMED_UWS);
UwEMatcher_MatcherCriteria criteria1;
criteria1.add_extension_id(
ExtensionID::Create(kGoogleTestAUwEID).value().AsString());
criteria1.add_update_url("*");
criteria1.add_install_method(UwEMatcher::POLICY_EXTENSION_FORCELIST);
*matcher1.mutable_criteria() = criteria1;
UwEMatcher matcher2;
matcher2.add_uws_id(kGoogleTestBUwSID);
matcher2.set_flags(FoilUwSData_Attribute_FLAGS_STATE_CONFIRMED_UWS);
UwEMatcher_MatcherCriteria criteria2;
criteria2.add_extension_id(
ExtensionID::Create(kGoogleTestBUwEID).value().AsString());
criteria2.add_update_url("*");
criteria2.add_install_method(UwEMatcher::POLICY_EXTENSION_FORCELIST);
*matcher2.mutable_criteria() = criteria2;
UwEMatchers matchers;
*matchers.mutable_uwe_matcher()->Add() = matcher1;
*matchers.mutable_uwe_matcher()->Add() = matcher2;
const std::vector<ForceInstalledExtension> extensions{matched1, matched2};
scanner_ = std::make_unique<UwEScannerWrapper>(
std::make_unique<ScannerImpl>(mock_engine_client_.get()), &matchers,
extensions);
ExpectStartScanCall(DoAll(
WithArg<kFoundUwSCallbackPos>(Invoke(report_found_uws1)),
WithArg<kFoundUwSCallbackPos>(Invoke(report_found_uws2)),
WithArg<kDoneCallbackPos>(Invoke(ReportDone(EngineResultCode::kSuccess))),
Return(EngineResultCode::kSuccess)));
RunScanAndExpect(RESULT_CODE_SUCCESS, {kUwSId1, kUwSId2},
{report_found_extension1, report_found_extension2});
}
TEST_F(UwEScannerWrapperTest, ScanFailure) {
const std::vector<ForceInstalledExtension> extensions;
scanner_ = std::make_unique<UwEScannerWrapper>(
std::make_unique<ScannerImpl>(mock_engine_client_.get()), &matchers_,
extensions);
ExpectStartScanCall(DoAll(WithArg<kDoneCallbackPos>(Invoke(
ReportDone(EngineResultCode::kEngineInternal))),
Return(EngineResultCode::kSuccess)));
RunScanAndExpect(RESULT_CODE_SCANNING_ENGINE_ERROR, {}, {});
}
} // namespace chrome_cleaner