blob: 634da816656aea3959cf43397cd9adbb375a26af [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/sha1.h"
#include "base/single_thread_task_runner.h"
#include "base/strings/string_number_conversions.h"
#include "base/task_scheduler/task_scheduler.h"
#include "base/threading/thread_task_runner_handle.h"
#include "build/build_config.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/local_database_manager.h"
#include "chrome/browser/safe_browsing/safe_browsing_service.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/safe_browsing/binary_feature_extractor.h"
#include "chrome/common/safe_browsing/file_type_policies_test_util.h"
#include "chrome/test/base/testing_profile.h"
#include "components/history/core/browser/history_service.h"
#include "components/prefs/pref_service.h"
#include "components/safe_browsing/common/safe_browsing_prefs.h"
#include "components/safe_browsing/common/safebrowsing_switches.h"
#include "components/safe_browsing/proto/csd.pb.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 "content/public/browser/download_danger_type.h"
#include "content/public/browser/page_navigator.h"
#include "content/public/test/mock_download_item.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 "net/base/url_util.h"
#include "net/cert/x509_certificate.h"
#include "net/http/http_status_code.h"
#include "net/url_request/test_url_fetcher_factory.h"
#include "net/url_request/url_fetcher_delegate.h"
#include "net/url_request/url_fetcher_impl.h"
#include "net/url_request/url_request_status.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 ::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::StrictMock;
using ::testing::_;
using base::RunLoop;
using content::BrowserThread;
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:
virtual ~MockSafeBrowsingDatabaseManager() {}
DISALLOW_COPY_AND_ASSIGN(MockSafeBrowsingDatabaseManager);
};
class FakeSafeBrowsingService : public SafeBrowsingService,
public ServicesDelegate::ServicesCreator {
public:
FakeSafeBrowsingService() : download_report_count_(0) {
services_delegate_ = ServicesDelegate::CreateForTest(this, this);
}
// Returned pointer has the same lifespan as the database_manager_ refcounted
// object.
MockSafeBrowsingDatabaseManager* mock_database_manager() {
return mock_database_manager_;
}
void SendSerializedDownloadReport(const std::string& unused_report) override {
download_report_count_++;
}
int download_report_count() { return download_report_count_; }
protected:
~FakeSafeBrowsingService() override { mock_database_manager_ = nullptr; }
SafeBrowsingDatabaseManager* CreateDatabaseManager() override {
mock_database_manager_ = new MockSafeBrowsingDatabaseManager();
return mock_database_manager_;
}
SafeBrowsingProtocolManagerDelegate* GetProtocolManagerDelegate() override {
// Our SafeBrowsingDatabaseManager doesn't implement this delegate.
return NULL;
}
void RegisterAllDelayedAnalysis() override {}
private:
// ServicesDelegate::ServicesCreator:
bool CanCreateDownloadProtectionService() override { return false; }
bool CanCreateIncidentReportingService() override { return true; }
bool CanCreateResourceRequestDetector() override { return false; }
DownloadProtectionService* CreateDownloadProtectionService() override {
NOTREACHED();
return nullptr;
}
IncidentReportingService* CreateIncidentReportingService() override {
return new IncidentReportingService(nullptr);
}
ResourceRequestDetector* CreateResourceRequestDetector() override {
NOTREACHED();
return nullptr;
}
MockSafeBrowsingDatabaseManager* mock_database_manager_;
int download_report_count_;
DISALLOW_COPY_AND_ASSIGN(FakeSafeBrowsingService);
};
class MockBinaryFeatureExtractor : public BinaryFeatureExtractor {
public:
MockBinaryFeatureExtractor() {}
MOCK_METHOD2(CheckSignature,
void(const base::FilePath&,
ClientDownloadRequest_SignatureInfo*));
MOCK_METHOD4(ExtractImageFeatures,
bool(const base::FilePath&,
ExtractHeadersOption,
ClientDownloadRequest_ImageHeaders*,
google::protobuf::RepeatedPtrField<std::string>*));
protected:
virtual ~MockBinaryFeatureExtractor() {}
private:
DISALLOW_COPY_AND_ASSIGN(MockBinaryFeatureExtractor);
};
class TestURLFetcherWatcher : public net::TestURLFetcherDelegateForTests {
public:
explicit TestURLFetcherWatcher(net::TestURLFetcherFactory* factory)
: factory_(factory), fetcher_id_(-1) {
factory_->SetDelegateForTests(this);
}
~TestURLFetcherWatcher() { factory_->SetDelegateForTests(NULL); }
// TestURLFetcherDelegateForTests impl:
void OnRequestStart(int fetcher_id) override {
fetcher_id_ = fetcher_id;
run_loop_.Quit();
}
void OnChunkUpload(int fetcher_id) override {}
void OnRequestEnd(int fetcher_id) override {}
int WaitForRequest() {
run_loop_.Run();
return fetcher_id_;
}
private:
net::TestURLFetcherFactory* factory_;
int fetcher_id_;
RunLoop run_loop_;
};
using NiceMockDownloadItem = NiceMock<content::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());
}
// We can't call OnSafeBrowsingResult directly because SafeBrowsingCheck does
// not have any copy constructor which means it can't be stored in a callback
// easily. Note: check will be deleted automatically when the callback is
// deleted.
void OnSafeBrowsingResult(
LocalSafeBrowsingDatabaseManager::SafeBrowsingCheck* check) {
check->OnSafeBrowsingResult();
}
ACTION_P(CheckDownloadUrlDone, threat_type) {
// TODO(nparker): Remove use of SafeBrowsingCheck and instead call
// client->OnCheckDownloadUrlResult(..) directly.
LocalSafeBrowsingDatabaseManager::SafeBrowsingCheck* check =
new LocalSafeBrowsingDatabaseManager::SafeBrowsingCheck(
arg0, std::vector<SBFullHash>(), arg1, BINURL,
CreateSBThreatTypeSet({SB_THREAT_TYPE_URL_BINARY_MALWARE}));
for (size_t i = 0; i < check->url_results.size(); ++i)
check->url_results[i] = threat_type;
BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
base::BindOnce(&OnSafeBrowsingResult, base::Owned(check)));
}
class DownloadProtectionServiceTest : public testing::Test {
protected:
DownloadProtectionServiceTest()
: test_browser_thread_bundle_(
content::TestBrowserThreadBundle::IO_MAINLOOP) {}
void SetUp() override {
// 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();
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(PathService::Get(base::DIR_SOURCE_ROOT, &source_path));
testdata_path_ = source_path.AppendASCII("chrome")
.AppendASCII("test")
.AppendASCII("data")
.AppendASCII("safe_browsing")
.AppendASCII("download_protection");
// Setup a profile
ASSERT_TRUE(profile_dir_.CreateUniqueTempDir());
profile_.reset(new TestingProfile(profile_dir_.GetPath()));
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);
}
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;
}
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();
}
// Proxy for private method.
static void GetCertificateWhitelistStrings(
const net::X509Certificate& certificate,
const net::X509Certificate& issuer,
std::vector<std::string>* whitelist_strings) {
DownloadProtectionService::GetCertificateWhitelistStrings(
certificate, issuer, whitelist_strings);
}
// 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(net::FakeURLFetcherFactory* factory,
ClientDownloadResponse::Verdict verdict,
net::HttpStatusCode response_code,
net::URLRequestStatus::Status status,
bool upload_requested = false) {
ClientDownloadResponse response;
response.set_verdict(verdict);
response.set_upload(upload_requested);
factory->SetFakeResponse(PPAPIDownloadRequest::GetDownloadRequestUrl(),
response.SerializeAsString(), response_code,
status);
}
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(""));
}
private:
// Helper functions for FlushThreadMessageLoops.
void RunAllPendingAndQuitUI(const base::Closure& quit_closure) {
RunLoop().RunUntilIdle();
BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, quit_closure);
}
void PostRunMessageLoopTask(BrowserThread::ID thread,
const base::Closure& quit_closure) {
BrowserThread::PostTask(
thread, FROM_HERE,
base::BindOnce(&DownloadProtectionServiceTest::RunAllPendingAndQuitUI,
base::Unretained(this), quit_closure));
}
void FlushMessageLoop(BrowserThread::ID thread) {
RunLoop run_loop;
BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
base::BindOnce(&DownloadProtectionServiceTest::PostRunMessageLoopTask,
base::Unretained(this), thread, run_loop.QuitClosure()));
run_loop.Run();
}
void OnClientDownloadRequest(content::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;
}
void SendURLFetchComplete(net::TestURLFetcher* fetcher) {
fetcher->delegate()->OnURLFetchComplete(fetcher);
}
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<MockBinaryFeatureExtractor> binary_feature_extractor_;
DownloadProtectionService* download_service_;
DownloadCheckResult result_;
bool has_result_;
content::TestBrowserThreadBundle test_browser_thread_bundle_;
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_;
base::ScopedTempDir profile_dir_;
std::unique_ptr<TestingProfile> profile_;
// 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_;
};
void DownloadProtectionServiceTest::CheckClientDownloadReportCorruptArchive(
ArchiveType type) {
net::FakeURLFetcherFactory factory(NULL);
PrepareResponse(&factory, 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_INVALID_MAC_ARCHIVE;
EXPECT_EQ(expected_type, GetClientDownloadRequest()->download_type());
ClearClientDownloadRequest();
Mock::VerifyAndClearExpectations(sb_service_.get());
Mock::VerifyAndClearExpectations(binary_feature_extractor_.get());
}
// TODO(jialiul): 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.
net::FakeURLFetcherFactory factory(NULL);
PrepareResponse(&factory, 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(4);
EXPECT_CALL(*binary_feature_extractor_.get(),
ExtractImageFeatures(
tmp_path_, BinaryFeatureExtractor::kDefaultOptions, _, _))
.Times(4);
// 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.
net::FakeURLFetcherFactory factory(NULL);
PrepareResponse(&factory, 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(4);
EXPECT_CALL(*binary_feature_extractor_.get(),
ExtractImageFeatures(
tmp_path_, BinaryFeatureExtractor::kDefaultOptions, _, _))
.Times(6);
// 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);
EXPECT_CALL(item, GetBrowserContext())
.WillRepeatedly(Return(profile_->GetOffTheRecordProfile()));
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);
EXPECT_CALL(item, GetBrowserContext())
.WillRepeatedly(Return(profile_->GetOffTheRecordProfile()));
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.
EXPECT_CALL(item, GetBrowserContext())
.WillRepeatedly(Return(profile_.get()));
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);
EXPECT_CALL(item, GetBrowserContext())
.WillRepeatedly(Return(profile_.get()));
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::X509Certificate::GetDEREncoded(test_cert->os_cert_handle(),
&test_cert_der);
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.
EXPECT_CALL(item, GetBrowserContext())
.WillRepeatedly(Return(profile_.get()));
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.
EXPECT_CALL(item, GetBrowserContext())
.WillRepeatedly(Return(profile_.get()));
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.
net::FakeURLFetcherFactory factory(NULL);
PrepareResponse(&factory, 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);
EXPECT_CALL(item, GetBrowserContext())
.WillRepeatedly(Return(profile_->GetOffTheRecordProfile()));
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.
EXPECT_CALL(item, GetBrowserContext())
.WillRepeatedly(Return(profile_.get()));
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);
EXPECT_CALL(item, GetBrowserContext())
.WillRepeatedly(Return(profile_->GetOffTheRecordProfile()));
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.
EXPECT_CALL(item, GetBrowserContext())
.WillRepeatedly(Return(profile_.get()));
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.
net::FakeURLFetcherFactory factory(NULL);
PrepareResponse(&factory, 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) {
net::FakeURLFetcherFactory factory(NULL);
PrepareResponse(&factory, 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;
factory.SetFakeResponse(PPAPIDownloadRequest::GetDownloadRequestUrl(),
invalid_response.SerializePartialAsString(),
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::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(&factory, 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(&factory, 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(&factory, 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(&factory, 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(&factory, 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
PrepareResponse(&factory, ClientDownloadResponse::UNKNOWN, 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::UNKNOWN));
EXPECT_TRUE(HasClientDownloadRequest());
ClearClientDownloadRequest();
}
}
TEST_F(DownloadProtectionServiceTest, CheckClientDownloadHTTPS) {
net::FakeURLFetcherFactory factory(NULL);
PrepareResponse(&factory, 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) {
net::FakeURLFetcherFactory factory(NULL);
PrepareResponse(&factory, 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) {
net::FakeURLFetcherFactory factory(NULL);
PrepareResponse(&factory, 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) {
net::FakeURLFetcherFactory factory(NULL);
PrepareResponse(&factory, 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(&factory, 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(0, 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);
}
// Tests that signatures get recorded and uploaded for signed DMGs.
TEST_F(DownloadProtectionServiceTest,
CheckClientDownloadReportDmgWithSignature) {
net::FakeURLFetcherFactory factory(NULL);
PrepareResponse(&factory, ClientDownloadResponse::SAFE, net::HTTP_OK,
net::URLRequestStatus::SUCCESS);
base::FilePath signed_dmg;
EXPECT_TRUE(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(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) {
net::FakeURLFetcherFactory factory(NULL);
PrepareResponse(&factory, ClientDownloadResponse::SAFE, net::HTTP_OK,
net::URLRequestStatus::SUCCESS);
base::FilePath unsigned_dmg;
EXPECT_TRUE(PathService::Get(chrome::DIR_GEN_TEST_DATA, &unsigned_dmg));
unsigned_dmg = unsigned_dmg.AppendASCII("chrome")
.AppendASCII("safe_browsing_dmg")
.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) {
net::FakeURLFetcherFactory factory(NULL);
PrepareResponse(&factory, ClientDownloadResponse::SAFE, net::HTTP_OK,
net::URLRequestStatus::SUCCESS);
base::FilePath test_data;
EXPECT_TRUE(PathService::Get(chrome::DIR_GEN_TEST_DATA, &test_data));
test_data = test_data.AppendASCII("chrome")
.AppendASCII("safe_browsing_dmg")
.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) {
net::FakeURLFetcherFactory factory(NULL);
PrepareResponse(&factory, ClientDownloadResponse::SAFE, net::HTTP_OK,
net::URLRequestStatus::SUCCESS);
base::FilePath test_data;
EXPECT_TRUE(PathService::Get(chrome::DIR_GEN_TEST_DATA, &test_data));
test_data = test_data.AppendASCII("chrome")
.AppendASCII("safe_browsing_dmg")
.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) {
net::FakeURLFetcherFactory factory(NULL);
PrepareResponse(&factory, ClientDownloadResponse::SAFE, net::HTTP_OK,
net::URLRequestStatus::SUCCESS);
base::FilePath unsigned_dmg;
EXPECT_TRUE(PathService::Get(chrome::DIR_GEN_TEST_DATA, &unsigned_dmg));
unsigned_dmg = unsigned_dmg.AppendASCII("chrome")
.AppendASCII("safe_browsing_dmg")
.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
EXPECT_CALL(item, GetTotalBytes())
.WillRepeatedly(Return(std::numeric_limits<int64_t>::max()));
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());
}
#endif // OS_MACOSX
TEST_F(DownloadProtectionServiceTest, CheckClientDownloadValidateRequest) {
net::TestURLFetcherFactory factory;
#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
RunLoop run_loop;
download_service_->CheckClientDownload(
&item, base::Bind(&DownloadProtectionServiceTest::CheckDoneCallback,
base::Unretained(this), run_loop.QuitClosure()));
// Run the message loop(s) until SendRequest is called.
FlushThreadMessageLoops();
EXPECT_TRUE(HasClientDownloadRequest());
ClearClientDownloadRequest();
net::TestURLFetcher* fetcher = factory.GetFetcherByID(0);
ASSERT_TRUE(fetcher);
ClientDownloadRequest request;
EXPECT_TRUE(request.ParseFromString(fetcher->upload_data()));
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()));
EXPECT_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
// Simulate the request finishing.
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(&DownloadProtectionServiceTest::SendURLFetchComplete,
base::Unretained(this), fetcher));
run_loop.Run();
}
// Similar to above, but with an unsigned binary.
TEST_F(DownloadProtectionServiceTest,
CheckClientDownloadValidateRequestNoSignature) {
net::TestURLFetcherFactory factory;
#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
RunLoop run_loop;
download_service_->CheckClientDownload(
&item, base::Bind(&DownloadProtectionServiceTest::CheckDoneCallback,
base::Unretained(this), run_loop.QuitClosure()));
// Run the message loop(s) until SendRequest is called.
FlushThreadMessageLoops();
EXPECT_TRUE(HasClientDownloadRequest());
ClearClientDownloadRequest();
net::TestURLFetcher* fetcher = factory.GetFetcherByID(0);
ASSERT_TRUE(fetcher);
ClientDownloadRequest request;
EXPECT_TRUE(request.ParseFromString(fetcher->upload_data()));
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_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()));
EXPECT_TRUE(request.has_signature());
EXPECT_EQ(0, request.signature().certificate_chain_size());
// Simulate the request finishing.
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(&DownloadProtectionServiceTest::SendURLFetchComplete,
base::Unretained(this), fetcher));
run_loop.Run();
}
// Similar to above, but with tab history.
TEST_F(DownloadProtectionServiceTest,
CheckClientDownloadValidateRequestTabHistory) {
net::TestURLFetcherFactory factory;
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));
EXPECT_CALL(item, GetBrowserContext()).WillRepeatedly(Return(profile_.get()));
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.
{
TestURLFetcherWatcher fetcher_watcher(&factory);
RunLoop run_loop;
download_service_->CheckClientDownload(
&item, base::Bind(&DownloadProtectionServiceTest::CheckDoneCallback,
base::Unretained(this), run_loop.QuitClosure()));
EXPECT_EQ(0, fetcher_watcher.WaitForRequest());
EXPECT_TRUE(HasClientDownloadRequest());
ClearClientDownloadRequest();
net::TestURLFetcher* fetcher = factory.GetFetcherByID(0);
ASSERT_TRUE(fetcher);
ClientDownloadRequest request;
EXPECT_TRUE(request.ParseFromString(fetcher->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());
// Simulate the request finishing.
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(&DownloadProtectionServiceTest::SendURLFetchComplete,
base::Unretained(this), fetcher));
run_loop.Run();
}
// Now try with a history match.
{
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_.get(),
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);
TestURLFetcherWatcher fetcher_watcher(&factory);
RunLoop run_loop;
download_service_->CheckClientDownload(
&item, base::Bind(&DownloadProtectionServiceTest::CheckDoneCallback,
base::Unretained(this), run_loop.QuitClosure()));
EXPECT_EQ(0, fetcher_watcher.WaitForRequest());
EXPECT_TRUE(HasClientDownloadRequest());
ClearClientDownloadRequest();
net::TestURLFetcher* fetcher = factory.GetFetcherByID(0);
ASSERT_TRUE(fetcher);
ClientDownloadRequest request;
EXPECT_TRUE(request.ParseFromString(fetcher->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.
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE,
base::BindOnce(&DownloadProtectionServiceTest::SendURLFetchComplete,
base::Unretained(this), fetcher));
run_loop.Run();
}
}
TEST_F(DownloadProtectionServiceTest, TestCheckDownloadUrl) {
net::TestURLFetcherFactory factory;
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, TestDownloadRequestTimeout) {
net::TestURLFetcherFactory factory;
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));
#if defined(OS_MACOSX)
// Expects that MockDownloadItem will go out of scope while asynchronous
// processing is parsing file metadata, and thus ExtractFileOrDmgFeatures()
// will return rather than continuing to process the download.
EXPECT_CALL(*binary_feature_extractor_.get(), CheckSignature(tmp_path_, _))
.Times(0);
EXPECT_CALL(*binary_feature_extractor_.get(),
ExtractImageFeatures(
tmp_path_, BinaryFeatureExtractor::kDefaultOptions, _, _))
.Times(0);
#else
// Expects synchronous processing that continues to extract features from
// download even after MockDownloadItem goes out of scope.
EXPECT_CALL(*binary_feature_extractor_.get(), CheckSignature(tmp_path_, _));
EXPECT_CALL(*binary_feature_extractor_.get(),
ExtractImageFeatures(
tmp_path_, BinaryFeatureExtractor::kDefaultOptions, _, _));
#endif
download_service_->CheckClientDownload(
&item, base::Bind(&DownloadProtectionServiceTest::SyncCheckDoneCallback,
base::Unretained(this)));
// MockDownloadItem going out of scope triggers the OnDownloadDestroyed
// notification.
}
EXPECT_TRUE(IsResult(DownloadCheckResult::UNKNOWN));
EXPECT_FALSE(HasClientDownloadRequest());
}
TEST_F(DownloadProtectionServiceTest,
TestDownloadItemDestroyedDuringWhitelistCheck) {
net::TestURLFetcherFactory factory;
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_, _));
EXPECT_CALL(*binary_feature_extractor_.get(),
ExtractImageFeatures(
tmp_path_, BinaryFeatureExtractor::kDefaultOptions, _, _));
RunLoop run_loop;
download_service_->CheckClientDownload(
item.get(), base::Bind(&DownloadProtectionServiceTest::CheckDoneCallback,
base::Unretained(this), run_loop.QuitClosure()));
run_loop.Run();
EXPECT_TRUE(IsResult(DownloadCheckResult::UNKNOWN));
EXPECT_FALSE(HasClientDownloadRequest());
}
TEST_F(DownloadProtectionServiceTest, GetCertificateWhitelistStrings) {
// We'll pass this cert in as the "issuer", even though it isn't really
// used to sign the certs below. GetCertificateWhitelistStirngs doesn't care
// about this.
scoped_refptr<net::X509Certificate> issuer_cert(
ReadTestCertificate("issuer.pem"));
ASSERT_TRUE(issuer_cert.get());
std::string issuer_der;
net::X509Certificate::GetDEREncoded(issuer_cert->os_cert_handle(),
&issuer_der);
std::string hashed = base::SHA1HashString(issuer_der);
std::string cert_base =
"cert/" + base::HexEncode(hashed.data(), hashed.size());
scoped_refptr<net::X509Certificate> cert(ReadTestCertificate("test_cn.pem"));
ASSERT_TRUE(cert.get());
std::vector<std::string> whitelist_strings;
GetCertificateWhitelistStrings(*cert.get(), *issuer_cert.get(),
&whitelist_strings);
// This also tests escaping of characters in the certificate attributes.
EXPECT_THAT(whitelist_strings, ElementsAre(cert_base + "/CN=subject%2F%251"));
cert = ReadTestCertificate("test_cn_o.pem");
ASSERT_TRUE(cert.get());
whitelist_strings.clear();
GetCertificateWhitelistStrings(*cert.get(), *issuer_cert.get(),
&whitelist_strings);
EXPECT_THAT(whitelist_strings, ElementsAre(cert_base + "/CN=subject",
cert_base + "/CN=subject/O=org",
cert_base + "/O=org"));
cert = ReadTestCertificate("test_cn_o_ou.pem");
ASSERT_TRUE(cert.get());
whitelist_strings.clear();
GetCertificateWhitelistStrings(*cert.get(), *issuer_cert.get(),
&whitelist_strings);
EXPECT_THAT(
whitelist_strings,
ElementsAre(cert_base + "/CN=subject", cert_base + "/CN=subject/O=org",
cert_base + "/CN=subject/O=org/OU=unit",
cert_base + "/CN=subject/OU=unit", cert_base + "/O=org",
cert_base + "/O=org/OU=unit", cert_base + "/OU=unit"));
cert = ReadTestCertificate("test_cn_ou.pem");
ASSERT_TRUE(cert.get());
whitelist_strings.clear();
GetCertificateWhitelistStrings(*cert.get(), *issuer_cert.get(),
&whitelist_strings);
EXPECT_THAT(whitelist_strings, ElementsAre(cert_base + "/CN=subject",
cert_base + "/CN=subject/OU=unit",
cert_base + "/OU=unit"));
cert = ReadTestCertificate("test_o.pem");
ASSERT_TRUE(cert.get());
whitelist_strings.clear();
GetCertificateWhitelistStrings(*cert.get(), *issuer_cert.get(),
&whitelist_strings);
EXPECT_THAT(whitelist_strings, ElementsAre(cert_base + "/O=org"));
cert = ReadTestCertificate("test_o_ou.pem");
ASSERT_TRUE(cert.get());
whitelist_strings.clear();
GetCertificateWhitelistStrings(*cert.get(), *issuer_cert.get(),
&whitelist_strings);
EXPECT_THAT(whitelist_strings,
ElementsAre(cert_base + "/O=org", cert_base + "/O=org/OU=unit",
cert_base + "/OU=unit"));
cert = ReadTestCertificate("test_ou.pem");
ASSERT_TRUE(cert.get());
whitelist_strings.clear();
GetCertificateWhitelistStrings(*cert.get(), *issuer_cert.get(),
&whitelist_strings);
EXPECT_THAT(whitelist_strings, ElementsAre(cert_base + "/OU=unit"));
cert = ReadTestCertificate("test_c.pem");
ASSERT_TRUE(cert.get());
whitelist_strings.clear();
GetCertificateWhitelistStrings(*cert.get(), *issuer_cert.get(),
&whitelist_strings);
EXPECT_THAT(whitelist_strings, ElementsAre());
}
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<content::MockDownloadItem> mock_download_item;
EXPECT_CALL(mock_download_item, GetDangerType())
.WillOnce(Return(content::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_.get(),
base::Bind(&DownloadProtectionServiceTest::SyncCheckDoneCallback,
base::Unretained(this)));
ASSERT_TRUE(IsResult(DownloadCheckResult::SAFE));
}
TEST_F(DownloadProtectionServiceTest, PPAPIDownloadRequest_SupportedDefault) {
net::FakeURLFetcherFactory factory(nullptr);
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) {
factory.ClearFakeResponses();
PrepareResponse(&factory, 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_.get(),
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) {
net::FakeURLFetcherFactory factory(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(".crx")};
PrepareResponse(&factory, 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_.get(),
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) {
net::FakeURLFetcherFactory factory(nullptr);
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_.get(),
base::Bind(&DownloadProtectionServiceTest::CheckDoneCallback,
base::Unretained(this), run_loop.QuitClosure()));
run_loop.Run();
ASSERT_TRUE(IsResult(DownloadCheckResult::SAFE));
}
TEST_F(DownloadProtectionServiceTest, PPAPIDownloadRequest_FetchFailed) {
net::FakeURLFetcherFactory factory(nullptr);
base::FilePath default_file_path(FILE_PATH_LITERAL("/foo/bar/test.crx"));
std::vector<base::FilePath::StringType> alternate_extensions;
PrepareResponse(&factory, 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_.get(),
base::Bind(&DownloadProtectionServiceTest::CheckDoneCallback,
base::Unretained(this), run_loop.QuitClosure()));
run_loop.Run();
ASSERT_TRUE(IsResult(DownloadCheckResult::UNKNOWN));
}
TEST_F(DownloadProtectionServiceTest, PPAPIDownloadRequest_InvalidResponse) {
net::FakeURLFetcherFactory factory(nullptr);
base::FilePath default_file_path(FILE_PATH_LITERAL("/foo/bar/test.crx"));
std::vector<base::FilePath::StringType> alternate_extensions;
factory.SetFakeResponse(PPAPIDownloadRequest::GetDownloadRequestUrl(),
"Hello world!", net::HTTP_OK,
net::URLRequestStatus::SUCCESS);
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_.get(),
base::Bind(&DownloadProtectionServiceTest::CheckDoneCallback,
base::Unretained(this), run_loop.QuitClosure()));
run_loop.Run();
ASSERT_TRUE(IsResult(DownloadCheckResult::UNKNOWN));
}
TEST_F(DownloadProtectionServiceTest, PPAPIDownloadRequest_Timeout) {
net::FakeURLFetcherFactory factory(nullptr);
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(&factory, 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_.get(),
base::Bind(&DownloadProtectionServiceTest::CheckDoneCallback,
base::Unretained(this), run_loop.QuitClosure()));
run_loop.Run();
ASSERT_TRUE(IsResult(DownloadCheckResult::UNKNOWN));
}
namespace {
std::unique_ptr<net::FakeURLFetcher> FakeURLFetcherCreatorFunc(
std::string* upload_data_receiver,
const GURL& url,
net::URLFetcherDelegate* delegate,
const std::string& response_body,
net::HttpStatusCode response_code,
net::URLRequestStatus::Status status) {
class URLFetcher : public net::FakeURLFetcher {
public:
URLFetcher(std::string* upload_data_receiver,
const GURL& url,
net::URLFetcherDelegate* delegate,
const std::string& response_body,
net::HttpStatusCode response_code,
net::URLRequestStatus::Status status)
: FakeURLFetcher(url, delegate, response_body, response_code, status),
upload_data_receiver_(upload_data_receiver) {}
void SetUploadData(const std::string& upload_content_type,
const std::string& upload_content) override {
*upload_data_receiver_ = upload_content;
FakeURLFetcher::SetUploadData(upload_content_type, upload_content);
}
private:
std::string* upload_data_receiver_;
};
return std::unique_ptr<net::FakeURLFetcher>(
new URLFetcher(upload_data_receiver, url, delegate, response_body,
response_code, status));
}
} // namespace
TEST_F(DownloadProtectionServiceTest, PPAPIDownloadRequest_Payload) {
std::string upload_data;
net::FakeURLFetcherFactory factory(
nullptr, base::Bind(&FakeURLFetcherCreatorFunc, &upload_data));
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(&factory, 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_.get(),
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,
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
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");
EXPECT_CALL(item, GetBrowserContext())
.WillRepeatedly(Return(profile_->GetOffTheRecordProfile()));
download_service_->MaybeSendDangerousDownloadOpenedReport(&item, false);
EXPECT_EQ(0, sb_service_->download_report_count());
// No report sent if user is not in extended reporting group.
EXPECT_CALL(item, GetBrowserContext()).WillRepeatedly(Return(profile_.get()));
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,
VerifyReferrerChainWithEmptyNavigationHistory) {
// Setup a web_contents with "http://example.com" as its last committed url.
content::WebContents* web_contents =
content::WebContentsTester::CreateTestWebContents(profile_.get(),
nullptr);
content::WebContentsTester* web_contents_tester =
content::WebContentsTester::For(web_contents);
web_contents_tester->SetLastCommittedURL(GURL("http://example.com"));
NiceMockDownloadItem item;
PrepareBasicDownloadItem(
&item, {"http://referrer.com", "http://www.evil.com/a.exe"}, // url_chain
"http://example.com/", // referrer
FILE_PATH_LITERAL("a.tmp"), // tmp_path
FILE_PATH_LITERAL("a.exe")); // final_path
ON_CALL(item, GetWebContents()).WillByDefault(Return(web_contents));
std::unique_ptr<ReferrerChain> referrer_chain =
download_service_->IdentifyReferrerChain(item);
ASSERT_EQ(1, referrer_chain->size());
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());
}
// ------------ 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));
}
} // namespace safe_browsing