blob: b973162c8c13c35092926a085915fe9adf6a06f9 [file] [log] [blame]
// Copyright (c) 2012 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/download_protection/download_protection_service.h"
#include <stddef.h>
#include <stdint.h>
#include <map>
#include <memory>
#include <string>
#include "base/base_paths.h"
#include "base/bind.h"
#include "base/callback.h"
#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/location.h"
#include "base/macros.h"
#include "base/memory/ref_counted.h"
#include "base/path_service.h"
#include "base/run_loop.h"
#include "base/single_thread_task_runner.h"
#include "base/strings/string_number_conversions.h"
#include "base/task/post_task.h"
#include "base/task/task_scheduler/task_scheduler.h"
#include "base/test/bind_test_util.h"
#include "base/threading/thread_task_runner_handle.h"
#include "build/build_config.h"
#include "chrome/browser/extensions/api/safe_browsing_private/safe_browsing_private_event_router_factory.h"
#include "chrome/browser/history/history_service_factory.h"
#include "chrome/browser/safe_browsing/download_protection/download_feedback_service.h"
#include "chrome/browser/safe_browsing/download_protection/download_protection_util.h"
#include "chrome/browser/safe_browsing/download_protection/ppapi_download_request.h"
#include "chrome/browser/safe_browsing/incident_reporting/incident_reporting_service.h"
#include "chrome/browser/safe_browsing/safe_browsing_navigation_observer.h"
#include "chrome/browser/safe_browsing/safe_browsing_service.h"
#include "chrome/browser/safe_browsing/test_extension_event_observer.h"
#include "chrome/browser/safe_browsing/test_safe_browsing_service.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/extensions/api/safe_browsing_private.h"
#include "chrome/common/safe_browsing/binary_feature_extractor.h"
#include "chrome/common/safe_browsing/file_type_policies_test_util.h"
#include "chrome/common/safe_browsing/mock_binary_feature_extractor.h"
#include "chrome/test/base/chrome_render_view_host_test_harness.h"
#include "chrome/test/base/testing_browser_process.h"
#include "chrome/test/base/testing_profile.h"
#include "components/download/public/common/download_danger_type.h"
#include "components/download/public/common/mock_download_item.h"
#include "components/history/core/browser/history_service.h"
#include "components/prefs/pref_service.h"
#include "components/prefs/scoped_user_pref_update.h"
#include "components/safe_browsing/common/safe_browsing_prefs.h"
#include "components/safe_browsing/common/safebrowsing_switches.h"
#include "components/safe_browsing/db/database_manager.h"
#include "components/safe_browsing/db/test_database_manager.h"
#include "components/safe_browsing/db/v4_protocol_manager_util.h"
#include "components/safe_browsing/proto/csd.pb.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/download_item_utils.h"
#include "content/public/browser/page_navigator.h"
#include "content/public/test/navigation_simulator.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "content/public/test/test_utils.h"
#include "content/public/test/web_contents_tester.h"
#include "extensions/browser/test_event_router.h"
#include "net/base/url_util.h"
#include "net/cert/x509_certificate.h"
#include "net/cert/x509_util.h"
#include "net/http/http_status_code.h"
#include "net/url_request/url_request_test_util.h"
#include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
#include "services/network/test/test_url_loader_factory.h"
#include "services/network/test/test_utils.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/zlib/google/zip.h"
#include "url/gurl.h"
using base::RunLoop;
using content::BrowserThread;
using ::testing::_;
using ::testing::Assign;
using ::testing::ContainerEq;
using ::testing::DoAll;
using ::testing::ElementsAre;
using ::testing::Invoke;
using ::testing::Mock;
using ::testing::NiceMock;
using ::testing::NotNull;
using ::testing::Return;
using ::testing::ReturnRef;
using ::testing::SaveArg;
using ::testing::SizeIs;
using ::testing::StrictMock;
namespace OnDangerousDownloadOpened =
extensions::api::safe_browsing_private::OnDangerousDownloadOpened;
namespace safe_browsing {
namespace {
// A SafeBrowsingDatabaseManager implementation that returns a fixed result for
// a given URL.
class MockSafeBrowsingDatabaseManager : public TestSafeBrowsingDatabaseManager {
public:
MockSafeBrowsingDatabaseManager() {}
MOCK_METHOD1(MatchDownloadWhitelistUrl, bool(const GURL&));
MOCK_METHOD1(MatchDownloadWhitelistString, bool(const std::string&));
MOCK_METHOD2(CheckDownloadUrl,
bool(const std::vector<GURL>& url_chain,
SafeBrowsingDatabaseManager::Client* client));
private:
~MockSafeBrowsingDatabaseManager() override {}
DISALLOW_COPY_AND_ASSIGN(MockSafeBrowsingDatabaseManager);
};
class FakeSafeBrowsingService : public TestSafeBrowsingService {
public:
FakeSafeBrowsingService()
: test_shared_loader_factory_(
base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>(
&test_url_loader_factory_)),
download_report_count_(0) {
services_delegate_ = ServicesDelegate::CreateForTest(this, this);
mock_database_manager_ = new MockSafeBrowsingDatabaseManager();
}
// Returned pointer has the same lifespan as the database_manager_ refcounted
// object.
MockSafeBrowsingDatabaseManager* mock_database_manager() {
return mock_database_manager_.get();
}
scoped_refptr<network::SharedURLLoaderFactory> GetURLLoaderFactory()
override {
return test_shared_loader_factory_;
}
void SendSerializedDownloadReport(const std::string& unused_report) override {
download_report_count_++;
}
network::TestURLLoaderFactory* test_url_loader_factory() {
return &test_url_loader_factory_;
}
int download_report_count() { return download_report_count_; }
protected:
~FakeSafeBrowsingService() override {}
void RegisterAllDelayedAnalysis() override {}
private:
// ServicesDelegate::ServicesCreator:
bool CanCreateDatabaseManager() override { return true; }
bool CanCreateIncidentReportingService() override { return true; }
safe_browsing::SafeBrowsingDatabaseManager* CreateDatabaseManager() override {
return mock_database_manager_.get();
}
IncidentReportingService* CreateIncidentReportingService() override {
return new IncidentReportingService(nullptr);
}
network::TestURLLoaderFactory test_url_loader_factory_;
scoped_refptr<network::SharedURLLoaderFactory> test_shared_loader_factory_;
scoped_refptr<MockSafeBrowsingDatabaseManager> mock_database_manager_;
int download_report_count_;
DISALLOW_COPY_AND_ASSIGN(FakeSafeBrowsingService);
};
using NiceMockDownloadItem = NiceMock<download::MockDownloadItem>;
} // namespace
ACTION_P(SetCertificateContents, contents) {
arg1->add_certificate_chain()->add_element()->set_certificate(contents);
}
ACTION_P(SetDosHeaderContents, contents) {
arg2->mutable_pe_headers()->set_dos_header(contents);
return true;
}
ACTION_P(TrustSignature, contents) {
arg1->set_trusted(true);
// Add a certificate chain. Note that we add the certificate twice so that
// it appears as its own issuer.
ClientDownloadRequest_CertificateChain* chain = arg1->add_certificate_chain();
chain->add_element()->set_certificate(contents.data(), contents.size());
chain->add_element()->set_certificate(contents.data(), contents.size());
}
ACTION_P(CheckDownloadUrlDone, threat_type) {
base::PostTaskWithTraits(
FROM_HERE, {BrowserThread::IO},
base::BindOnce(
&SafeBrowsingDatabaseManager::Client::OnCheckDownloadUrlResult,
base::Unretained(arg1), arg0, threat_type));
}
class DownloadProtectionServiceTest : public ChromeRenderViewHostTestHarness {
protected:
DownloadProtectionServiceTest() {}
void SetUp() override {
ChromeRenderViewHostTestHarness::SetUp();
system_request_context_getter_ =
base::MakeRefCounted<net::TestURLRequestContextGetter>(
base::CreateSingleThreadTaskRunnerWithTraits(
{content::BrowserThread::IO}));
TestingBrowserProcess::GetGlobal()->SetSystemRequestContext(
system_request_context_getter_.get());
in_process_utility_thread_helper_ =
std::make_unique<content::InProcessUtilityThreadHelper>();
// Start real threads for the IO and File threads so that the DCHECKs
// to test that we're on the correct thread work.
sb_service_ = new StrictMock<FakeSafeBrowsingService>();
sb_service_->Initialize();
TestingBrowserProcess::GetGlobal()->SetSafeBrowsingService(
sb_service_.get());
binary_feature_extractor_ = new StrictMock<MockBinaryFeatureExtractor>();
ON_CALL(*binary_feature_extractor_, ExtractImageFeatures(_, _, _, _))
.WillByDefault(Return(true));
download_service_ = sb_service_->download_protection_service();
download_service_->binary_feature_extractor_ = binary_feature_extractor_;
download_service_->SetEnabled(true);
client_download_request_subscription_ =
download_service_->RegisterClientDownloadRequestCallback(
base::Bind(&DownloadProtectionServiceTest::OnClientDownloadRequest,
base::Unretained(this)));
ppapi_download_request_subscription_ =
download_service_->RegisterPPAPIDownloadRequestCallback(
base::Bind(&DownloadProtectionServiceTest::OnPPAPIDownloadRequest,
base::Unretained(this)));
RunLoop().RunUntilIdle();
has_result_ = false;
base::FilePath source_path;
ASSERT_TRUE(base::PathService::Get(base::DIR_SOURCE_ROOT, &source_path));
testdata_path_ = source_path.AppendASCII("chrome")
.AppendASCII("test")
.AppendASCII("data")
.AppendASCII("safe_browsing")
.AppendASCII("download_protection");
ASSERT_TRUE(profile()->CreateHistoryService(true /* delete_file */,
false /* no_db */));
// Setup a directory to place test files in.
ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
// Turn off binary sampling by default.
SetBinarySamplingProbability(0.0);
// |test_event_router_| is owned by KeyedServiceFactory.
test_event_router_ = extensions::CreateAndUseTestEventRouter(profile());
extensions::SafeBrowsingPrivateEventRouterFactory::GetInstance()
->SetTestingFactory(
profile(),
base::BindRepeating(&BuildSafeBrowsingPrivateEventRouter));
}
void TearDown() override {
client_download_request_subscription_.reset();
ppapi_download_request_subscription_.reset();
sb_service_->ShutDown();
// Flush all of the thread message loops to ensure that there are no
// tasks currently running.
FlushThreadMessageLoops();
sb_service_ = NULL;
TestingBrowserProcess::GetGlobal()->SetSystemRequestContext(nullptr);
TestingBrowserProcess::GetGlobal()->SetSafeBrowsingService(nullptr);
system_request_context_getter_ = nullptr;
in_process_utility_thread_helper_ = nullptr;
ChromeRenderViewHostTestHarness::TearDown();
}
void SetWhitelistedDownloadSampleRate(double target_rate) {
download_service_->whitelist_sample_rate_ = target_rate;
}
void SetBinarySamplingProbability(double target_rate) {
std::unique_ptr<DownloadFileTypeConfig> config =
policies_.DuplicateConfig();
config->set_sampled_ping_probability(target_rate);
policies_.SwapConfig(config);
}
bool RequestContainsResource(const ClientDownloadRequest& request,
ClientDownloadRequest::ResourceType type,
const std::string& url,
const std::string& referrer) {
for (int i = 0; i < request.resources_size(); ++i) {
if (request.resources(i).url() == url &&
request.resources(i).type() == type &&
(referrer.empty() || request.resources(i).referrer() == referrer)) {
return true;
}
}
return false;
}
// At this point we only set the server IP for the download itself.
bool RequestContainsServerIp(const ClientDownloadRequest& request,
const std::string& remote_address) {
for (int i = 0; i < request.resources_size(); ++i) {
// We want the last DOWNLOAD_URL in the chain.
if (request.resources(i).type() == ClientDownloadRequest::DOWNLOAD_URL &&
(i + 1 == request.resources_size() ||
request.resources(i + 1).type() !=
ClientDownloadRequest::DOWNLOAD_URL)) {
return remote_address == request.resources(i).remote_ip();
}
}
return false;
}
static const ClientDownloadRequest_ArchivedBinary* GetRequestArchivedBinary(
const ClientDownloadRequest& request,
const std::string& file_basename) {
for (const auto& archived_binary : request.archived_binary()) {
if (archived_binary.file_basename() == file_basename)
return &archived_binary;
}
return nullptr;
}
// Flushes any pending tasks in the message loops of all threads.
void FlushThreadMessageLoops() {
base::TaskScheduler::GetInstance()->FlushForTesting();
FlushMessageLoop(BrowserThread::IO);
RunLoop().RunUntilIdle();
}
// Reads a single PEM-encoded certificate from the testdata directory.
// Returns NULL on failure.
scoped_refptr<net::X509Certificate> ReadTestCertificate(
const std::string& filename) {
std::string cert_data;
if (!base::ReadFileToString(testdata_path_.AppendASCII(filename),
&cert_data)) {
return NULL;
}
net::CertificateList certs =
net::X509Certificate::CreateCertificateListFromBytes(
cert_data.data(), cert_data.size(),
net::X509Certificate::FORMAT_PEM_CERT_SEQUENCE);
return certs.empty() ? NULL : certs[0];
}
const ClientDownloadRequest* GetClientDownloadRequest() const {
return last_client_download_request_.get();
}
bool HasClientDownloadRequest() const {
return last_client_download_request_.get() != NULL;
}
void ClearClientDownloadRequest() { last_client_download_request_.reset(); }
void PrepareResponse(ClientDownloadResponse::Verdict verdict,
net::HttpStatusCode response_code,
int net_error,
bool upload_requested = false) {
if (net_error != net::OK) {
network::URLLoaderCompletionStatus status;
sb_service_->test_url_loader_factory()->AddResponse(
PPAPIDownloadRequest::GetDownloadRequestUrl(),
network::ResourceResponseHead(), std::string(),
network::URLLoaderCompletionStatus(net_error));
return;
}
ClientDownloadResponse response;
response.set_verdict(verdict);
response.set_upload(upload_requested);
sb_service_->test_url_loader_factory()->AddResponse(
PPAPIDownloadRequest::GetDownloadRequestUrl().spec(),
response.SerializeAsString());
}
void PrepareBasicDownloadItem(
NiceMockDownloadItem* item,
const std::vector<std::string> url_chain_items,
const std::string& referrer_url,
const base::FilePath::StringType& tmp_path_literal,
const base::FilePath::StringType& final_path_literal) {
base::FilePath tmp_path = temp_dir_.GetPath().Append(tmp_path_literal);
base::FilePath final_path = temp_dir_.GetPath().Append(final_path_literal);
PrepareBasicDownloadItemWithFullPaths(item, url_chain_items, referrer_url,
tmp_path, final_path);
}
void PrepareBasicDownloadItemWithFullPaths(
NiceMockDownloadItem* item,
const std::vector<std::string> url_chain_items,
const std::string& referrer_url,
const base::FilePath& tmp_full_path,
const base::FilePath& final_full_path) {
url_chain_.clear();
for (std::string item : url_chain_items)
url_chain_.push_back(GURL(item));
if (url_chain_.empty())
url_chain_.push_back(GURL());
referrer_ = GURL(referrer_url);
tmp_path_ = tmp_full_path;
final_path_ = final_full_path;
hash_ = "hash";
EXPECT_CALL(*item, GetURL()).WillRepeatedly(ReturnRef(url_chain_.back()));
EXPECT_CALL(*item, GetFullPath()).WillRepeatedly(ReturnRef(tmp_path_));
EXPECT_CALL(*item, GetTargetFilePath())
.WillRepeatedly(ReturnRef(final_path_));
EXPECT_CALL(*item, GetUrlChain()).WillRepeatedly(ReturnRef(url_chain_));
EXPECT_CALL(*item, GetReferrerUrl()).WillRepeatedly(ReturnRef(referrer_));
EXPECT_CALL(*item, GetTabUrl())
.WillRepeatedly(ReturnRef(GURL::EmptyGURL()));
EXPECT_CALL(*item, GetTabReferrerUrl())
.WillRepeatedly(ReturnRef(GURL::EmptyGURL()));
EXPECT_CALL(*item, GetHash()).WillRepeatedly(ReturnRef(hash_));
EXPECT_CALL(*item, GetReceivedBytes()).WillRepeatedly(Return(100));
EXPECT_CALL(*item, HasUserGesture()).WillRepeatedly(Return(true));
EXPECT_CALL(*item, GetRemoteAddress()).WillRepeatedly(Return(""));
}
void AddDomainToEnterpriseWhitelist(const std::string& domain) {
ListPrefUpdate update(profile()->GetPrefs(),
prefs::kSafeBrowsingWhitelistDomains);
update.Get()->AppendString(domain);
}
// Helper function to simulate a user gesture, then a link click.
// The usual NavigateAndCommit is unsuitable because it creates
// browser-initiated navigations, causing us to drop the referrer.
// TODO(drubery): This function could be eliminated if we dropped referrer
// depending on PageTransition. This could help eliminate edge cases in
// browser/renderer navigations.
void SimulateLinkClick(const GURL& url) {
content::WebContentsTester::For(web_contents())
->TestDidReceiveInputEvent(blink::WebInputEvent::kMouseDown);
std::unique_ptr<content::NavigationSimulator> navigation =
content::NavigationSimulator::CreateRendererInitiated(
url, web_contents()->GetMainFrame());
navigation->SetTransition(ui::PAGE_TRANSITION_LINK);
navigation->Commit();
}
private:
// Helper functions for FlushThreadMessageLoops.
void RunAllPendingAndQuitUI(const base::Closure& quit_closure) {
RunLoop().RunUntilIdle();
base::PostTaskWithTraits(FROM_HERE, {BrowserThread::UI}, quit_closure);
}
void PostRunMessageLoopTask(BrowserThread::ID thread,
const base::Closure& quit_closure) {
base::PostTaskWithTraits(
FROM_HERE, {thread},
base::BindOnce(&DownloadProtectionServiceTest::RunAllPendingAndQuitUI,
base::Unretained(this), quit_closure));
}
void FlushMessageLoop(BrowserThread::ID thread) {
RunLoop run_loop;
base::PostTaskWithTraits(
FROM_HERE, {BrowserThread::UI},
base::BindOnce(&DownloadProtectionServiceTest::PostRunMessageLoopTask,
base::Unretained(this), thread, run_loop.QuitClosure()));
run_loop.Run();
}
void OnClientDownloadRequest(download::DownloadItem* download,
const ClientDownloadRequest* request) {
if (request)
last_client_download_request_.reset(new ClientDownloadRequest(*request));
else
last_client_download_request_.reset();
}
void OnPPAPIDownloadRequest(const ClientDownloadRequest* request) {
if (request)
last_client_download_request_.reset(new ClientDownloadRequest(*request));
else
last_client_download_request_.reset();
}
public:
enum ArchiveType { ZIP, DMG };
void CheckDoneCallback(const base::Closure& quit_closure,
DownloadCheckResult result) {
result_ = result;
has_result_ = true;
quit_closure.Run();
}
void SyncCheckDoneCallback(DownloadCheckResult result) {
result_ = result;
has_result_ = true;
}
testing::AssertionResult IsResult(DownloadCheckResult expected) {
if (!has_result_)
return testing::AssertionFailure() << "No result";
has_result_ = false;
return result_ == expected ? testing::AssertionSuccess()
: testing::AssertionFailure()
<< "Expected "
<< static_cast<int>(expected) << ", got "
<< static_cast<int>(result_);
}
void SetExtendedReportingPreference(bool is_extended_reporting) {
SetExtendedReportingPref(profile()->GetPrefs(), is_extended_reporting);
}
// Verify that corrupted ZIP/DMGs do send a ping.
void CheckClientDownloadReportCorruptArchive(ArchiveType type);
protected:
// This will effectivly mask the global Singleton while this is in scope.
FileTypePoliciesTestOverlay policies_;
scoped_refptr<FakeSafeBrowsingService> sb_service_;
scoped_refptr<net::URLRequestContextGetter> system_request_context_getter_;
scoped_refptr<MockBinaryFeatureExtractor> binary_feature_extractor_;
DownloadProtectionService* download_service_;
DownloadCheckResult result_;
bool has_result_;
std::unique_ptr<content::InProcessUtilityThreadHelper>
in_process_utility_thread_helper_;
base::FilePath testdata_path_;
ClientDownloadRequestSubscription client_download_request_subscription_;
PPAPIDownloadRequestSubscription ppapi_download_request_subscription_;
std::unique_ptr<ClientDownloadRequest> last_client_download_request_;
// The following 5 fields are used by PrepareBasicDownloadItem() function to
// store attributes of the last download item. They can be modified
// afterwards and the *item will return the new values.
std::vector<GURL> url_chain_;
GURL referrer_;
base::FilePath tmp_path_;
base::FilePath final_path_;
std::string hash_;
base::ScopedTempDir temp_dir_;
extensions::TestEventRouter* test_event_router_;
};
void DownloadProtectionServiceTest::CheckClientDownloadReportCorruptArchive(
ArchiveType type) {
PrepareResponse(ClientDownloadResponse::SAFE, net::HTTP_OK,
net::URLRequestStatus::SUCCESS);
NiceMockDownloadItem item;
if (type == ZIP) {
PrepareBasicDownloadItem(&item, {"http://www.evil.com/a.zip"}, // url_chain
"http://www.google.com/", // referrer
FILE_PATH_LITERAL("a.tmp"), // tmp_path
FILE_PATH_LITERAL("a.zip")); // final_path
} else if (type == DMG) {
PrepareBasicDownloadItem(&item, {"http://www.evil.com/a.dmg"}, // url_chain
"http://www.google.com/", // referrer
FILE_PATH_LITERAL("a.tmp"), // tmp_path
FILE_PATH_LITERAL("a.dmg")); // final_path
}
std::string file_contents = "corrupt archive file";
ASSERT_EQ(
static_cast<int>(file_contents.size()),
base::WriteFile(tmp_path_, file_contents.data(), file_contents.size()));
RunLoop run_loop;
download_service_->CheckClientDownload(
&item, base::Bind(&DownloadProtectionServiceTest::CheckDoneCallback,
base::Unretained(this), run_loop.QuitClosure()));
run_loop.Run();
ASSERT_TRUE(HasClientDownloadRequest());
EXPECT_EQ(0, GetClientDownloadRequest()->archived_binary_size());
EXPECT_TRUE(GetClientDownloadRequest()->has_download_type());
ClientDownloadRequest::DownloadType expected_type =
type == ZIP
? ClientDownloadRequest_DownloadType_INVALID_ZIP
: ClientDownloadRequest_DownloadType_MAC_ARCHIVE_FAILED_PARSING;
EXPECT_EQ(expected_type, GetClientDownloadRequest()->download_type());
ClearClientDownloadRequest();
Mock::VerifyAndClearExpectations(sb_service_.get());
Mock::VerifyAndClearExpectations(binary_feature_extractor_.get());
}
// TODO(crbug.com/721964): Create specific unit tests for
// check_client_download_request.*, download_url_sb_client.*, and
// ppapi_download_request.*.
TEST_F(DownloadProtectionServiceTest, CheckClientDownloadInvalidUrl) {
NiceMockDownloadItem item;
{
PrepareBasicDownloadItem(&item,
std::vector<std::string>(), // empty url_chain
"http://www.google.com/", // referrer
FILE_PATH_LITERAL("a.tmp"), // tmp_path
FILE_PATH_LITERAL("a.exe")); // final_path
RunLoop run_loop;
download_service_->CheckClientDownload(
&item, base::Bind(&DownloadProtectionServiceTest::CheckDoneCallback,
base::Unretained(this), run_loop.QuitClosure()));
run_loop.Run();
EXPECT_TRUE(IsResult(DownloadCheckResult::UNKNOWN));
EXPECT_FALSE(HasClientDownloadRequest());
Mock::VerifyAndClearExpectations(&item);
}
{
PrepareBasicDownloadItem(&item, {"file://www.google.com/"}, // url_chain
"http://www.google.com/", // referrer
FILE_PATH_LITERAL("a.tmp"), // tmp_path
FILE_PATH_LITERAL("a.exe")); // final_path
RunLoop run_loop;
download_service_->CheckClientDownload(
&item, base::Bind(&DownloadProtectionServiceTest::CheckDoneCallback,
base::Unretained(this), run_loop.QuitClosure()));
run_loop.Run();
EXPECT_TRUE(IsResult(DownloadCheckResult::UNKNOWN));
EXPECT_FALSE(HasClientDownloadRequest());
}
}
TEST_F(DownloadProtectionServiceTest, CheckClientDownloadNotABinary) {
NiceMockDownloadItem item;
PrepareBasicDownloadItem(&item,
std::vector<std::string>(), // empty url_chain
"http://www.google.com/", // referrer
FILE_PATH_LITERAL("a.tmp"), // tmp_path
FILE_PATH_LITERAL("a.txt")); // final_path
RunLoop run_loop;
download_service_->CheckClientDownload(
&item, base::Bind(&DownloadProtectionServiceTest::CheckDoneCallback,
base::Unretained(this), run_loop.QuitClosure()));
run_loop.Run();
EXPECT_TRUE(IsResult(DownloadCheckResult::UNKNOWN));
EXPECT_FALSE(HasClientDownloadRequest());
}
TEST_F(DownloadProtectionServiceTest,
CheckClientDownloadWhitelistedUrlWithoutSampling) {
// Response to any requests will be DANGEROUS.
PrepareResponse(ClientDownloadResponse::DANGEROUS, net::HTTP_OK,
net::URLRequestStatus::SUCCESS);
NiceMockDownloadItem item;
PrepareBasicDownloadItem(&item,
std::vector<std::string>(), // empty url_chain
"", // referrer
FILE_PATH_LITERAL("a.tmp"), // tmp_path
FILE_PATH_LITERAL("a.exe")); // final_path
EXPECT_CALL(*binary_feature_extractor_.get(), CheckSignature(tmp_path_, _))
.Times(3);
EXPECT_CALL(*binary_feature_extractor_.get(),
ExtractImageFeatures(
tmp_path_, BinaryFeatureExtractor::kDefaultOptions, _, _))
.Times(3);
// We should not get whilelist checks for other URLs than specified below.
EXPECT_CALL(*sb_service_->mock_database_manager(),
MatchDownloadWhitelistUrl(_))
.Times(0);
EXPECT_CALL(*sb_service_->mock_database_manager(),
MatchDownloadWhitelistUrl(GURL("http://www.evil.com/bla.exe")))
.WillRepeatedly(Return(false));
EXPECT_CALL(*sb_service_->mock_database_manager(),
MatchDownloadWhitelistUrl(GURL("http://www.google.com/a.exe")))
.WillRepeatedly(Return(true));
// Set sample rate to 0 to prevent sampling.
SetWhitelistedDownloadSampleRate(0);
{
// With no referrer and just the bad url, should be marked DANGEROUS.
url_chain_.push_back(GURL("http://www.evil.com/bla.exe"));
RunLoop run_loop;
download_service_->CheckClientDownload(
&item, base::Bind(&DownloadProtectionServiceTest::CheckDoneCallback,
base::Unretained(this), run_loop.QuitClosure()));
run_loop.Run();
EXPECT_TRUE(IsResult(DownloadCheckResult::DANGEROUS));
ASSERT_TRUE(HasClientDownloadRequest());
EXPECT_FALSE(GetClientDownloadRequest()->skipped_url_whitelist());
EXPECT_FALSE(GetClientDownloadRequest()->skipped_certificate_whitelist());
ClearClientDownloadRequest();
}
{
// Check that the referrer is not matched against the whitelist.
referrer_ = GURL("http://www.google.com/");
RunLoop run_loop;
download_service_->CheckClientDownload(
&item, base::Bind(&DownloadProtectionServiceTest::CheckDoneCallback,
base::Unretained(this), run_loop.QuitClosure()));
run_loop.Run();
EXPECT_TRUE(IsResult(DownloadCheckResult::DANGEROUS));
ASSERT_TRUE(HasClientDownloadRequest());
EXPECT_FALSE(GetClientDownloadRequest()->skipped_url_whitelist());
EXPECT_FALSE(GetClientDownloadRequest()->skipped_certificate_whitelist());
ClearClientDownloadRequest();
}
{
// Redirect from a site shouldn't be checked either.
url_chain_.insert(url_chain_.begin(),
GURL("http://www.google.com/redirect"));
RunLoop run_loop;
download_service_->CheckClientDownload(
&item, base::Bind(&DownloadProtectionServiceTest::CheckDoneCallback,
base::Unretained(this), run_loop.QuitClosure()));
run_loop.Run();
EXPECT_TRUE(IsResult(DownloadCheckResult::DANGEROUS));
ASSERT_TRUE(HasClientDownloadRequest());
EXPECT_FALSE(GetClientDownloadRequest()->skipped_url_whitelist());
EXPECT_FALSE(GetClientDownloadRequest()->skipped_certificate_whitelist());
ClearClientDownloadRequest();
}
{
// Only if the final url is whitelisted should it be SAFE.
url_chain_.push_back(GURL("http://www.google.com/a.exe"));
RunLoop run_loop;
download_service_->CheckClientDownload(
&item, base::Bind(&DownloadProtectionServiceTest::CheckDoneCallback,
base::Unretained(this), run_loop.QuitClosure()));
run_loop.Run();
EXPECT_TRUE(IsResult(DownloadCheckResult::SAFE));
// TODO(grt): Make the service produce the request even when the URL is
// whitelisted.
EXPECT_FALSE(HasClientDownloadRequest());
}
}
TEST_F(DownloadProtectionServiceTest,
CheckClientDownloadWhitelistedUrlWithSampling) {
// Server responses "SAFE" to every requests coming from whitelisted
// download.
PrepareResponse(ClientDownloadResponse::SAFE, net::HTTP_OK,
net::URLRequestStatus::SUCCESS);
NiceMockDownloadItem item;
PrepareBasicDownloadItem(&item,
std::vector<std::string>(), // empty url_chain
"http://www.google.com/", // referrer
FILE_PATH_LITERAL("a.tmp"), // tmp_path
FILE_PATH_LITERAL("a.exe")); // final_path
EXPECT_CALL(*binary_feature_extractor_.get(), CheckSignature(tmp_path_, _))
.Times(1);
EXPECT_CALL(*binary_feature_extractor_.get(),
ExtractImageFeatures(
tmp_path_, BinaryFeatureExtractor::kDefaultOptions, _, _))
.Times(3);
// Assume http://www.whitelist.com/a.exe is on the whitelist.
EXPECT_CALL(*sb_service_->mock_database_manager(),
MatchDownloadWhitelistUrl(_))
.Times(0);
EXPECT_CALL(*sb_service_->mock_database_manager(),
MatchDownloadWhitelistUrl(GURL("http://www.whitelist.com/a.exe")))
.WillRepeatedly(Return(true));
url_chain_.push_back(GURL("http://www.whitelist.com/a.exe"));
// Set sample rate to 1.00, so download_service_ will always send download
// pings for whitelisted downloads.
SetWhitelistedDownloadSampleRate(1.00);
{
// Case (1): is_extended_reporting && is_incognito.
// ClientDownloadRequest should NOT be sent.
SetExtendedReportingPreference(true);
content::DownloadItemUtils::AttachInfo(
&item, profile()->GetOffTheRecordProfile(), nullptr);
RunLoop run_loop;
download_service_->CheckClientDownload(
&item, base::Bind(&DownloadProtectionServiceTest::CheckDoneCallback,
base::Unretained(this), run_loop.QuitClosure()));
run_loop.Run();
EXPECT_TRUE(IsResult(DownloadCheckResult::SAFE));
EXPECT_FALSE(HasClientDownloadRequest());
}
{
// Case (2): !is_extended_reporting && is_incognito.
// ClientDownloadRequest should NOT be sent.
SetExtendedReportingPreference(false);
content::DownloadItemUtils::AttachInfo(
&item, profile()->GetOffTheRecordProfile(), nullptr);
RunLoop run_loop;
download_service_->CheckClientDownload(
&item, base::Bind(&DownloadProtectionServiceTest::CheckDoneCallback,
base::Unretained(this), run_loop.QuitClosure()));
run_loop.Run();
EXPECT_TRUE(IsResult(DownloadCheckResult::SAFE));
EXPECT_FALSE(HasClientDownloadRequest());
}
{
// Case (3): !is_extended_reporting && !is_incognito.
// ClientDownloadRequest should NOT be sent.
content::DownloadItemUtils::AttachInfo(&item, profile(), nullptr);
RunLoop run_loop;
download_service_->CheckClientDownload(
&item, base::Bind(&DownloadProtectionServiceTest::CheckDoneCallback,
base::Unretained(this), run_loop.QuitClosure()));
run_loop.Run();
EXPECT_TRUE(IsResult(DownloadCheckResult::SAFE));
EXPECT_FALSE(HasClientDownloadRequest());
}
{
// Case (4): is_extended_reporting && !is_incognito &&
// Download matches URL whitelist.
// ClientDownloadRequest should be sent.
SetExtendedReportingPreference(true);
content::DownloadItemUtils::AttachInfo(&item, profile(), nullptr);
RunLoop run_loop;
download_service_->CheckClientDownload(
&item, base::Bind(&DownloadProtectionServiceTest::CheckDoneCallback,
base::Unretained(this), run_loop.QuitClosure()));
run_loop.Run();
EXPECT_TRUE(IsResult(DownloadCheckResult::SAFE));
ASSERT_TRUE(HasClientDownloadRequest());
EXPECT_TRUE(GetClientDownloadRequest()->skipped_url_whitelist());
EXPECT_FALSE(GetClientDownloadRequest()->skipped_certificate_whitelist());
ClearClientDownloadRequest();
}
// Setup trusted and whitelisted certificates for test cases (5) and (6).
scoped_refptr<net::X509Certificate> test_cert(
ReadTestCertificate("test_cn.pem"));
ASSERT_TRUE(test_cert.get());
std::string test_cert_der(
net::x509_util::CryptoBufferAsStringPiece(test_cert->cert_buffer()));
EXPECT_CALL(*binary_feature_extractor_.get(), CheckSignature(tmp_path_, _))
.WillRepeatedly(TrustSignature(test_cert_der));
EXPECT_CALL(*sb_service_->mock_database_manager(),
MatchDownloadWhitelistString(_))
.WillRepeatedly(Return(true));
{
// Case (5): is_extended_reporting && !is_incognito &&
// Download matches certificate whitelist.
// ClientDownloadRequest should be sent.
content::DownloadItemUtils::AttachInfo(&item, profile(), nullptr);
EXPECT_CALL(
*sb_service_->mock_database_manager(),
MatchDownloadWhitelistUrl(GURL("http://www.whitelist.com/a.exe")))
.WillRepeatedly(Return(false));
RunLoop run_loop;
download_service_->CheckClientDownload(
&item, base::Bind(&DownloadProtectionServiceTest::CheckDoneCallback,
base::Unretained(this), run_loop.QuitClosure()));
run_loop.Run();
EXPECT_TRUE(IsResult(DownloadCheckResult::SAFE));
ASSERT_TRUE(HasClientDownloadRequest());
EXPECT_FALSE(GetClientDownloadRequest()->skipped_url_whitelist());
EXPECT_TRUE(GetClientDownloadRequest()->skipped_certificate_whitelist());
ClearClientDownloadRequest();
}
{
// Case (6): is_extended_reporting && !is_incognito &&
// Download matches both URL and certificate whitelists.
// ClientDownloadRequest should be sent.
content::DownloadItemUtils::AttachInfo(&item, profile(), nullptr);
EXPECT_CALL(
*sb_service_->mock_database_manager(),
MatchDownloadWhitelistUrl(GURL("http://www.whitelist.com/a.exe")))
.WillRepeatedly(Return(true));
RunLoop run_loop;
download_service_->CheckClientDownload(
&item, base::Bind(&DownloadProtectionServiceTest::CheckDoneCallback,
base::Unretained(this), run_loop.QuitClosure()));
run_loop.Run();
EXPECT_TRUE(IsResult(DownloadCheckResult::SAFE));
ASSERT_TRUE(HasClientDownloadRequest());
EXPECT_TRUE(GetClientDownloadRequest()->skipped_url_whitelist());
// Since download matches URL whitelist and gets sampled, no need to
// do certificate whitelist checking and sampling.
EXPECT_FALSE(GetClientDownloadRequest()->skipped_certificate_whitelist());
ClearClientDownloadRequest();
}
}
TEST_F(DownloadProtectionServiceTest, CheckClientDownloadSampledFile) {
// Server response will be discarded.
PrepareResponse(ClientDownloadResponse::DANGEROUS, net::HTTP_OK,
net::URLRequestStatus::SUCCESS);
NiceMockDownloadItem item;
PrepareBasicDownloadItem(
&item,
// Add paths so we can check they are properly removed.
{"http://referrer.com/1/2", "http://referrer.com/3/4",
"http://download.com/path/a.foobar_unknown_type"},
"http://referrer.com/3/4", // Referrer
FILE_PATH_LITERAL("a.tmp"), // tmp_path
FILE_PATH_LITERAL("a.foobar_unknown_type")); // final_path
EXPECT_CALL(*binary_feature_extractor_.get(), CheckSignature(tmp_path_, _))
.Times(1);
EXPECT_CALL(*binary_feature_extractor_.get(),
ExtractImageFeatures(
tmp_path_, BinaryFeatureExtractor::kDefaultOptions, _, _))
.Times(1);
// Set ping sample rate to 1.00 so download_service_ will always send a
// "light" ping for unknown types if allowed.
SetBinarySamplingProbability(1.0);
{
// Case (1): is_extended_reporting && is_incognito.
// ClientDownloadRequest should NOT be sent.
SetExtendedReportingPreference(true);
content::DownloadItemUtils::AttachInfo(
&item, profile()->GetOffTheRecordProfile(), nullptr);
RunLoop run_loop;
download_service_->CheckClientDownload(
&item, base::Bind(&DownloadProtectionServiceTest::CheckDoneCallback,
base::Unretained(this), run_loop.QuitClosure()));
run_loop.Run();
EXPECT_TRUE(IsResult(DownloadCheckResult::UNKNOWN));
EXPECT_FALSE(HasClientDownloadRequest());
}
{
// Case (2): is_extended_reporting && !is_incognito.
// A "light" ClientDownloadRequest should be sent.
content::DownloadItemUtils::AttachInfo(&item, profile(), nullptr);
RunLoop run_loop;
download_service_->CheckClientDownload(
&item, base::Bind(&DownloadProtectionServiceTest::CheckDoneCallback,
base::Unretained(this), run_loop.QuitClosure()));
run_loop.Run();
EXPECT_TRUE(IsResult(DownloadCheckResult::UNKNOWN));
ASSERT_TRUE(HasClientDownloadRequest());
// Verify it's a "light" ping, check that URLs don't have paths.
auto* req = GetClientDownloadRequest();
EXPECT_EQ(ClientDownloadRequest::SAMPLED_UNSUPPORTED_FILE,
req->download_type());
EXPECT_EQ(GURL(req->url()).GetOrigin().spec(), req->url());
for (auto resource : req->resources()) {
EXPECT_EQ(GURL(resource.url()).GetOrigin().spec(), resource.url());
EXPECT_EQ(GURL(resource.referrer()).GetOrigin().spec(),
resource.referrer());
}
ClearClientDownloadRequest();
}
{
// Case (3): !is_extended_reporting && is_incognito.
// ClientDownloadRequest should NOT be sent.
SetExtendedReportingPreference(false);
content::DownloadItemUtils::AttachInfo(
&item, profile()->GetOffTheRecordProfile(), nullptr);
RunLoop run_loop;
download_service_->CheckClientDownload(
&item, base::Bind(&DownloadProtectionServiceTest::CheckDoneCallback,
base::Unretained(this), run_loop.QuitClosure()));
run_loop.Run();
EXPECT_TRUE(IsResult(DownloadCheckResult::UNKNOWN));
EXPECT_FALSE(HasClientDownloadRequest());
}
{
// Case (4): !is_extended_reporting && !is_incognito.
// ClientDownloadRequest should NOT be sent.
content::DownloadItemUtils::AttachInfo(&item, profile(), nullptr);
RunLoop run_loop;
download_service_->CheckClientDownload(
&item, base::Bind(&DownloadProtectionServiceTest::CheckDoneCallback,
base::Unretained(this), run_loop.QuitClosure()));
run_loop.Run();
EXPECT_TRUE(IsResult(DownloadCheckResult::UNKNOWN));
EXPECT_FALSE(HasClientDownloadRequest());
}
}
TEST_F(DownloadProtectionServiceTest, CheckClientDownloadFetchFailed) {
// HTTP request will fail.
PrepareResponse(ClientDownloadResponse::SAFE, net::HTTP_INTERNAL_SERVER_ERROR,
net::URLRequestStatus::FAILED);
NiceMockDownloadItem item;
PrepareBasicDownloadItem(&item, {"http://www.evil.com/a.exe"}, // url_chain
"http://www.google.com/", // referrer
FILE_PATH_LITERAL("a.tmp"), // tmp_path
FILE_PATH_LITERAL("a.exe")); // final_path
EXPECT_CALL(*sb_service_->mock_database_manager(),
MatchDownloadWhitelistUrl(_))
.WillRepeatedly(Return(false));
EXPECT_CALL(*binary_feature_extractor_.get(), CheckSignature(tmp_path_, _));
EXPECT_CALL(*binary_feature_extractor_.get(),
ExtractImageFeatures(
tmp_path_, BinaryFeatureExtractor::kDefaultOptions, _, _));
RunLoop run_loop;
download_service_->CheckClientDownload(
&item, base::Bind(&DownloadProtectionServiceTest::CheckDoneCallback,
base::Unretained(this), run_loop.QuitClosure()));
run_loop.Run();
EXPECT_TRUE(IsResult(DownloadCheckResult::UNKNOWN));
}
TEST_F(DownloadProtectionServiceTest, CheckClientDownloadSuccess) {
PrepareResponse(ClientDownloadResponse::SAFE, net::HTTP_OK,
net::URLRequestStatus::SUCCESS);
NiceMockDownloadItem item;
PrepareBasicDownloadItem(&item, {"http://www.evil.com/a.exe"}, // url_chain
"http://www.google.com/", // referrer
FILE_PATH_LITERAL("a.tmp"), // tmp_path
FILE_PATH_LITERAL("a.exe")); // final_path
EXPECT_CALL(*sb_service_->mock_database_manager(),
MatchDownloadWhitelistUrl(_))
.WillRepeatedly(Return(false));
EXPECT_CALL(*binary_feature_extractor_.get(), CheckSignature(tmp_path_, _))
.Times(8);
EXPECT_CALL(*binary_feature_extractor_.get(),
ExtractImageFeatures(
tmp_path_, BinaryFeatureExtractor::kDefaultOptions, _, _))
.Times(8);
std::string feedback_ping;
std::string feedback_response;
ClientDownloadResponse expected_response;
{
RunLoop run_loop;
download_service_->CheckClientDownload(
&item, base::Bind(&DownloadProtectionServiceTest::CheckDoneCallback,
base::Unretained(this), run_loop.QuitClosure()));
run_loop.Run();
EXPECT_TRUE(IsResult(DownloadCheckResult::SAFE));
EXPECT_TRUE(HasClientDownloadRequest());
ClearClientDownloadRequest();
}
{
// Invalid response should result in SAFE (default value in proto).
ClientDownloadResponse invalid_response;
sb_service_->test_url_loader_factory()->AddResponse(
PPAPIDownloadRequest::GetDownloadRequestUrl().spec(),
invalid_response.SerializePartialAsString());
RunLoop run_loop;
download_service_->CheckClientDownload(
&item, base::Bind(&DownloadProtectionServiceTest::CheckDoneCallback,
base::Unretained(this), run_loop.QuitClosure()));
run_loop.Run();
EXPECT_TRUE(IsResult(DownloadCheckResult::SAFE));
EXPECT_TRUE(HasClientDownloadRequest());
ClearClientDownloadRequest();
EXPECT_FALSE(DownloadFeedbackService::GetPingsForDownloadForTesting(
item, &feedback_ping, &feedback_response));
}
{
// If the response is dangerous the result should also be marked as
// dangerous, and should not upload if not requested.
PrepareResponse(ClientDownloadResponse::DANGEROUS, net::HTTP_OK,
net::URLRequestStatus::SUCCESS,
false /* upload_requested */);
RunLoop run_loop;
download_service_->CheckClientDownload(
&item, base::Bind(&DownloadProtectionServiceTest::CheckDoneCallback,
base::Unretained(this), run_loop.QuitClosure()));
run_loop.Run();
EXPECT_FALSE(DownloadFeedbackService::GetPingsForDownloadForTesting(
item, &feedback_ping, &feedback_response));
EXPECT_TRUE(IsResult(DownloadCheckResult::DANGEROUS));
EXPECT_TRUE(HasClientDownloadRequest());
ClearClientDownloadRequest();
}
{
// If the response is dangerous and the server requests an upload,
// we should upload.
PrepareResponse(ClientDownloadResponse::DANGEROUS, net::HTTP_OK,
net::URLRequestStatus::SUCCESS,
true /* upload_requested */);
RunLoop run_loop;
download_service_->CheckClientDownload(
&item, base::Bind(&DownloadProtectionServiceTest::CheckDoneCallback,
base::Unretained(this), run_loop.QuitClosure()));
run_loop.Run();
EXPECT_TRUE(DownloadFeedbackService::GetPingsForDownloadForTesting(
item, &feedback_ping, &feedback_response));
EXPECT_TRUE(IsResult(DownloadCheckResult::DANGEROUS));
EXPECT_TRUE(HasClientDownloadRequest());
ClearClientDownloadRequest();
}
{
// If the response is uncommon the result should also be marked as uncommon.
PrepareResponse(ClientDownloadResponse::UNCOMMON, net::HTTP_OK,
net::URLRequestStatus::SUCCESS,
true /* upload_requested */);
RunLoop run_loop;
download_service_->CheckClientDownload(
&item, base::Bind(&DownloadProtectionServiceTest::CheckDoneCallback,
base::Unretained(this), run_loop.QuitClosure()));
run_loop.Run();
EXPECT_TRUE(IsResult(DownloadCheckResult::UNCOMMON));
EXPECT_TRUE(DownloadFeedbackService::GetPingsForDownloadForTesting(
item, &feedback_ping, &feedback_response));
ClientDownloadRequest decoded_request;
EXPECT_TRUE(decoded_request.ParseFromString(feedback_ping));
EXPECT_EQ(url_chain_.back().spec(), decoded_request.url());
expected_response.set_verdict(ClientDownloadResponse::UNCOMMON);
expected_response.set_upload(true);
EXPECT_EQ(expected_response.SerializeAsString(), feedback_response);
EXPECT_TRUE(HasClientDownloadRequest());
ClearClientDownloadRequest();
}
{
// If the response is dangerous_host the result should also be marked as
// dangerous_host.
PrepareResponse(ClientDownloadResponse::DANGEROUS_HOST, net::HTTP_OK,
net::URLRequestStatus::SUCCESS,
true /* upload_requested */);
RunLoop run_loop;
download_service_->CheckClientDownload(
&item, base::Bind(&DownloadProtectionServiceTest::CheckDoneCallback,
base::Unretained(this), run_loop.QuitClosure()));
run_loop.Run();
EXPECT_TRUE(IsResult(DownloadCheckResult::DANGEROUS_HOST));
EXPECT_TRUE(DownloadFeedbackService::GetPingsForDownloadForTesting(
item, &feedback_ping, &feedback_response));
expected_response.set_verdict(ClientDownloadResponse::DANGEROUS_HOST);
expected_response.set_upload(true);
EXPECT_EQ(expected_response.SerializeAsString(), feedback_response);
EXPECT_TRUE(HasClientDownloadRequest());
ClearClientDownloadRequest();
}
{
// If the response is POTENTIALLY_UNWANTED the result should also be marked
// as POTENTIALLY_UNWANTED.
PrepareResponse(ClientDownloadResponse::POTENTIALLY_UNWANTED, net::HTTP_OK,
net::URLRequestStatus::SUCCESS);
RunLoop run_loop;
download_service_->CheckClientDownload(
&item, base::Bind(&DownloadProtectionServiceTest::CheckDoneCallback,
base::Unretained(this), run_loop.QuitClosure()));
run_loop.Run();
EXPECT_TRUE(IsResult(DownloadCheckResult::POTENTIALLY_UNWANTED));
EXPECT_TRUE(HasClientDownloadRequest());
ClearClientDownloadRequest();
}
{
// If the response is UNKNOWN the result should also be marked as
// UNKNOWN. And if the server requests an upload, we should upload.
PrepareResponse(ClientDownloadResponse::UNKNOWN, net::HTTP_OK,
net::URLRequestStatus::SUCCESS,
true /* upload_requested */);
RunLoop run_loop;
download_service_->CheckClientDownload(
&item, base::Bind(&DownloadProtectionServiceTest::CheckDoneCallback,
base::Unretained(this), run_loop.QuitClosure()));
run_loop.Run();
EXPECT_TRUE(DownloadFeedbackService::GetPingsForDownloadForTesting(
item, &feedback_ping, &feedback_response));
EXPECT_TRUE(IsResult(DownloadCheckResult::UNKNOWN));
EXPECT_TRUE(HasClientDownloadRequest());
ClearClientDownloadRequest();
}
}
TEST_F(DownloadProtectionServiceTest, CheckClientDownloadHTTPS) {
PrepareResponse(ClientDownloadResponse::DANGEROUS, net::HTTP_OK,
net::URLRequestStatus::SUCCESS);
NiceMockDownloadItem item;
PrepareBasicDownloadItem(&item, {"http://www.evil.com/a.exe"}, // url_chain
"http://www.google.com/", // referrer
FILE_PATH_LITERAL("a.tmp"), // tmp_path
FILE_PATH_LITERAL("a.exe")); // final_path
EXPECT_CALL(*sb_service_->mock_database_manager(),
MatchDownloadWhitelistUrl(_))
.WillRepeatedly(Return(false));
EXPECT_CALL(*binary_feature_extractor_.get(), CheckSignature(tmp_path_, _))
.Times(1);
EXPECT_CALL(*binary_feature_extractor_.get(),
ExtractImageFeatures(
tmp_path_, BinaryFeatureExtractor::kDefaultOptions, _, _))
.Times(1);
RunLoop run_loop;
download_service_->CheckClientDownload(
&item, base::Bind(&DownloadProtectionServiceTest::CheckDoneCallback,
base::Unretained(this), run_loop.QuitClosure()));
run_loop.Run();
EXPECT_TRUE(IsResult(DownloadCheckResult::DANGEROUS));
EXPECT_TRUE(HasClientDownloadRequest());
ClearClientDownloadRequest();
}
TEST_F(DownloadProtectionServiceTest, CheckClientDownloadBlob) {
PrepareResponse(ClientDownloadResponse::DANGEROUS, net::HTTP_OK,
net::URLRequestStatus::SUCCESS);
NiceMockDownloadItem item;
PrepareBasicDownloadItem(
&item, {"blob:http://www.evil.com/50b85f60-71e4-11e4-82f8-0800200c9a66"},
"http://www.google.com/", // referrer
FILE_PATH_LITERAL("a.tmp"), // tmp_path
FILE_PATH_LITERAL("a.exe")); // final_path
EXPECT_CALL(*sb_service_->mock_database_manager(),
MatchDownloadWhitelistUrl(_))
.WillRepeatedly(Return(false));
EXPECT_CALL(*binary_feature_extractor_.get(), CheckSignature(tmp_path_, _))
.Times(1);
EXPECT_CALL(*binary_feature_extractor_.get(),
ExtractImageFeatures(
tmp_path_, BinaryFeatureExtractor::kDefaultOptions, _, _))
.Times(1);
RunLoop run_loop;
download_service_->CheckClientDownload(
&item, base::Bind(&DownloadProtectionServiceTest::CheckDoneCallback,
base::Unretained(this), run_loop.QuitClosure()));
run_loop.Run();
EXPECT_TRUE(IsResult(DownloadCheckResult::DANGEROUS));
EXPECT_TRUE(HasClientDownloadRequest());
ClearClientDownloadRequest();
}
TEST_F(DownloadProtectionServiceTest, CheckClientDownloadData) {
PrepareResponse(ClientDownloadResponse::DANGEROUS, net::HTTP_OK,
net::URLRequestStatus::SUCCESS);
NiceMockDownloadItem item;
PrepareBasicDownloadItem(
&item,
{"data:text/html:base64,", "data:text/html:base64,blahblahblah",
"data:application/octet-stream:base64,blahblah"}, // url_chain
"data:text/html:base64,foobar", // referrer
FILE_PATH_LITERAL("a.tmp"), // tmp_path
FILE_PATH_LITERAL("a.exe")); // final_path
EXPECT_CALL(*sb_service_->mock_database_manager(),
MatchDownloadWhitelistUrl(_))
.WillRepeatedly(Return(false));
EXPECT_CALL(*binary_feature_extractor_.get(), CheckSignature(tmp_path_, _))
.Times(1);
EXPECT_CALL(*binary_feature_extractor_.get(),
ExtractImageFeatures(
tmp_path_, BinaryFeatureExtractor::kDefaultOptions, _, _))
.Times(1);
RunLoop run_loop;
download_service_->CheckClientDownload(
&item, base::Bind(&DownloadProtectionServiceTest::CheckDoneCallback,
base::Unretained(this), run_loop.QuitClosure()));
run_loop.Run();
EXPECT_TRUE(IsResult(DownloadCheckResult::DANGEROUS));
ASSERT_TRUE(HasClientDownloadRequest());
const ClientDownloadRequest& request = *GetClientDownloadRequest();
const char kExpectedUrl[] =
"data:application/octet-stream:base64,"
"ACBF6DFC6F907662F566CA0241DFE8690C48661F440BA1BBD0B86C582845CCC8";
const char kExpectedRedirect1[] = "data:text/html:base64,";
const char kExpectedRedirect2[] =
"data:text/html:base64,"
"620680767E15717A57DB11D94D1BEBD32B3344EBC5994DF4FB07B0D473F4EF6B";
const char kExpectedReferrer[] =
"data:text/html:base64,"
"06E2C655B9F7130B508FFF86FD19B57E6BF1A1CFEFD6EFE1C3EB09FE24EF456A";
EXPECT_EQ(hash_, request.digests().sha256());
EXPECT_EQ(kExpectedUrl, request.url());
EXPECT_EQ(3, request.resources_size());
EXPECT_TRUE(RequestContainsResource(request,
ClientDownloadRequest::DOWNLOAD_REDIRECT,
kExpectedRedirect1, ""));
EXPECT_TRUE(RequestContainsResource(request,
ClientDownloadRequest::DOWNLOAD_REDIRECT,
kExpectedRedirect2, ""));
EXPECT_TRUE(RequestContainsResource(request,
ClientDownloadRequest::DOWNLOAD_URL,
kExpectedUrl, kExpectedReferrer));
ClearClientDownloadRequest();
}
TEST_F(DownloadProtectionServiceTest, CheckClientDownloadZip) {
PrepareResponse(ClientDownloadResponse::SAFE, net::HTTP_OK,
net::URLRequestStatus::SUCCESS);
NiceMockDownloadItem item;
PrepareBasicDownloadItem(&item, {"http://www.evil.com/a.zip"}, // url_chain
"http://www.google.com/", // referrer
FILE_PATH_LITERAL("a.tmp"), // tmp_path
FILE_PATH_LITERAL("a.zip")); // final_path
// Write out a zip archive to the temporary file.
base::ScopedTempDir zip_source_dir;
ASSERT_TRUE(zip_source_dir.CreateUniqueTempDir());
std::string file_contents = "dummy file";
{
// In this case, it only contains a text file.
ASSERT_EQ(static_cast<int>(file_contents.size()),
base::WriteFile(zip_source_dir.GetPath().Append(
FILE_PATH_LITERAL("file.txt")),
file_contents.data(), file_contents.size()));
ASSERT_TRUE(zip::Zip(zip_source_dir.GetPath(), tmp_path_, false));
RunLoop run_loop;
download_service_->CheckClientDownload(
&item, base::Bind(&DownloadProtectionServiceTest::CheckDoneCallback,
base::Unretained(this), run_loop.QuitClosure()));
run_loop.Run();
EXPECT_TRUE(IsResult(DownloadCheckResult::UNKNOWN));
EXPECT_FALSE(HasClientDownloadRequest());
Mock::VerifyAndClearExpectations(sb_service_.get());
Mock::VerifyAndClearExpectations(binary_feature_extractor_.get());
}
{
// Now check with an executable in the zip file as well.
ASSERT_EQ(static_cast<int>(file_contents.size()),
base::WriteFile(zip_source_dir.GetPath().Append(
FILE_PATH_LITERAL("file.exe")),
file_contents.data(), file_contents.size()));
ASSERT_TRUE(zip::Zip(zip_source_dir.GetPath(), tmp_path_, false));
EXPECT_CALL(*sb_service_->mock_database_manager(),
MatchDownloadWhitelistUrl(_))
.WillRepeatedly(Return(false));
RunLoop run_loop;
download_service_->CheckClientDownload(
&item, base::Bind(&DownloadProtectionServiceTest::CheckDoneCallback,
base::Unretained(this), run_loop.QuitClosure()));
run_loop.Run();
EXPECT_TRUE(IsResult(DownloadCheckResult::SAFE));
ASSERT_TRUE(HasClientDownloadRequest());
const ClientDownloadRequest& request = *GetClientDownloadRequest();
EXPECT_TRUE(request.has_download_type());
EXPECT_EQ(ClientDownloadRequest_DownloadType_ZIPPED_EXECUTABLE,
request.download_type());
EXPECT_EQ(1, request.archived_binary_size());
const ClientDownloadRequest_ArchivedBinary* archived_binary =
GetRequestArchivedBinary(request, "file.exe");
ASSERT_NE(nullptr, archived_binary);
EXPECT_EQ(ClientDownloadRequest_DownloadType_WIN_EXECUTABLE,
archived_binary->download_type());
EXPECT_EQ(static_cast<int64_t>(file_contents.size()),
archived_binary->length());
ClearClientDownloadRequest();
Mock::VerifyAndClearExpectations(binary_feature_extractor_.get());
}
{
// If the response is dangerous the result should also be marked as
// dangerous.
PrepareResponse(ClientDownloadResponse::DANGEROUS, net::HTTP_OK,
net::URLRequestStatus::SUCCESS);
RunLoop run_loop;
download_service_->CheckClientDownload(
&item, base::Bind(&DownloadProtectionServiceTest::CheckDoneCallback,
base::Unretained(this), run_loop.QuitClosure()));
run_loop.Run();
EXPECT_TRUE(IsResult(DownloadCheckResult::DANGEROUS));
EXPECT_TRUE(HasClientDownloadRequest());
ClearClientDownloadRequest();
Mock::VerifyAndClearExpectations(binary_feature_extractor_.get());
}
{
// Repeat the test with an archive inside the zip file in addition to the
// executable.
ASSERT_EQ(static_cast<int>(file_contents.size()),
base::WriteFile(zip_source_dir.GetPath().Append(
FILE_PATH_LITERAL("file.rar")),
file_contents.data(), file_contents.size()));
ASSERT_TRUE(zip::Zip(zip_source_dir.GetPath(), tmp_path_, false));
RunLoop run_loop;
download_service_->CheckClientDownload(
&item, base::Bind(&DownloadProtectionServiceTest::CheckDoneCallback,
base::Unretained(this), run_loop.QuitClosure()));
run_loop.Run();
ASSERT_TRUE(HasClientDownloadRequest());
EXPECT_EQ(2, GetClientDownloadRequest()->archived_binary_size());
EXPECT_TRUE(GetClientDownloadRequest()->has_download_type());
EXPECT_EQ(ClientDownloadRequest_DownloadType_ZIPPED_EXECUTABLE,
GetClientDownloadRequest()->download_type());
ClearClientDownloadRequest();
Mock::VerifyAndClearExpectations(binary_feature_extractor_.get());
}
{
// Repeat the test with just the archive inside the zip file.
ASSERT_TRUE(base::DeleteFile(
zip_source_dir.GetPath().AppendASCII("file.exe"), false));
ASSERT_TRUE(zip::Zip(zip_source_dir.GetPath(), tmp_path_, false));
RunLoop run_loop;
download_service_->CheckClientDownload(
&item, base::Bind(&DownloadProtectionServiceTest::CheckDoneCallback,
base::Unretained(this), run_loop.QuitClosure()));
run_loop.Run();
ASSERT_TRUE(HasClientDownloadRequest());
EXPECT_EQ(1, GetClientDownloadRequest()->archived_binary_size());
EXPECT_TRUE(GetClientDownloadRequest()->has_download_type());
EXPECT_EQ(ClientDownloadRequest_DownloadType_ZIPPED_ARCHIVE,
GetClientDownloadRequest()->download_type());
ClearClientDownloadRequest();
Mock::VerifyAndClearExpectations(binary_feature_extractor_.get());
}
}
TEST_F(DownloadProtectionServiceTest, CheckClientDownloadReportCorruptZip) {
CheckClientDownloadReportCorruptArchive(ZIP);
}
#if defined(OS_MACOSX)
TEST_F(DownloadProtectionServiceTest, CheckClientDownloadReportCorruptDmg) {
CheckClientDownloadReportCorruptArchive(DMG);
}
TEST_F(DownloadProtectionServiceTest, CheckClientDownloadReportValidDmg) {
PrepareResponse(ClientDownloadResponse::SAFE, net::HTTP_OK,
net::URLRequestStatus::SUCCESS);
base::FilePath test_dmg;
EXPECT_TRUE(base::PathService::Get(chrome::DIR_TEST_DATA, &test_dmg));
test_dmg = test_dmg.AppendASCII("safe_browsing")
.AppendASCII("mach_o")
.AppendASCII("signed-archive.dmg");
NiceMockDownloadItem item;
PrepareBasicDownloadItemWithFullPaths(
&item, {"http://www.evil.com/a.dmg"}, // url_chain
"http://www.google.com/", // referrer
test_dmg, // tmp_path
temp_dir_.GetPath().Append(FILE_PATH_LITERAL("a.dmg"))); // final_path
RunLoop run_loop;
download_service_->CheckClientDownload(
&item,
base::BindRepeating(&DownloadProtectionServiceTest::CheckDoneCallback,
base::Unretained(this), run_loop.QuitClosure()));
run_loop.Run();
ASSERT_TRUE(HasClientDownloadRequest());
EXPECT_TRUE(GetClientDownloadRequest()->archive_valid());
ClearClientDownloadRequest();
Mock::VerifyAndClearExpectations(sb_service_.get());
Mock::VerifyAndClearExpectations(binary_feature_extractor_.get());
}
// Tests that signatures get recorded and uploaded for signed DMGs.
TEST_F(DownloadProtectionServiceTest,
CheckClientDownloadReportDmgWithSignature) {
PrepareResponse(ClientDownloadResponse::SAFE, net::HTTP_OK,
net::URLRequestStatus::SUCCESS);
base::FilePath signed_dmg;
EXPECT_TRUE(base::PathService::Get(chrome::DIR_TEST_DATA, &signed_dmg));
signed_dmg = signed_dmg.AppendASCII("safe_browsing")
.AppendASCII("mach_o")
.AppendASCII("signed-archive.dmg");
NiceMockDownloadItem item;
PrepareBasicDownloadItemWithFullPaths(
&item, {"http://www.evil.com/a.dmg"}, // url_chain
"http://www.google.com/", // referrer
signed_dmg, // tmp_path
temp_dir_.GetPath().Append(FILE_PATH_LITERAL("a.dmg"))); // final_path
RunLoop run_loop;
download_service_->CheckClientDownload(
&item, base::Bind(&DownloadProtectionServiceTest::CheckDoneCallback,
base::Unretained(this), run_loop.QuitClosure()));
run_loop.Run();
ASSERT_TRUE(HasClientDownloadRequest());
EXPECT_TRUE(GetClientDownloadRequest()->has_udif_code_signature());
EXPECT_EQ(2215u, GetClientDownloadRequest()->udif_code_signature().length());
base::FilePath signed_dmg_signature;
EXPECT_TRUE(
base::PathService::Get(chrome::DIR_TEST_DATA, &signed_dmg_signature));
signed_dmg_signature = signed_dmg_signature.AppendASCII("safe_browsing")
.AppendASCII("mach_o")
.AppendASCII("signed-archive-signature.data");
std::string signature;
base::ReadFileToString(signed_dmg_signature, &signature);
EXPECT_EQ(2215u, signature.length());
EXPECT_EQ(signature, GetClientDownloadRequest()->udif_code_signature());
ClearClientDownloadRequest();
Mock::VerifyAndClearExpectations(sb_service_.get());
Mock::VerifyAndClearExpectations(binary_feature_extractor_.get());
}
// Tests that no signature gets recorded and uploaded for unsigned DMGs.
TEST_F(DownloadProtectionServiceTest,
CheckClientDownloadReportDmgWithoutSignature) {
PrepareResponse(ClientDownloadResponse::SAFE, net::HTTP_OK,
net::URLRequestStatus::SUCCESS);
base::FilePath unsigned_dmg;
EXPECT_TRUE(base::PathService::Get(chrome::DIR_TEST_DATA, &unsigned_dmg));
unsigned_dmg = unsigned_dmg.AppendASCII("safe_browsing")
.AppendASCII("dmg")
.AppendASCII("data")
.AppendASCII("mach_o_in_dmg.dmg");
NiceMockDownloadItem item;
PrepareBasicDownloadItemWithFullPaths(
&item, {"http://www.evil.com/a.dmg"}, // url_chain
"http://www.google.com/", // referrer
unsigned_dmg, // tmp_path
temp_dir_.GetPath().Append(FILE_PATH_LITERAL("a.dmg"))); // final_path
RunLoop run_loop;
download_service_->CheckClientDownload(
&item, base::Bind(&DownloadProtectionServiceTest::CheckDoneCallback,
base::Unretained(this), run_loop.QuitClosure()));
run_loop.Run();
ASSERT_TRUE(HasClientDownloadRequest());
EXPECT_TRUE(GetClientDownloadRequest()->archive_valid());
EXPECT_FALSE(GetClientDownloadRequest()->has_udif_code_signature());
ClearClientDownloadRequest();
Mock::VerifyAndClearExpectations(sb_service_.get());
Mock::VerifyAndClearExpectations(binary_feature_extractor_.get());
}
// Test that downloaded files with no disk image extension that have a 'koly'
// trailer are treated as disk images and processed accordingly.
TEST_F(DownloadProtectionServiceTest,
CheckClientDownloadReportDmgWithoutExtension) {
PrepareResponse(ClientDownloadResponse::SAFE, net::HTTP_OK,
net::URLRequestStatus::SUCCESS);
base::FilePath test_data;
EXPECT_TRUE(base::PathService::Get(chrome::DIR_TEST_DATA, &test_data));
test_data = test_data.AppendASCII("safe_browsing")
.AppendASCII("dmg")
.AppendASCII("data")
.AppendASCII("mach_o_in_dmg.txt");
NiceMockDownloadItem item;
PrepareBasicDownloadItemWithFullPaths(
&item, {"http://www.evil.com/a.dmg"}, // url_chain
"http://www.google.com/", // referrer
test_data, // tmp_path
temp_dir_.GetPath().Append(FILE_PATH_LITERAL("a.dmg"))); // final_path
RunLoop run_loop;
download_service_->CheckClientDownload(
&item, base::Bind(&DownloadProtectionServiceTest::CheckDoneCallback,
base::Unretained(this), run_loop.QuitClosure()));
run_loop.Run();
ASSERT_TRUE(HasClientDownloadRequest());
EXPECT_TRUE(GetClientDownloadRequest()->archive_valid());
ClearClientDownloadRequest();
Mock::VerifyAndClearExpectations(sb_service_.get());
Mock::VerifyAndClearExpectations(binary_feature_extractor_.get());
}
// Demonstrate that a .dmg file whose a) extension has been changed to .txt and
// b) 'koly' signature has been removed is not processed as a disk image.
TEST_F(DownloadProtectionServiceTest, CheckClientDownloadReportDmgWithoutKoly) {
PrepareResponse(ClientDownloadResponse::SAFE, net::HTTP_OK,
net::URLRequestStatus::SUCCESS);
base::FilePath test_data;
EXPECT_TRUE(base::PathService::Get(chrome::DIR_TEST_DATA, &test_data));
test_data = test_data.AppendASCII("safe_browsing")
.AppendASCII("dmg")
.AppendASCII("data")
.AppendASCII("mach_o_in_dmg_no_koly_signature.txt");
NiceMockDownloadItem item;
PrepareBasicDownloadItemWithFullPaths(
&item, {"http://www.evil.com/a.dmg"}, // url_chain
"http://www.google.com/", // referrer
test_data, // tmp_path
temp_dir_.GetPath().Append(FILE_PATH_LITERAL("a.dmg"))); // final_path
RunLoop run_loop;
download_service_->CheckClientDownload(
&item, base::Bind(&DownloadProtectionServiceTest::CheckDoneCallback,
base::Unretained(this), run_loop.QuitClosure()));
run_loop.Run();
ASSERT_TRUE(HasClientDownloadRequest());
EXPECT_FALSE(GetClientDownloadRequest()->archive_valid());
ClearClientDownloadRequest();
Mock::VerifyAndClearExpectations(sb_service_.get());
Mock::VerifyAndClearExpectations(binary_feature_extractor_.get());
}
// Test that a large DMG (size equals max value of 64 bit signed int) is not
// unpacked for binary feature analysis.
TEST_F(DownloadProtectionServiceTest, CheckClientDownloadReportLargeDmg) {
PrepareResponse(ClientDownloadResponse::SAFE, net::HTTP_OK,
net::URLRequestStatus::SUCCESS);
base::FilePath unsigned_dmg;
EXPECT_TRUE(base::PathService::Get(chrome::DIR_TEST_DATA, &unsigned_dmg));
unsigned_dmg = unsigned_dmg.AppendASCII("safe_browsing")
.AppendASCII("dmg")
.AppendASCII("data")
.AppendASCII("mach_o_in_dmg.dmg");
NiceMockDownloadItem item;
PrepareBasicDownloadItemWithFullPaths(
&item, {"http://www.evil.com/a.dmg"}, // url_chain
"http://www.google.com/", // referrer
unsigned_dmg, // tmp_path
temp_dir_.GetPath().Append(FILE_PATH_LITERAL("a.dmg"))); // final_path
// Set the max file size to unpack to 0, so that this DMG is now "too large"
std::unique_ptr<DownloadFileTypeConfig> config = policies_.DuplicateConfig();
for (int i = 0; i < config->file_types_size(); i++) {
if (config->file_types(i).extension() == "dmg") {
for (int j = 0; j < config->file_types(i).platform_settings_size(); j++) {
config->mutable_file_types(i)
->mutable_platform_settings(j)
->set_max_file_size_to_analyze(0);
}
}
}
policies_.SwapConfig(config);
RunLoop run_loop;
download_service_->CheckClientDownload(
&item, base::Bind(&DownloadProtectionServiceTest::CheckDoneCallback,
base::Unretained(this), run_loop.QuitClosure()));
run_loop.Run();
ASSERT_TRUE(HasClientDownloadRequest());
// Even though the test DMG is valid, it is not unpacked due to its large
// size.
EXPECT_FALSE(GetClientDownloadRequest()->archive_valid());
ClearClientDownloadRequest();
Mock::VerifyAndClearExpectations(sb_service_.get());
Mock::VerifyAndClearExpectations(binary_feature_extractor_.get());
}
// Verifies the results of DMG analysis end-to-end.
TEST_F(DownloadProtectionServiceTest, DMGAnalysisEndToEnd) {
PrepareResponse(ClientDownloadResponse::SAFE, net::HTTP_OK,
net::URLRequestStatus::SUCCESS);
base::FilePath dmg;
EXPECT_TRUE(base::PathService::Get(chrome::DIR_TEST_DATA, &dmg));
dmg = dmg.AppendASCII("safe_browsing")
.AppendASCII("dmg")
.AppendASCII("data")
.AppendASCII("mach_o_in_dmg.dmg");
NiceMockDownloadItem item;
PrepareBasicDownloadItemWithFullPaths(
&item, {"http://www.evil.com/a.dmg"}, // url_chain
"http://www.google.com/", // referrer
dmg, // tmp_path
temp_dir_.GetPath().Append(FILE_PATH_LITERAL("a.dmg"))); // final_path
RunLoop run_loop;
download_service_->CheckClientDownload(
&item,
base::BindRepeating(&DownloadProtectionServiceTest::CheckDoneCallback,
base::Unretained(this), run_loop.QuitClosure()));
run_loop.Run();
ASSERT_TRUE(HasClientDownloadRequest());
auto* request = GetClientDownloadRequest();
EXPECT_TRUE(request->archive_valid());
EXPECT_FALSE(request->has_udif_code_signature());
EXPECT_EQ(ClientDownloadRequest_DownloadType_MAC_EXECUTABLE,
request->download_type());
ASSERT_EQ(2, request->archived_binary().size());
for (const auto& binary : request->archived_binary()) {
EXPECT_FALSE(binary.file_basename().empty());
EXPECT_EQ(ClientDownloadRequest_DownloadType_MAC_EXECUTABLE,
binary.download_type());
ASSERT_TRUE(binary.has_digests());
EXPECT_TRUE(binary.digests().has_sha256());
EXPECT_TRUE(binary.has_length());
ASSERT_TRUE(binary.has_image_headers());
ASSERT_FALSE(binary.image_headers().mach_o_headers().empty());
EXPECT_FALSE(
binary.image_headers().mach_o_headers().Get(0).mach_header().empty());
EXPECT_FALSE(
binary.image_headers().mach_o_headers().Get(0).load_commands().empty());
}
ASSERT_EQ(1, request->detached_code_signature().size());
EXPECT_FALSE(request->detached_code_signature().Get(0).file_name().empty());
EXPECT_FALSE(request->detached_code_signature().Get(0).contents().empty());
ClearClientDownloadRequest();
Mock::VerifyAndClearExpectations(sb_service_.get());
Mock::VerifyAndClearExpectations(binary_feature_extractor_.get());
}
#endif // OS_MACOSX
TEST_F(DownloadProtectionServiceTest, CheckClientDownloadValidateRequest) {
#if defined(OS_MACOSX)
std::string download_file_path("ftp://www.google.com/bla.dmg");
#else
std::string download_file_path("ftp://www.google.com/bla.exe");
#endif // OS_MACOSX
NiceMockDownloadItem item;
#if defined(OS_MACOSX)
PrepareBasicDownloadItem(
&item, {"http://www.google.com/", download_file_path}, // url_chain
"http://www.google.com/", // referrer
FILE_PATH_LITERAL("bla.tmp"), // tmp_path
FILE_PATH_LITERAL("bla.dmg")); // final_path
#else
PrepareBasicDownloadItem(
&item, {"http://www.google.com/", download_file_path}, // url_chain
"http://www.google.com/", // referrer
FILE_PATH_LITERAL("bla.tmp"), // tmp_path
FILE_PATH_LITERAL("bla.exe")); // final_path
#endif // OS_MACOSX
std::string remote_address = "10.11.12.13";
EXPECT_CALL(item, GetRemoteAddress()).WillRepeatedly(Return(remote_address));
EXPECT_CALL(*sb_service_->mock_database_manager(),
MatchDownloadWhitelistUrl(_))
.WillRepeatedly(Return(false));
#if !defined(OS_MACOSX)
EXPECT_CALL(*binary_feature_extractor_.get(), CheckSignature(tmp_path_, _))
.WillOnce(SetCertificateContents("dummy cert data"));
EXPECT_CALL(*binary_feature_extractor_.get(),
ExtractImageFeatures(
tmp_path_, BinaryFeatureExtractor::kDefaultOptions, _, _))
.WillOnce(SetDosHeaderContents("dummy dos header"));
#endif // OS_MACOSX
PrepareResponse(ClientDownloadResponse::SAFE, net::HTTP_OK,
net::URLRequestStatus::SUCCESS);
RunLoop run_loop;
download_service_->CheckClientDownload(
&item, base::Bind(&DownloadProtectionServiceTest::CheckDoneCallback,
base::Unretained(this), run_loop.QuitClosure()));
// Wait until processing is finished.
run_loop.Run();
ASSERT_TRUE(HasClientDownloadRequest());
const ClientDownloadRequest* request = GetClientDownloadRequest();
EXPECT_EQ(download_file_path, request->url());
EXPECT_EQ(hash_, request->digests().sha256());
EXPECT_EQ(item.GetReceivedBytes(), request->length());
EXPECT_EQ(item.HasUserGesture(), request->user_initiated());
EXPECT_TRUE(RequestContainsServerIp(*request, remote_address));
EXPECT_EQ(2, request->resources_size());
EXPECT_TRUE(RequestContainsResource(*request,
ClientDownloadRequest::DOWNLOAD_REDIRECT,
"http://www.google.com/", ""));
EXPECT_TRUE(RequestContainsResource(*request,
ClientDownloadRequest::DOWNLOAD_URL,
download_file_path, referrer_.spec()));
ASSERT_TRUE(request->has_signature());
#if !defined(OS_MACOSX)
ASSERT_EQ(1, request->signature().certificate_chain_size());
const ClientDownloadRequest_CertificateChain& chain =
request->signature().certificate_chain(0);
ASSERT_EQ(1, chain.element_size());
EXPECT_EQ("dummy cert data", chain.element(0).certificate());
EXPECT_TRUE(request->has_image_headers());
const ClientDownloadRequest_ImageHeaders& headers = request->image_headers();
EXPECT_TRUE(headers.has_pe_headers());
EXPECT_TRUE(headers.pe_headers().has_dos_header());
EXPECT_EQ("dummy dos header", headers.pe_headers().dos_header());
#endif // OS_MACOSX
}
// Similar to above, but with an unsigned binary.
TEST_F(DownloadProtectionServiceTest,
CheckClientDownloadValidateRequestNoSignature) {
#if defined(OS_MACOSX)
std::string download_file_path("ftp://www.google.com/bla.dmg");
#else
std::string download_file_path("ftp://www.google.com/bla.exe");
#endif // OS_MACOSX
NiceMockDownloadItem item;
#if defined(OS_MACOSX)
PrepareBasicDownloadItem(
&item, {"http://www.google.com/", download_file_path}, // url_chain
"http://www.google.com/", // referrer
FILE_PATH_LITERAL("bla.tmp"), // tmp_path
FILE_PATH_LITERAL("bla.dmg")); // final_path
#else
PrepareBasicDownloadItem(
&item, {"http://www.google.com/", download_file_path}, // url_chain
"http://www.google.com/", // referrer
FILE_PATH_LITERAL("bla.tmp"), // tmp_path
FILE_PATH_LITERAL("bla.exe")); // final_path
#endif // OS_MACOSX
std::string remote_address = "10.11.12.13";
EXPECT_CALL(item, GetRemoteAddress()).WillRepeatedly(Return(remote_address));
EXPECT_CALL(*sb_service_->mock_database_manager(),
MatchDownloadWhitelistUrl(_))
.WillRepeatedly(Return(false));
#if !defined(OS_MACOSX)
EXPECT_CALL(*binary_feature_extractor_.get(), CheckSignature(tmp_path_, _));
EXPECT_CALL(*binary_feature_extractor_.get(),
ExtractImageFeatures(
tmp_path_, BinaryFeatureExtractor::kDefaultOptions, _, _));
#endif // OS_MACOSX
PrepareResponse(ClientDownloadResponse::SAFE, net::HTTP_OK,
net::URLRequestStatus::SUCCESS);
RunLoop run_loop;
download_service_->CheckClientDownload(
&item, base::Bind(&DownloadProtectionServiceTest::CheckDoneCallback,
base::Unretained(this), run_loop.QuitClosure()));
// Wait until processing is finished.
run_loop.Run();
ASSERT_TRUE(HasClientDownloadRequest());
const ClientDownloadRequest* request = GetClientDownloadRequest();
EXPECT_EQ(download_file_path, request->url());
EXPECT_EQ(hash_, request->digests().sha256());
EXPECT_EQ(item.GetReceivedBytes(), request->length());
EXPECT_EQ(item.HasUserGesture(), request->user_initiated());
EXPECT_TRUE(RequestContainsServerIp(*request, remote_address));
EXPECT_EQ(2, request->resources_size());
EXPECT_TRUE(RequestContainsResource(*request,
ClientDownloadRequest::DOWNLOAD_REDIRECT,
"http://www.google.com/", ""));
EXPECT_TRUE(RequestContainsResource(*request,
ClientDownloadRequest::DOWNLOAD_URL,
download_file_path, referrer_.spec()));
ASSERT_TRUE(request->has_signature());
EXPECT_EQ(0, request->signature().certificate_chain_size());
}
// Similar to above, but with tab history.
TEST_F(DownloadProtectionServiceTest,
CheckClientDownloadValidateRequestTabHistory) {
NiceMockDownloadItem item;
PrepareBasicDownloadItem(
&item,
{"http://www.google.com/", "http://www.google.com/bla.exe"}, // url_chain
"http://www.google.com/", // referrer
FILE_PATH_LITERAL("bla.tmp"), // tmp_path
FILE_PATH_LITERAL("bla.exe")); // final_path
GURL tab_url("http://tab.com/final");
GURL tab_referrer("http://tab.com/referrer");
std::string remote_address = "10.11.12.13";
EXPECT_CALL(item, GetTabUrl()).WillRepeatedly(ReturnRef(tab_url));
EXPECT_CALL(item, GetTabReferrerUrl())
.WillRepeatedly(ReturnRef(tab_referrer));
EXPECT_CALL(item, GetRemoteAddress()).WillRepeatedly(Return(remote_address));
content::DownloadItemUtils::AttachInfo(&item, profile(), nullptr);
EXPECT_CALL(*sb_service_->mock_database_manager(),
MatchDownloadWhitelistUrl(_))
.WillRepeatedly(Return(false));
EXPECT_CALL(*binary_feature_extractor_.get(), CheckSignature(tmp_path_, _))
.WillRepeatedly(SetCertificateContents("dummy cert data"));
EXPECT_CALL(*binary_feature_extractor_.get(),
ExtractImageFeatures(
tmp_path_, BinaryFeatureExtractor::kDefaultOptions, _, _))
.WillRepeatedly(SetDosHeaderContents("dummy dos header"));
// First test with no history match for the tab URL.
{
RunLoop interceptor_run_loop;
std::string upload_data;
sb_service_->test_url_loader_factory()->SetInterceptor(
base::BindLambdaForTesting(
[&](const network::ResourceRequest& request) {
upload_data = network::GetUploadData(request);
if (!upload_data.empty())
interceptor_run_loop.Quit();
}));
PrepareResponse(ClientDownloadResponse::SAFE, net::HTTP_OK,
net::URLRequestStatus::SUCCESS);
RunLoop run_loop;
download_service_->CheckClientDownload(
&item, base::Bind(&DownloadProtectionServiceTest::CheckDoneCallback,
base::Unretained(this), run_loop.QuitClosure()));
interceptor_run_loop.Run();
EXPECT_TRUE(HasClientDownloadRequest());
ClearClientDownloadRequest();
ClientDownloadRequest request;
EXPECT_TRUE(request.ParseFromString(upload_data));
EXPECT_EQ("http://www.google.com/bla.exe", request.url());
EXPECT_EQ(hash_, request.digests().sha256());
EXPECT_EQ(item.GetReceivedBytes(), request.length());
EXPECT_EQ(item.HasUserGesture(), request.user_initiated());
EXPECT_TRUE(RequestContainsServerIp(request, remote_address));
EXPECT_EQ(3, request.resources_size());
EXPECT_TRUE(RequestContainsResource(
request, ClientDownloadRequest::DOWNLOAD_REDIRECT,
"http://www.google.com/", ""));
EXPECT_TRUE(RequestContainsResource(
request, ClientDownloadRequest::DOWNLOAD_URL,
"http://www.google.com/bla.exe", referrer_.spec()));
EXPECT_TRUE(RequestContainsResource(request, ClientDownloadRequest::TAB_URL,
tab_url.spec(), tab_referrer.spec()));
EXPECT_TRUE(request.has_signature());
ASSERT_EQ(1, request.signature().certificate_chain_size());
const ClientDownloadRequest_CertificateChain& chain =
request.signature().certificate_chain(0);
ASSERT_EQ(1, chain.element_size());
EXPECT_EQ("dummy cert data", chain.element(0).certificate());
EXPECT_TRUE(request.has_image_headers());
const ClientDownloadRequest_ImageHeaders& headers = request.image_headers();
EXPECT_TRUE(headers.has_pe_headers());
EXPECT_TRUE(headers.pe_headers().has_dos_header());
EXPECT_EQ("dummy dos header", headers.pe_headers().dos_header());
sb_service_->test_url_loader_factory()->SetInterceptor(
network::TestURLLoaderFactory::Interceptor());
// Simulate the request finishing.
run_loop.Run();
}
// Now try with a history match.
{
RunLoop interceptor_run_loop;
std::string upload_data;
sb_service_->test_url_loader_factory()->SetInterceptor(
base::BindLambdaForTesting(
[&](const network::ResourceRequest& request) {
upload_data = network::GetUploadData(request);
if (!upload_data.empty())
interceptor_run_loop.Quit();
}));
history::RedirectList redirects;
redirects.push_back(GURL("http://tab.com/ref1"));
redirects.push_back(GURL("http://tab.com/ref2"));
redirects.push_back(tab_url);
HistoryServiceFactory::GetForProfile(profile(),
ServiceAccessType::EXPLICIT_ACCESS)
->AddPage(tab_url, base::Time::Now(),
reinterpret_cast<history::ContextID>(1), 0, GURL(), redirects,
ui::PAGE_TRANSITION_TYPED, history::SOURCE_BROWSED, false);
PrepareResponse(ClientDownloadResponse::SAFE, net::HTTP_OK,
net::URLRequestStatus::SUCCESS);
RunLoop run_loop;
download_service_->CheckClientDownload(
&item, base::Bind(&DownloadProtectionServiceTest::CheckDoneCallback,
base::Unretained(this), run_loop.QuitClosure()));
interceptor_run_loop.Run();
EXPECT_TRUE(HasClientDownloadRequest());
ClearClientDownloadRequest();
ClientDownloadRequest request;
EXPECT_TRUE(request.ParseFromString(upload_data));
EXPECT_EQ("http://www.google.com/bla.exe", request.url());
EXPECT_EQ(hash_, request.digests().sha256());
EXPECT_EQ(item.GetReceivedBytes(), request.length());
EXPECT_EQ(item.HasUserGesture(), request.user_initiated());
EXPECT_TRUE(RequestContainsServerIp(request, remote_address));
EXPECT_EQ(5, request.resources_size());
EXPECT_TRUE(RequestContainsResource(
request, ClientDownloadRequest::DOWNLOAD_REDIRECT,
"http://www.google.com/", ""));
EXPECT_TRUE(RequestContainsResource(
request, ClientDownloadRequest::DOWNLOAD_URL,
"http://www.google.com/bla.exe", referrer_.spec()));
EXPECT_TRUE(RequestContainsResource(request,
ClientDownloadRequest::TAB_REDIRECT,
"http://tab.com/ref1", ""));
EXPECT_TRUE(RequestContainsResource(request,
ClientDownloadRequest::TAB_REDIRECT,
"http://tab.com/ref2", ""));
EXPECT_TRUE(RequestContainsResource(request, ClientDownloadRequest::TAB_URL,
tab_url.spec(), tab_referrer.spec()));
EXPECT_TRUE(request.has_signature());
ASSERT_EQ(1, request.signature().certificate_chain_size());
const ClientDownloadRequest_CertificateChain& chain =
request.signature().certificate_chain(0);
ASSERT_EQ(1, chain.element_size());
EXPECT_EQ("dummy cert data", chain.element(0).certificate());
// Simulate the request finishing.
run_loop.Run();
}
}
TEST_F(DownloadProtectionServiceTest, TestCheckDownloadUrl) {
std::vector<GURL> url_chain;
url_chain.push_back(GURL("http://www.google.com/"));
url_chain.push_back(GURL("http://www.google.com/bla.exe"));
GURL referrer("http://www.google.com/");
std::string hash = "hash";
NiceMockDownloadItem item;
EXPECT_CALL(item, GetURL()).WillRepeatedly(ReturnRef(url_chain.back()));
EXPECT_CALL(item, GetUrlChain()).WillRepeatedly(ReturnRef(url_chain));
EXPECT_CALL(item, GetReferrerUrl()).WillRepeatedly(ReturnRef(referrer));
EXPECT_CALL(item, GetHash()).WillRepeatedly(ReturnRef(hash));
{
// CheckDownloadURL returns immediately which means the client object
// callback will never be called. Nevertheless the callback provided
// to CheckClientDownload must still be called.
EXPECT_CALL(*sb_service_->mock_database_manager(),
CheckDownloadUrl(ContainerEq(url_chain), NotNull()))
.WillOnce(Return(true));
RunLoop run_loop;
download_service_->CheckDownloadUrl(
&item, base::Bind(&DownloadProtectionServiceTest::CheckDoneCallback,
base::Unretained(this), run_loop.QuitClosure()));
run_loop.Run();
EXPECT_TRUE(IsResult(DownloadCheckResult::SAFE));
Mock::VerifyAndClearExpectations(sb_service_.get());
}
{
EXPECT_CALL(*sb_service_->mock_database_manager(),
CheckDownloadUrl(ContainerEq(url_chain), NotNull()))
.WillOnce(
DoAll(CheckDownloadUrlDone(SB_THREAT_TYPE_SAFE), Return(false)));
RunLoop run_loop;
download_service_->CheckDownloadUrl(
&item, base::Bind(&DownloadProtectionServiceTest::CheckDoneCallback,
base::Unretained(this), run_loop.QuitClosure()));
run_loop.Run();
EXPECT_TRUE(IsResult(DownloadCheckResult::SAFE));
Mock::VerifyAndClearExpectations(sb_service_.get());
}
{
EXPECT_CALL(*sb_service_->mock_database_manager(),
CheckDownloadUrl(ContainerEq(url_chain), NotNull()))
.WillOnce(DoAll(CheckDownloadUrlDone(SB_THREAT_TYPE_URL_MALWARE),
Return(false)));
RunLoop run_loop;
download_service_->CheckDownloadUrl(
&item, base::Bind(&DownloadProtectionServiceTest::CheckDoneCallback,
base::Unretained(this), run_loop.QuitClosure()));
run_loop.Run();
EXPECT_TRUE(IsResult(DownloadCheckResult::SAFE));
Mock::VerifyAndClearExpectations(sb_service_.get());
}
{
EXPECT_CALL(*sb_service_->mock_database_manager(),
CheckDownloadUrl(ContainerEq(url_chain), NotNull()))
.WillOnce(DoAll(CheckDownloadUrlDone(SB_THREAT_TYPE_URL_BINARY_MALWARE),
Return(false)));
RunLoop run_loop;
download_service_->CheckDownloadUrl(
&item, base::Bind(&DownloadProtectionServiceTest::CheckDoneCallback,
base::Unretained(this), run_loop.QuitClosure()));
run_loop.Run();
EXPECT_TRUE(IsResult(DownloadCheckResult::DANGEROUS));
}
}
TEST_F(DownloadProtectionServiceTest,
TestCheckDownloadUrlOnPolicyWhitelistedDownload) {
AddDomainToEnterpriseWhitelist("example.com");
// Prepares download item that its download url (last url in url chain)
// matches enterprise whitelist.
NiceMockDownloadItem item;
PrepareBasicDownloadItem(
&item,
{"https://landingpage.com", "http://example.com/download_page/a.exe"},
"http://referrer.com", // referrer
FILE_PATH_LITERAL("a.tmp"), // tmp_path
FILE_PATH_LITERAL("a.exe")); // final_path
std::unique_ptr<content::WebContents> web_contents(
content::WebContentsTester::CreateTestWebContents(profile(), nullptr));
content::DownloadItemUtils::AttachInfo(&item, profile(), web_contents.get());
EXPECT_CALL(*sb_service_->mock_database_manager(), CheckDownloadUrl(_, _))
.Times(0);
RunLoop run_loop;
download_service_->CheckDownloadUrl(
&item,
base::BindRepeating(&DownloadProtectionServiceTest::CheckDoneCallback,
base::Unretained(this), run_loop.QuitClosure()));
run_loop.Run();
EXPECT_TRUE(IsResult(DownloadCheckResult::WHITELISTED_BY_POLICY));
// Prepares download item that other url in the url chain matches enterprise
// whitelist.
NiceMockDownloadItem item2;
PrepareBasicDownloadItem(
&item2, {"https://example.com/landing", "http://otherdomain.com/a.exe"},
"http://referrer.com", // referrer
FILE_PATH_LITERAL("a.tmp"), // tmp_path
FILE_PATH_LITERAL("a.exe")); // final_path
content::DownloadItemUtils::AttachInfo(&item2, profile(), web_contents.get());
EXPECT_CALL(*sb_service_->mock_database_manager(), CheckDownloadUrl(_, _))
.Times(0);
RunLoop run_loop2;
download_service_->CheckDownloadUrl(
&item2,
base::BindRepeating(&DownloadProtectionServiceTest::CheckDoneCallback,
base::Unretained(this), run_loop2.QuitClosure()));
run_loop2.Run();
EXPECT_TRUE(IsResult(DownloadCheckResult::WHITELISTED_BY_POLICY));
}
TEST_F(DownloadProtectionServiceTest, TestDownloadRequestTimeout) {
NiceMockDownloadItem item;
PrepareBasicDownloadItem(&item, {"http://www.evil.com/bla.exe"}, // url_chain
"http://www.google.com/", // referrer
FILE_PATH_LITERAL("a.tmp"), // tmp_path
FILE_PATH_LITERAL("a.exe")); // final_path
EXPECT_CALL(*sb_service_->mock_database_manager(),
MatchDownloadWhitelistUrl(_))
.WillRepeatedly(Return(false));
EXPECT_CALL(*binary_feature_extractor_.get(), CheckSignature(tmp_path_, _));
EXPECT_CALL(*binary_feature_extractor_.get(),
ExtractImageFeatures(
tmp_path_, BinaryFeatureExtractor::kDefaultOptions, _, _));
download_service_->download_request_timeout_ms_ = 10;
RunLoop run_loop;
download_service_->CheckClientDownload(
&item, base::Bind(&DownloadProtectionServiceTest::CheckDoneCallback,
base::Unretained(this), run_loop.QuitClosure()));
// The request should time out because the HTTP request hasn't returned
// anything yet.
run_loop.Run();
EXPECT_TRUE(IsResult(DownloadCheckResult::UNKNOWN));
EXPECT_TRUE(HasClientDownloadRequest());
ClearClientDownloadRequest();
}
TEST_F(DownloadProtectionServiceTest, TestDownloadItemDestroyed) {
{
NiceMockDownloadItem item;
PrepareBasicDownloadItem(&item,
{"http://www.evil.com/bla.exe"}, // url_chain
"http://www.google.com/", // referrer
FILE_PATH_LITERAL("a.tmp"), // tmp_path
FILE_PATH_LITERAL("a.exe")); // final_path
GURL tab_url("http://www.google.com/tab");
EXPECT_CALL(item, GetTabUrl()).WillRepeatedly(ReturnRef(tab_url));
EXPECT_CALL(*sb_service_->mock_database_manager(),
MatchDownloadWhitelistUrl(_))
.WillRepeatedly(Return(false));
// Expects that MockDownloadItem will go out of scope while asynchronous
// processing is checking whitelist, and thus will return after whitelist
// check rather than continuing to process the download, since
// OnDownloadDestroyed will be called to terminate the processing.
EXPECT_CALL(*binary_feature_extractor_.get(), CheckSignature(tmp_path_, _))
.Times(0);
EXPECT_CALL(*binary_feature_extractor_.get(),
ExtractImageFeatures(
tmp_path_, BinaryFeatureExtractor::kDefaultOptions, _, _))
.Times(0);
download_service_->CheckClientDownload(
&item, base::Bind(&DownloadProtectionServiceTest::SyncCheckDoneCallback,
base::Unretained(this)));
// MockDownloadItem going out of scope triggers the OnDownloadDestroyed
// notification.
}
// When download is destroyed, no need to check for client download request
// result.
EXPECT_FALSE(has_result_);
EXPECT_FALSE(HasClientDownloadRequest());
}
TEST_F(DownloadProtectionServiceTest,
TestDownloadItemDestroyedDuringWhitelistCheck) {
std::unique_ptr<NiceMockDownloadItem> item(new NiceMockDownloadItem);
PrepareBasicDownloadItem(item.get(),
{"http://www.evil.com/bla.exe"}, // url_chain
"http://www.google.com/", // referrer
FILE_PATH_LITERAL("a.tmp"), // tmp_path
FILE_PATH_LITERAL("a.exe")); // final_path
GURL tab_url("http://www.google.com/tab");
EXPECT_CALL(*item, GetTabUrl()).WillRepeatedly(ReturnRef(tab_url));
EXPECT_CALL(*sb_service_->mock_database_manager(),
MatchDownloadWhitelistUrl(_))
.WillRepeatedly(Invoke([&item](const GURL&) {
item.reset();
return false;
}));
EXPECT_CALL(*binary_feature_extractor_.get(), CheckSignature(tmp_path_, _))
.Times(0);
EXPECT_CALL(*binary_feature_extractor_.get(),
ExtractImageFeatures(
tmp_path_, BinaryFeatureExtractor::kDefaultOptions, _, _))
.Times(0);
download_service_->CheckClientDownload(
item.get(),
base::BindRepeating(&DownloadProtectionServiceTest::SyncCheckDoneCallback,
base::Unretained(this)));
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(has_result_);
EXPECT_FALSE(HasClientDownloadRequest());
}
namespace {
class MockPageNavigator : public content::PageNavigator {
public:
MOCK_METHOD1(OpenURL, content::WebContents*(const content::OpenURLParams&));
};
// A custom matcher that matches a OpenURLParams value with a url with a query
// parameter patching |value|.
MATCHER_P(OpenURLParamsWithContextValue, value, "") {
std::string query_value;
return net::GetValueForKeyInQuery(arg.url, "ctx", &query_value) &&
query_value == value;
}
} // namespace
// ShowDetailsForDownload() should open a URL showing more information about why
// a download was flagged by SafeBrowsing. The URL should have a &ctx= parameter
// whose value is the DownloadDangerType.
TEST_F(DownloadProtectionServiceTest, ShowDetailsForDownloadHasContext) {
StrictMock<MockPageNavigator> mock_page_navigator;
StrictMock<download::MockDownloadItem> mock_download_item;
EXPECT_CALL(mock_download_item, GetDangerType())
.WillOnce(Return(download::DOWNLOAD_DANGER_TYPE_DANGEROUS_HOST));
EXPECT_CALL(mock_page_navigator, OpenURL(OpenURLParamsWithContextValue("7")));
download_service_->ShowDetailsForDownload(mock_download_item,
&mock_page_navigator);
}
TEST_F(DownloadProtectionServiceTest, GetAndSetDownloadPingToken) {
NiceMockDownloadItem item;
EXPECT_TRUE(DownloadProtectionService::GetDownloadPingToken(&item).empty());
std::string token = "download_ping_token";
DownloadProtectionService::SetDownloadPingToken(&item, token);
EXPECT_EQ(token, DownloadProtectionService::GetDownloadPingToken(&item));
DownloadProtectionService::SetDownloadPingToken(&item, std::string());
EXPECT_TRUE(DownloadProtectionService::GetDownloadPingToken(&item).empty());
}
TEST_F(DownloadProtectionServiceTest, PPAPIDownloadRequest_Unsupported) {
base::FilePath default_file_path(FILE_PATH_LITERAL("/foo/bar/test.txt"));
std::vector<base::FilePath::StringType> alternate_extensions{
FILE_PATH_LITERAL(".tmp"), FILE_PATH_LITERAL(".asdfasdf")};
download_service_->CheckPPAPIDownloadRequest(
GURL("http://example.com/foo"), GURL(), nullptr, default_file_path,
alternate_extensions, profile(),
base::Bind(&DownloadProtectionServiceTest::SyncCheckDoneCallback,
base::Unretained(this)));
ASSERT_TRUE(IsResult(DownloadCheckResult::SAFE));
}
TEST_F(DownloadProtectionServiceTest, PPAPIDownloadRequest_SupportedDefault) {
base::FilePath default_file_path(FILE_PATH_LITERAL("/foo/bar/test.crx"));
std::vector<base::FilePath::StringType> alternate_extensions;
EXPECT_CALL(*sb_service_->mock_database_manager(),
MatchDownloadWhitelistUrl(_))
.WillRepeatedly(Return(false));
struct {
ClientDownloadResponse::Verdict verdict;
DownloadCheckResult expected_result;
} kExpectedResults[] = {
{ClientDownloadResponse::SAFE, DownloadCheckResult::SAFE},
{ClientDownloadResponse::DANGEROUS, DownloadCheckResult::DANGEROUS},
{ClientDownloadResponse::UNCOMMON, DownloadCheckResult::UNCOMMON},
{ClientDownloadResponse::DANGEROUS_HOST,
DownloadCheckResult::DANGEROUS_HOST},
{ClientDownloadResponse::POTENTIALLY_UNWANTED,
DownloadCheckResult::POTENTIALLY_UNWANTED},
{ClientDownloadResponse::UNKNOWN, DownloadCheckResult::UNKNOWN},
};
for (const auto& test_case : kExpectedResults) {
sb_service_->test_url_loader_factory()->ClearResponses();
PrepareResponse(test_case.verdict, net::HTTP_OK,
net::URLRequestStatus::SUCCESS);
SetExtendedReportingPreference(true);
RunLoop run_loop;
download_service_->CheckPPAPIDownloadRequest(
GURL("http://example.com/foo"), GURL(), nullptr, default_file_path,
alternate_extensions, profile(),
base::Bind(&DownloadProtectionServiceTest::CheckDoneCallback,
base::Unretained(this), run_loop.QuitClosure()));
run_loop.Run();
ASSERT_TRUE(IsResult(test_case.expected_result));
ASSERT_EQ(ChromeUserPopulation::EXTENDED_REPORTING,
GetClientDownloadRequest()->population().user_population());
}
}
TEST_F(DownloadProtectionServiceTest, PPAPIDownloadRequest_SupportedAlternate) {
base::FilePath default_file_path(FILE_PATH_LITERAL("/foo/bar/test.txt"));
std::vector<base::FilePath::StringType> alternate_extensions{
FILE_PATH_LITERAL(".tmp"), FILE_PATH_LITERAL(".crx")};
PrepareResponse(ClientDownloadResponse::DANGEROUS, net::HTTP_OK,
net::URLRequestStatus::SUCCESS);
EXPECT_CALL(*sb_service_->mock_database_manager(),
MatchDownloadWhitelistUrl(_))
.WillRepeatedly(Return(false));
SetExtendedReportingPreference(false);
RunLoop run_loop;
download_service_->CheckPPAPIDownloadRequest(
GURL("http://example.com/foo"), GURL(), nullptr, default_file_path,
alternate_extensions, profile(),
base::Bind(&DownloadProtectionServiceTest::CheckDoneCallback,
base::Unretained(this), run_loop.QuitClosure()));
run_loop.Run();
ASSERT_TRUE(IsResult(DownloadCheckResult::DANGEROUS));
ASSERT_EQ(ChromeUserPopulation::SAFE_BROWSING,
GetClientDownloadRequest()->population().user_population());
}
TEST_F(DownloadProtectionServiceTest, PPAPIDownloadRequest_WhitelistedURL) {
base::FilePath default_file_path(FILE_PATH_LITERAL("/foo/bar/test.crx"));
std::vector<base::FilePath::StringType> alternate_extensions;
EXPECT_CALL(*sb_service_->mock_database_manager(),
MatchDownloadWhitelistUrl(_))
.WillRepeatedly(Return(true));
RunLoop run_loop;
download_service_->CheckPPAPIDownloadRequest(
GURL("http://example.com/foo"), GURL(), nullptr, default_file_path,
alternate_extensions, profile(),
base::Bind(&DownloadProtectionServiceTest::CheckDoneCallback,
base::Unretained(this), run_loop.QuitClosure()));
run_loop.Run();
ASSERT_TRUE(IsResult(DownloadCheckResult::SAFE));
}
TEST_F(DownloadProtectionServiceTest, PPAPIDownloadRequest_FetchFailed) {
base::FilePath default_file_path(FILE_PATH_LITERAL("/foo/bar/test.crx"));
std::vector<base::FilePath::StringType> alternate_extensions;
PrepareResponse(ClientDownloadResponse::DANGEROUS, net::HTTP_OK,
net::URLRequestStatus::FAILED);
EXPECT_CALL(*sb_service_->mock_database_manager(),
MatchDownloadWhitelistUrl(_))
.WillRepeatedly(Return(false));
RunLoop run_loop;
download_service_->CheckPPAPIDownloadRequest(
GURL("http://example.com/foo"), GURL(), nullptr, default_file_path,
alternate_extensions, profile(),
base::Bind(&DownloadProtectionServiceTest::CheckDoneCallback,
base::Unretained(this), run_loop.QuitClosure()));
run_loop.Run();
ASSERT_TRUE(IsResult(DownloadCheckResult::UNKNOWN));
}
TEST_F(DownloadProtectionServiceTest, PPAPIDownloadRequest_InvalidResponse) {
base::FilePath default_file_path(FILE_PATH_LITERAL("/foo/bar/test.crx"));
std::vector<base::FilePath::StringType> alternate_extensions;
sb_service_->test_url_loader_factory()->AddResponse(
PPAPIDownloadRequest::GetDownloadRequestUrl().spec(), "Hello world!");
EXPECT_CALL(*sb_service_->mock_database_manager(),
MatchDownloadWhitelistUrl(_))
.WillRepeatedly(Return(false));
RunLoop run_loop;
download_service_->CheckPPAPIDownloadRequest(
GURL("http://example.com/foo"), GURL(), nullptr, default_file_path,
alternate_extensions, profile(),
base::Bind(&DownloadProtectionServiceTest::CheckDoneCallback,
base::Unretained(this), run_loop.QuitClosure()));
run_loop.Run();
ASSERT_TRUE(IsResult(DownloadCheckResult::UNKNOWN));
}
TEST_F(DownloadProtectionServiceTest, PPAPIDownloadRequest_Timeout) {
base::FilePath default_file_path(FILE_PATH_LITERAL("/foo/bar/test.crx"));
std::vector<base::FilePath::StringType> alternate_extensions;
EXPECT_CALL(*sb_service_->mock_database_manager(),
MatchDownloadWhitelistUrl(_))
.WillRepeatedly(Return(false));
PrepareResponse(ClientDownloadResponse::SAFE, net::HTTP_OK,
net::URLRequestStatus::SUCCESS);
download_service_->download_request_timeout_ms_ = 0;
RunLoop run_loop;
download_service_->CheckPPAPIDownloadRequest(
GURL("http://example.com/foo"), GURL(), nullptr, default_file_path,
alternate_extensions, profile(),
base::Bind(&DownloadProtectionServiceTest::CheckDoneCallback,
base::Unretained(this), run_loop.QuitClosure()));
run_loop.Run();
ASSERT_TRUE(IsResult(DownloadCheckResult::UNKNOWN));
}
TEST_F(DownloadProtectionServiceTest, PPAPIDownloadRequest_Payload) {
RunLoop interceptor_run_loop;
std::string upload_data;
sb_service_->test_url_loader_factory()->SetInterceptor(
base::BindLambdaForTesting([&](const network::ResourceRequest& request) {
upload_data = network::GetUploadData(request);
}));
base::FilePath default_file_path(FILE_PATH_LITERAL("/foo/bar/test.crx"));
std::vector<base::FilePath::StringType> alternate_extensions{
FILE_PATH_LITERAL(".txt"), FILE_PATH_LITERAL(".abc"),
FILE_PATH_LITERAL(""), FILE_PATH_LITERAL(".sdF")};
EXPECT_CALL(*sb_service_->mock_database_manager(),
MatchDownloadWhitelistUrl(_))
.WillRepeatedly(Return(false));
PrepareResponse(ClientDownloadResponse::SAFE, net::HTTP_OK,
net::URLRequestStatus::SUCCESS);
const GURL kRequestorUrl("http://example.com/foo");
RunLoop run_loop;
download_service_->CheckPPAPIDownloadRequest(
kRequestorUrl, GURL(), nullptr, default_file_path, alternate_extensions,
profile(),
base::Bind(&DownloadProtectionServiceTest::CheckDoneCallback,
base::Unretained(this), run_loop.QuitClosure()));
run_loop.Run();
ASSERT_FALSE(upload_data.empty());
ClientDownloadRequest request;
ASSERT_TRUE(request.ParseFromString(upload_data));
EXPECT_EQ(ClientDownloadRequest::PPAPI_SAVE_REQUEST, request.download_type());
EXPECT_EQ(kRequestorUrl.spec(), request.url());
EXPECT_EQ("test.crx", request.file_basename());
ASSERT_EQ(3, request.alternate_extensions_size());
EXPECT_EQ(".txt", request.alternate_extensions(0));
EXPECT_EQ(".abc", request.alternate_extensions(1));
EXPECT_EQ(".sdF", request.alternate_extensions(2));
}
TEST_F(DownloadProtectionServiceTest,
PPAPIDownloadRequest_WhitelistedByPolicy) {
AddDomainToEnterpriseWhitelist("example.com");
std::unique_ptr<content::WebContents> web_contents(
content::WebContentsTester::CreateTestWebContents(profile(), nullptr));
base::FilePath default_file_path(FILE_PATH_LITERAL("/foo/bar/test.txt"));
std::vector<base::FilePath::StringType> alternate_extensions{
FILE_PATH_LITERAL(".tmp"), FILE_PATH_LITERAL(".asdfasdf")};
download_service_->CheckPPAPIDownloadRequest(
GURL("http://example.com/foo"), GURL(), web_contents.get(),
default_file_path, alternate_extensions, profile(),
base::BindRepeating(&DownloadProtectionServiceTest::SyncCheckDoneCallback,
base::Unretained(this)));
ASSERT_TRUE(IsResult(DownloadCheckResult::WHITELISTED_BY_POLICY));
}
TEST_F(DownloadProtectionServiceTest,
VerifyMaybeSendDangerousDownloadOpenedReport) {
NiceMockDownloadItem item;
PrepareBasicDownloadItem(&item,
std::vector<std::string>(), // empty url_chain
"http://www.google.com/", // referrer
FILE_PATH_LITERAL("a.tmp"), // tmp_path
FILE_PATH_LITERAL("a.exe")); // final_path
content::DownloadItemUtils::AttachInfo(&item, profile(), nullptr);
ASSERT_EQ(0, sb_service_->download_report_count());
// No report sent if download item without token field.
download_service_->MaybeSendDangerousDownloadOpenedReport(&item, false);
EXPECT_EQ(0, sb_service_->download_report_count());
// No report sent if user is in incognito mode.
DownloadProtectionService::SetDownloadPingToken(&item, "token");
content::DownloadItemUtils::AttachInfo(
&item, profile()->GetOffTheRecordProfile(), nullptr);
download_service_->MaybeSendDangerousDownloadOpenedReport(&item, false);
EXPECT_EQ(0, sb_service_->download_report_count());
// No report sent if user is not in extended reporting group.
content::DownloadItemUtils::AttachInfo(&item, profile(), nullptr);
SetExtendedReportingPreference(false);
download_service_->MaybeSendDangerousDownloadOpenedReport(&item, false);
EXPECT_EQ(0, sb_service_->download_report_count());
// Report successfully sent if user opted-in extended reporting, not in
// incognito, and download item has a token stored.
SetExtendedReportingPreference(true);
download_service_->MaybeSendDangerousDownloadOpenedReport(&item, false);
EXPECT_EQ(1, sb_service_->download_report_count());
download_service_->MaybeSendDangerousDownloadOpenedReport(&item, true);
EXPECT_EQ(2, sb_service_->download_report_count());
}
TEST_F(DownloadProtectionServiceTest, VerifyDangerousDownloadOpenedAPICall) {
NiceMockDownloadItem item;
PrepareBasicDownloadItem(&item,
{"http://example.com/a.exe"}, // empty url_chain
"http://example.com/", // referrer
FILE_PATH_LITERAL("a.tmp"), // tmp_path
FILE_PATH_LITERAL("a.exe")); // final_path
std::string hash = "hash";
EXPECT_CALL(item, GetHash()).WillRepeatedly(ReturnRef(hash));
base::FilePath target_path;
target_path = target_path.AppendASCII("filepath");
EXPECT_CALL(item, GetTargetFilePath()).WillRepeatedly(ReturnRef(target_path));
TestExtensionEventObserver event_observer(test_event_router_);
content::DownloadItemUtils::AttachInfo(&item, profile(), nullptr);
download_service_->MaybeSendDangerousDownloadOpenedReport(&item, false);
ASSERT_EQ(1, test_event_router_->GetEventCount(
OnDangerousDownloadOpened::kEventName));
auto captured_args = event_observer.PassEventArgs().GetList()[0].Clone();
EXPECT_EQ("http://example.com/a.exe",
captured_args.FindKey("url")->GetString());
EXPECT_EQ(base::HexEncode(hash.data(), hash.size()),
captured_args.FindKey("downloadDigestSha256")->GetString());
EXPECT_EQ(target_path.MaybeAsASCII(),
captured_args.FindKey("fileName")->GetString());
// No event is triggered if in incognito mode..
content::DownloadItemUtils::AttachInfo(
&item, profile()->GetOffTheRecordProfile(), nullptr);
download_service_->MaybeSendDangerousDownloadOpenedReport(&item, false);
EXPECT_EQ(1, test_event_router_->GetEventCount(
OnDangerousDownloadOpened::kEventName));
}
TEST_F(DownloadProtectionServiceTest, CheckClientDownloadWhitelistedByPolicy) {
NiceMockDownloadItem item;
PrepareBasicDownloadItem(&item, {"http://www.evil.com/a.exe"}, // url_chain
"http://www.google.com/", // referrer
FILE_PATH_LITERAL("a.tmp"), // tmp_path
FILE_PATH_LITERAL("a.exe")); // final_path
EXPECT_CALL(item, GetDangerType())
.WillRepeatedly(
Return(download::DOWNLOAD_DANGER_TYPE_WHITELISTED_BY_POLICY));
EXPECT_CALL(item, GetHash()).Times(0);
EXPECT_CALL(*sb_service_->mock_database_manager(),
MatchDownloadWhitelistUrl(_))
.Times(0);
EXPECT_CALL(*binary_feature_extractor_.get(), CheckSignature(_, _)).Times(0);
EXPECT_CALL(*binary_feature_extractor_.get(),
ExtractImageFeatures(
tmp_path_, BinaryFeatureExtractor::kDefaultOptions, _, _))
.Times(0);
RunLoop run_loop;
download_service_->CheckClientDownload(
&item,
base::BindRepeating(&DownloadProtectionServiceTest::CheckDoneCallback,
base::Unretained(this), run_loop.QuitClosure()));
run_loop.Run();
EXPECT_FALSE(HasClientDownloadRequest());
EXPECT_TRUE(IsResult(DownloadCheckResult::WHITELISTED_BY_POLICY));
}
TEST_F(DownloadProtectionServiceTest, CheckOffTheRecordDoesNotSendFeedback) {
NiceMockDownloadItem item;
EXPECT_FALSE(download_service_->MaybeBeginFeedbackForDownload(
profile()->GetOffTheRecordProfile(), &item, DownloadCommands::KEEP));
}
TEST_F(DownloadProtectionServiceTest,
CheckNotExtendedReportedDisabledDoesNotSendFeedback) {
SetExtendedReportingPreference(false);
NiceMockDownloadItem item;
EXPECT_FALSE(download_service_->MaybeBeginFeedbackForDownload(
profile(), &item, DownloadCommands::KEEP));
}
// ------------ class DownloadProtectionServiceFlagTest ----------------
class DownloadProtectionServiceFlagTest : public DownloadProtectionServiceTest {
protected:
DownloadProtectionServiceFlagTest()
// Matches unsigned.exe within zipfile_one_unsigned_binary.zip
: blacklisted_hash_hex_(
"1e954d9ce0389e2ba7447216f21761f98d1e6540c2abecdbecff570e36c493d"
"b") {}
void SetUp() override {
std::vector<uint8_t> bytes;
ASSERT_TRUE(base::HexStringToBytes(blacklisted_hash_hex_, &bytes) &&
bytes.size() == 32);
blacklisted_hash_ = std::string(bytes.begin(), bytes.end());
base::CommandLine::ForCurrentProcess()->AppendSwitchASCII(
safe_browsing::switches::kSbManualDownloadBlacklist,
blacklisted_hash_hex_);
DownloadProtectionServiceTest::SetUp();
}
// Hex 64 chars
const std::string blacklisted_hash_hex_;
// Binary 32 bytes
std::string blacklisted_hash_;
};
TEST_F(DownloadProtectionServiceFlagTest, CheckClientDownloadOverridenByFlag) {
NiceMockDownloadItem item;
PrepareBasicDownloadItem(&item, {"http://www.evil.com/a.exe"}, // url_chain
"http://www.google.com/", // referrer
FILE_PATH_LITERAL("a.tmp"), // tmp_path
FILE_PATH_LITERAL("a.exe")); // final_path
EXPECT_CALL(item, GetHash()).WillRepeatedly(ReturnRef(blacklisted_hash_));
EXPECT_CALL(*sb_service_->mock_database_manager(),
MatchDownloadWhitelistUrl(_))
.WillRepeatedly(Return(false));
EXPECT_CALL(*binary_feature_extractor_.get(), CheckSignature(tmp_path_, _));
EXPECT_CALL(*binary_feature_extractor_.get(),
ExtractImageFeatures(
tmp_path_, BinaryFeatureExtractor::kDefaultOptions, _, _));
RunLoop run_loop;
download_service_->CheckClientDownload(
&item, base::Bind(&DownloadProtectionServiceTest::CheckDoneCallback,
base::Unretained(this), run_loop.QuitClosure()));
run_loop.Run();
EXPECT_FALSE(HasClientDownloadRequest());
// Overriden by flag:
EXPECT_TRUE(IsResult(DownloadCheckResult::DANGEROUS));
}
// Test a real .zip with a real .exe in it, where the .exe is manually
// blacklisted by hash.
TEST_F(DownloadProtectionServiceFlagTest,
CheckClientDownloadZipOverridenByFlag) {
NiceMockDownloadItem item;
PrepareBasicDownloadItemWithFullPaths(
&item, {"http://www.evil.com/a.exe"}, // url_chain
"http://www.google.com/", // referrer
testdata_path_.AppendASCII(
"zipfile_one_unsigned_binary.zip"), // tmp_path
temp_dir_.GetPath().Append(FILE_PATH_LITERAL("a.zip"))); // final_path
EXPECT_CALL(*sb_service_->mock_database_manager(),
MatchDownloadWhitelistUrl(_))
.WillRepeatedly(Return(false));
RunLoop run_loop;
download_service_->CheckClientDownload(
&item, base::Bind(&DownloadProtectionServiceTest::CheckDoneCallback,
base::Unretained(this), run_loop.QuitClosure()));
run_loop.Run();
EXPECT_FALSE(HasClientDownloadRequest());
// Overriden by flag:
EXPECT_TRUE(IsResult(DownloadCheckResult::DANGEROUS));
}
TEST_F(DownloadProtectionServiceTest,
VerifyReferrerChainWithEmptyNavigationHistory) {
// Setup a web_contents with "http://example.com" as its last committed url.
NavigateAndCommit(GURL("http://example.com"));
NiceMockDownloadItem item;
std::vector<GURL> url_chain = {GURL("http://example.com/referrer"),
GURL("http://example.com/evil.exe")};
EXPECT_CALL(item, GetURL()).WillRepeatedly(ReturnRef(url_chain.back()));
EXPECT_CALL(item, GetUrlChain()).WillRepeatedly(ReturnRef(url_chain));
content::DownloadItemUtils::AttachInfo(&item, nullptr, web_contents());
std::unique_ptr<ReferrerChainData> referrer_chain_data =
download_service_->IdentifyReferrerChain(item);
ReferrerChain* referrer_chain = referrer_chain_data->GetReferrerChain();
ASSERT_EQ(1u, referrer_chain_data->referrer_chain_length());
EXPECT_EQ(item.GetUrlChain().back(), referrer_chain->Get(0).url());
EXPECT_EQ(web_contents()->GetLastCommittedURL().spec(),
referrer_chain->Get(0).referrer_url());
EXPECT_EQ(ReferrerChainEntry::EVENT_URL, referrer_chain->Get(0).type());
EXPECT_EQ(static_cast<int>(item.GetUrlChain().size()),
referrer_chain->Get(0).server_redirect_chain_size());
EXPECT_FALSE(referrer_chain->Get(0).is_retargeting());
}
TEST_F(DownloadProtectionServiceTest,
VerifyReferrerChainLengthForExtendedReporting) {
SafeBrowsingNavigationObserver::MaybeCreateForWebContents(web_contents());
// Simulate 6 user interactions
SimulateLinkClick(GURL("http://example.com/0"));
SimulateLinkClick(GURL("http://example.com/1"));
SimulateLinkClick(GURL("http://example.com/2"));
SimulateLinkClick(GURL("http://example.com/3"));
SimulateLinkClick(GURL("http://example.com/4"));
SimulateLinkClick(GURL("http://example.com/5"));
SimulateLinkClick(GURL("http://example.com/evil.exe"));
NiceMockDownloadItem item;
std::vector<GURL> url_chain = {GURL("http://example.com/evil.exe")};
EXPECT_CALL(item, GetURL()).WillRepeatedly(ReturnRef(url_chain.back()));
EXPECT_CALL(item, GetUrlChain()).WillRepeatedly(ReturnRef(url_chain));
content::DownloadItemUtils::AttachInfo(&item, nullptr, web_contents());
SetExtendedReportingPref(profile()->GetPrefs(), true);
std::unique_ptr<ReferrerChainData> referrer_chain_data =
download_service_->IdentifyReferrerChain(item);
// 6 entries means 5 interactions between entries.
EXPECT_EQ(referrer_chain_data->referrer_chain_length(), 6u);
SetExtendedReportingPref(profile()->GetPrefs(), false);
referrer_chain_data = download_service_->IdentifyReferrerChain(item);
// 3 entries means 2 interactions between entries.
EXPECT_EQ(referrer_chain_data->referrer_chain_length(), 3u);
}
TEST_F(DownloadProtectionServiceTest, DoesNotSendPingForCancelledDownloads) {
PrepareResponse(ClientDownloadResponse::SAFE, net::HTTP_OK,
net::URLRequestStatus::SUCCESS);
NiceMockDownloadItem item;
PrepareBasicDownloadItem(&item, {"http://www.evil.com/a.exe"}, // url_chain
"http://www.google.com/", // referrer
FILE_PATH_LITERAL("a.tmp"), // tmp_path
FILE_PATH_LITERAL("a.exe")); // final_path
// Mock a cancelled download.
EXPECT_CALL(item, GetState())
.WillRepeatedly(Return(download::DownloadItem::CANCELLED));
EXPECT_CALL(*sb_service_->mock_database_manager(),
MatchDownloadWhitelistUrl(_))
.WillRepeatedly(Return(false));
EXPECT_CALL(*binary_feature_extractor_.get(), CheckSignature(tmp_path_, _));
EXPECT_CALL(*binary_feature_extractor_.get(),
ExtractImageFeatures(
tmp_path_, BinaryFeatureExtractor::kDefaultOptions, _, _));
RunLoop run_loop;
download_service_->CheckClientDownload(
&item,
base::BindRepeating(&DownloadProtectionServiceTest::CheckDoneCallback,
base::Unretained(this), run_loop.QuitClosure()));
run_loop.RunUntilIdle();
EXPECT_FALSE(has_result_);
EXPECT_FALSE(HasClientDownloadRequest());
}
} // namespace safe_browsing