Commit a122489a authored by Daniel Rubery's avatar Daniel Rubery Committed by Commit Bot

Refactor content analysis in CheckClientDownloadRequest to its own class

There are two problems with the CheckClientDownloadRequest that this CL
takes steps towards resolving. First, the control flow of the
CheckClientDownloadRequest is very difficult to follow due to a large
number of private helper methods. Secondly, it uses several techniques
to prevent use-after-free (ref-counting, weak pointers, and cancellable
tasks). This CL encapsulates the logic that was in
CheckClientDownloadRequest that was related to analyzing the content
of the file into a singly-owned class, so that those two concerns can
be more easily addressed.

TODO: The FileAnalyzer still needs to be tested for RAR files.

Bug: 889986
Change-Id: I81c02bd3491c116a949dc959f2f02208511209bd
Reviewed-on: https://chromium-review.googlesource.com/c/1272275
Commit-Queue: Daniel Rubery <drubery@chromium.org>
Reviewed-by: default avatarVarun Khaneja <vakh@chromium.org>
Cr-Commit-Position: refs/heads/master@{#600785}
parent 46b3dde9
......@@ -150,6 +150,8 @@ static_library("safe_browsing") {
"download_protection/download_protection_util.h",
"download_protection/download_url_sb_client.cc",
"download_protection/download_url_sb_client.h",
"download_protection/file_analyzer.cc",
"download_protection/file_analyzer.h",
"download_protection/path_sanitizer.cc",
"download_protection/path_sanitizer.h",
"download_protection/ppapi_download_request.cc",
......
......@@ -49,12 +49,6 @@ void RecordFileExtensionType(const std::string& metric_name,
metric_name, FileTypePolicies::GetInstance()->UmaValueForFile(file));
}
void RecordArchivedArchiveFileExtensionType(const base::FilePath& file) {
base::UmaHistogramSparse(
"SBClientDownload.ArchivedArchiveExtensions",
FileTypePolicies::GetInstance()->UmaValueForFile(file));
}
std::string GetUnsupportedSchemeName(const GURL& download_url) {
if (download_url.SchemeIs(url::kContentScheme))
return "ContentScheme";
......@@ -89,7 +83,7 @@ CheckClientDownloadRequest::CheckClientDownloadRequest(
tab_url_(item->GetTabUrl()),
tab_referrer_url_(item->GetTabReferrerUrl()),
archived_executable_(false),
archive_is_valid_(ArchiveValid::UNSET),
archive_is_valid_(FileAnalyzer::ArchiveValid::UNSET),
#if defined(OS_MACOSX)
disk_image_signature_(nullptr),
#endif
......@@ -98,6 +92,7 @@ CheckClientDownloadRequest::CheckClientDownloadRequest(
binary_feature_extractor_(binary_feature_extractor),
database_manager_(database_manager),
pingback_enabled_(service_->enabled()),
file_analyzer_(new FileAnalyzer(binary_feature_extractor_)),
finished_(false),
type_(ClientDownloadRequest::WIN_EXECUTABLE),
start_time_(base::TimeTicks::Now()),
......@@ -375,74 +370,51 @@ void CheckClientDownloadRequest::AnalyzeFile() {
RecordFileExtensionType(kDownloadExtensionUmaName,
item_->GetTargetFilePath());
// Compute features from the file contents. Note that we record histograms
// based on the result, so this runs regardless of whether the pingbacks
// are enabled.
if (item_->GetTargetFilePath().MatchesExtension(FILE_PATH_LITERAL(".zip"))) {
StartExtractZipFeatures();
} else if (item_->GetTargetFilePath().MatchesExtension(
FILE_PATH_LITERAL(".rar")) &&
base::FeatureList::IsEnabled(kInspectDownloadedRarFiles)) {
StartExtractRarFeatures();
#if defined(OS_MACOSX)
} else if (item_->GetTargetFilePath().MatchesExtension(
FILE_PATH_LITERAL(".dmg")) ||
item_->GetTargetFilePath().MatchesExtension(
FILE_PATH_LITERAL(".img")) ||
item_->GetTargetFilePath().MatchesExtension(
FILE_PATH_LITERAL(".iso")) ||
item_->GetTargetFilePath().MatchesExtension(
FILE_PATH_LITERAL(".smi")) ||
item_->GetTargetFilePath().MatchesExtension(
FILE_PATH_LITERAL(".cdr")) ||
item_->GetTargetFilePath().MatchesExtension(
FILE_PATH_LITERAL(".dart")) ||
item_->GetTargetFilePath().MatchesExtension(
FILE_PATH_LITERAL(".dc42")) ||
item_->GetTargetFilePath().MatchesExtension(
FILE_PATH_LITERAL(".diskcopy42")) ||
item_->GetTargetFilePath().MatchesExtension(
FILE_PATH_LITERAL(".dmgpart")) ||
item_->GetTargetFilePath().MatchesExtension(
FILE_PATH_LITERAL(".dvdr")) ||
item_->GetTargetFilePath().MatchesExtension(
FILE_PATH_LITERAL(".imgpart")) ||
item_->GetTargetFilePath().MatchesExtension(
FILE_PATH_LITERAL(".ndif")) ||
item_->GetTargetFilePath().MatchesExtension(
FILE_PATH_LITERAL(".sparsebundle")) ||
item_->GetTargetFilePath().MatchesExtension(
FILE_PATH_LITERAL(".sparseimage")) ||
item_->GetTargetFilePath().MatchesExtension(
FILE_PATH_LITERAL(".toast")) ||
item_->GetTargetFilePath().MatchesExtension(
FILE_PATH_LITERAL(".udif"))) {
StartExtractDmgFeatures();
#endif
} else {
#if defined(OS_MACOSX)
// Checks for existence of "koly" signature even if file doesn't have
// archive-type extension, then calls ExtractFileOrDmgFeatures() with
// result.
base::PostTaskWithTraitsAndReplyWithResult(
FROM_HERE, {base::MayBlock(), base::TaskPriority::BEST_EFFORT},
base::Bind(DiskImageTypeSnifferMac::IsAppleDiskImage,
item_->GetTargetFilePath()),
base::Bind(&CheckClientDownloadRequest::ExtractFileOrDmgFeatures,
this));
#else
StartExtractFileFeatures();
#endif
}
file_analyzer_->Start(
item_->GetTargetFilePath(), item_->GetFullPath(),
base::BindOnce(&CheckClientDownloadRequest::OnFileFeatureExtractionDone,
weakptr_factory_.GetWeakPtr()));
}
void CheckClientDownloadRequest::OnFileFeatureExtractionDone() {
void CheckClientDownloadRequest::OnFileFeatureExtractionDone(
FileAnalyzer::Results results) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
// This can run in any thread, since it just posts more messages.
if (item_ == nullptr) {
PostFinishTask(DownloadCheckResult::UNKNOWN, REASON_REQUEST_CANCELED);
return;
}
// If it's an archive with no archives or executables, finish early.
if ((type_ == ClientDownloadRequest::ZIPPED_EXECUTABLE ||
type_ == ClientDownloadRequest::RAR_COMPRESSED_EXECUTABLE) &&
!results.archived_executable && !results.archived_archive &&
results.archive_is_valid == FileAnalyzer::ArchiveValid::VALID) {
PostFinishTask(DownloadCheckResult::UNKNOWN,
REASON_ARCHIVE_WITHOUT_BINARIES);
}
// The content checks cannot determine that we decided to sample this file, so
// special case that DownloadType.
if (type_ != ClientDownloadRequest::SAMPLED_UNSUPPORTED_FILE)
type_ = results.type;
archived_executable_ = results.archived_executable;
archive_is_valid_ = results.archive_is_valid;
archived_binaries_.CopyFrom(results.archived_binaries);
signature_info_ = results.signature_info;
image_headers_.reset(new ClientDownloadRequest_ImageHeaders());
*image_headers_ = results.image_headers;
#if defined(OS_MACOSX)
if (!results.disk_image_signature.empty())
disk_image_signature_ =
std::make_unique<std::vector<uint8_t>>(results.disk_image_signature);
else
disk_image_signature_ = nullptr;
detached_code_signatures_.CopyFrom(results.detached_code_signatures);
#endif
// TODO(noelutz): DownloadInfo should also contain the IP address of
// every URL in the redirect chain. We also should check whether the
// download URL is hosted on the internal network.
......@@ -460,296 +432,6 @@ void CheckClientDownloadRequest::OnFileFeatureExtractionDone() {
base::BindOnce(&CheckClientDownloadRequest::StartTimeout, this));
}
void CheckClientDownloadRequest::StartExtractFileFeatures() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK(item_); // Called directly from Start(), item should still exist.
// Since we do blocking I/O, offload this to a worker thread.
// The task does not need to block shutdown.
base::PostTaskWithTraits(
FROM_HERE,
{base::MayBlock(), base::TaskPriority::BEST_EFFORT,
base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
base::BindOnce(&CheckClientDownloadRequest::ExtractFileFeatures, this,
item_->GetFullPath()));
}
void CheckClientDownloadRequest::ExtractFileFeatures(
const base::FilePath& file_path) {
base::TimeTicks start_time = base::TimeTicks::Now();
binary_feature_extractor_->CheckSignature(file_path, &signature_info_);
bool is_signed = (signature_info_.certificate_chain_size() > 0);
if (is_signed) {
DVLOG(2) << "Downloaded a signed binary: " << file_path.value();
} else {
DVLOG(2) << "Downloaded an unsigned binary: " << file_path.value();
}
UMA_HISTOGRAM_BOOLEAN("SBClientDownload.SignedBinaryDownload", is_signed);
UMA_HISTOGRAM_TIMES("SBClientDownload.ExtractSignatureFeaturesTime",
base::TimeTicks::Now() - start_time);
start_time = base::TimeTicks::Now();
image_headers_.reset(new ClientDownloadRequest_ImageHeaders());
if (!binary_feature_extractor_->ExtractImageFeatures(
file_path, BinaryFeatureExtractor::kDefaultOptions,
image_headers_.get(), nullptr)) {
image_headers_.reset();
}
UMA_HISTOGRAM_TIMES("SBClientDownload.ExtractImageHeadersTime",
base::TimeTicks::Now() - start_time);
OnFileFeatureExtractionDone();
}
void CheckClientDownloadRequest::StartExtractRarFeatures() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK(item_); // Called directly from Start(), item should still exist.
rar_analysis_start_time_ = base::TimeTicks::Now();
// We give the rar analyzer a weak pointer to this object. Since the
// analyzer is refcounted, it might outlive the request.
rar_analyzer_ = new SandboxedRarAnalyzer(
item_->GetFullPath(),
base::BindRepeating(&CheckClientDownloadRequest::OnRarAnalysisFinished,
weakptr_factory_.GetWeakPtr()),
content::ServiceManagerConnection::GetForProcess()->GetConnector());
rar_analyzer_->Start();
}
void CheckClientDownloadRequest::OnRarAnalysisFinished(
const ArchiveAnalyzerResults& results) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (item_ == nullptr) {
PostFinishTask(DownloadCheckResult::UNKNOWN, REASON_REQUEST_CANCELED);
return;
}
if (!service_)
return;
archive_is_valid_ =
(results.success ? ArchiveValid::VALID : ArchiveValid::INVALID);
archived_executable_ = results.has_executable;
CopyArchivedBinaries(results.archived_binary, &archived_binaries_);
DVLOG(1) << "Rar analysis finished for " << item_->GetFullPath().value()
<< ", has_executable=" << results.has_executable
<< ", has_archive=" << results.has_archive
<< ", success=" << results.success;
if (archived_executable_) {
UMA_HISTOGRAM_COUNTS_100("SBClientDownload.RarFileArchivedBinariesCount",
results.archived_binary.size());
}
UMA_HISTOGRAM_BOOLEAN("SBClientDownload.RarFileSuccess", results.success);
UMA_HISTOGRAM_BOOLEAN("SBClientDownload.RarFileHasExecutable",
archived_executable_);
UMA_HISTOGRAM_BOOLEAN("SBClientDownload.RarFileHasArchiveButNoExecutable",
results.has_archive && !archived_executable_);
UMA_HISTOGRAM_TIMES("SBClientDownload.ExtractRarFeaturesTime",
base::TimeTicks::Now() - rar_analysis_start_time_);
for (const auto& file_name : results.archived_archive_filenames)
RecordArchivedArchiveFileExtensionType(file_name);
if (!archived_executable_) {
if (results.has_archive) {
type_ = ClientDownloadRequest::RAR_COMPRESSED_ARCHIVE;
} else if (!results.success) {
// .rar files that look invalid to Chrome may be successfully unpacked by
// other archive tools, so they may be a real threat.
type_ = ClientDownloadRequest::INVALID_RAR;
} else {
// Normal rar w/o EXEs, or invalid rar and not extended-reporting.
PostFinishTask(DownloadCheckResult::UNKNOWN,
REASON_ARCHIVE_WITHOUT_BINARIES);
return;
}
}
OnFileFeatureExtractionDone();
}
void CheckClientDownloadRequest::StartExtractZipFeatures() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK(item_); // Called directly from Start(), item should still exist.
zip_analysis_start_time_ = base::TimeTicks::Now();
// We give the zip analyzer a weak pointer to this object. Since the
// analyzer is refcounted, it might outlive the request.
zip_analyzer_ = new SandboxedZipAnalyzer(
item_->GetFullPath(),
base::Bind(&CheckClientDownloadRequest::OnZipAnalysisFinished,
weakptr_factory_.GetWeakPtr()),
content::ServiceManagerConnection::GetForProcess()->GetConnector());
zip_analyzer_->Start();
}
// static
void CheckClientDownloadRequest::CopyArchivedBinaries(
const ArchivedBinaries& src_binaries,
ArchivedBinaries* dest_binaries) {
// Limit the number of entries so we don't clog the backend.
// We can expand this limit by pushing a new download_file_types update.
int limit = FileTypePolicies::GetInstance()->GetMaxArchivedBinariesToReport();
dest_binaries->Clear();
for (int i = 0; i < limit && i < src_binaries.size(); i++) {
*dest_binaries->Add() = src_binaries[i];
}
}
void CheckClientDownloadRequest::OnZipAnalysisFinished(
const ArchiveAnalyzerResults& results) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK_EQ(ClientDownloadRequest::ZIPPED_EXECUTABLE, type_);
if (item_ == nullptr) {
PostFinishTask(DownloadCheckResult::UNKNOWN, REASON_REQUEST_CANCELED);
return;
}
if (!service_)
return;
// Even if !results.success, some of the zip may have been parsed.
// Some unzippers will successfully unpack archives that we cannot,
// so we're lenient here.
archive_is_valid_ =
(results.success ? ArchiveValid::VALID : ArchiveValid::INVALID);
archived_executable_ = results.has_executable;
CopyArchivedBinaries(results.archived_binary, &archived_binaries_);
DVLOG(1) << "Zip analysis finished for " << item_->GetFullPath().value()
<< ", has_executable=" << results.has_executable
<< ", has_archive=" << results.has_archive
<< ", success=" << results.success;
if (archived_executable_) {
UMA_HISTOGRAM_COUNTS_1M("SBClientDownload.ZipFileArchivedBinariesCount",
results.archived_binary.size());
}
UMA_HISTOGRAM_BOOLEAN("SBClientDownload.ZipFileSuccess", results.success);
UMA_HISTOGRAM_BOOLEAN("SBClientDownload.ZipFileHasExecutable",
archived_executable_);
UMA_HISTOGRAM_BOOLEAN("SBClientDownload.ZipFileHasArchiveButNoExecutable",
results.has_archive && !archived_executable_);
UMA_HISTOGRAM_TIMES("SBClientDownload.ExtractZipFeaturesTime",
base::TimeTicks::Now() - zip_analysis_start_time_);
for (const auto& file_name : results.archived_archive_filenames)
RecordArchivedArchiveFileExtensionType(file_name);
if (!archived_executable_) {
if (results.has_archive) {
type_ = ClientDownloadRequest::ZIPPED_ARCHIVE;
} else if (!results.success) {
// .zip files that look invalid to Chrome can often be successfully
// unpacked by other archive tools, so they may be a real threat.
type_ = ClientDownloadRequest::INVALID_ZIP;
} else {
// Normal zip w/o EXEs, or invalid zip and not extended-reporting.
PostFinishTask(DownloadCheckResult::UNKNOWN,
REASON_ARCHIVE_WITHOUT_BINARIES);
return;
}
}
OnFileFeatureExtractionDone();
}
#if defined(OS_MACOSX)
// This is called for .DMGs and other files that can be parsed by
// SandboxedDMGAnalyzer.
void CheckClientDownloadRequest::StartExtractDmgFeatures() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK(item_);
// Directly use 'dmg' extension since download file may not have any
// extension, but has still been deemed a DMG through file type sniffing.
bool too_big_to_unpack =
base::checked_cast<uint64_t>(item_->GetTotalBytes()) >
FileTypePolicies::GetInstance()->GetMaxFileSizeToAnalyze("dmg");
UMA_HISTOGRAM_BOOLEAN("SBClientDownload.DmgTooBigToUnpack",
too_big_to_unpack);
if (too_big_to_unpack) {
OnFileFeatureExtractionDone();
} else {
dmg_analyzer_ = new SandboxedDMGAnalyzer(
item_->GetFullPath(),
base::Bind(&CheckClientDownloadRequest::OnDmgAnalysisFinished,
weakptr_factory_.GetWeakPtr()),
content::ServiceManagerConnection::GetForProcess()->GetConnector());
dmg_analyzer_->Start();
dmg_analysis_start_time_ = base::TimeTicks::Now();
}
}
// Extracts DMG features if file has 'koly' signature, otherwise extracts
// regular file features.
void CheckClientDownloadRequest::ExtractFileOrDmgFeatures(
bool download_file_has_koly_signature) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
UMA_HISTOGRAM_BOOLEAN(
"SBClientDownload."
"DownloadFileWithoutDiskImageExtensionHasKolySignature",
download_file_has_koly_signature);
// Returns if DownloadItem was destroyed during parsing of file metadata.
if (item_ == nullptr)
return;
if (download_file_has_koly_signature)
StartExtractDmgFeatures();
else
StartExtractFileFeatures();
}
void CheckClientDownloadRequest::OnDmgAnalysisFinished(
const safe_browsing::ArchiveAnalyzerResults& results) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK_EQ(ClientDownloadRequest::MAC_EXECUTABLE, type_);
if (item_ == nullptr) {
PostFinishTask(DownloadCheckResult::UNKNOWN, REASON_REQUEST_CANCELED);
return;
}
if (!service_)
return;
if (results.signature_blob.size() > 0) {
disk_image_signature_ =
std::make_unique<std::vector<uint8_t>>(results.signature_blob);
}
detached_code_signatures_.CopyFrom(results.detached_code_signatures);
// Even if !results.success, some of the DMG may have been parsed.
archive_is_valid_ =
(results.success ? ArchiveValid::VALID : ArchiveValid::INVALID);
archived_executable_ = results.has_executable;
CopyArchivedBinaries(results.archived_binary, &archived_binaries_);
DVLOG(1) << "DMG analysis has finished for " << item_->GetFullPath().value()
<< ", has_executable=" << results.has_executable
<< ", success=" << results.success;
int64_t uma_file_type = FileTypePolicies::GetInstance()->UmaValueForFile(
item_->GetTargetFilePath());
if (results.success) {
base::UmaHistogramSparse("SBClientDownload.DmgFileSuccessByType",
uma_file_type);
} else {
base::UmaHistogramSparse("SBClientDownload.DmgFileFailureByType",
uma_file_type);
type_ = ClientDownloadRequest::MAC_ARCHIVE_FAILED_PARSING;
}
if (archived_executable_) {
base::UmaHistogramSparse("SBClientDownload.DmgFileHasExecutableByType",
uma_file_type);
UMA_HISTOGRAM_COUNTS_1M("SBClientDownload.DmgFileArchivedBinariesCount",
results.archived_binary.size());
} else {
base::UmaHistogramSparse("SBClientDownload.DmgFileHasNoExecutableByType",
uma_file_type);
}
UMA_HISTOGRAM_TIMES("SBClientDownload.ExtractDmgFeaturesTime",
base::TimeTicks::Now() - dmg_analysis_start_time_);
OnFileFeatureExtractionDone();
}
#endif // defined(OS_MACOSX)
bool CheckClientDownloadRequest::ShouldSampleWhitelistedDownload() {
// We currently sample 1% whitelisted downloads from users who opted
// in extended reporting and are not in incognito mode.
......@@ -995,8 +677,9 @@ void CheckClientDownloadRequest::SendRequest() {
}
#endif
if (archive_is_valid_ != ArchiveValid::UNSET)
request->set_archive_valid(archive_is_valid_ == ArchiveValid::VALID);
if (archive_is_valid_ != FileAnalyzer::ArchiveValid::UNSET)
request->set_archive_valid(archive_is_valid_ ==
FileAnalyzer::ArchiveValid::VALID);
request->mutable_signature()->CopyFrom(signature_info_);
if (image_headers_)
request->set_allocated_image_headers(image_headers_.release());
......
......@@ -19,6 +19,7 @@
#include "base/task/cancelable_task_tracker.h"
#include "build/build_config.h"
#include "chrome/browser/safe_browsing/download_protection/download_protection_util.h"
#include "chrome/browser/safe_browsing/download_protection/file_analyzer.h"
#include "chrome/browser/safe_browsing/safe_browsing_navigation_observer_manager.h"
#include "chrome/browser/safe_browsing/ui_manager.h"
#include "chrome/common/safe_browsing/binary_feature_extractor.h"
......@@ -79,7 +80,7 @@ class CheckClientDownloadRequest
// Performs file feature extraction and SafeBrowsing ping for downloads that
// don't match the URL whitelist.
void AnalyzeFile();
void OnFileFeatureExtractionDone();
void OnFileFeatureExtractionDone(FileAnalyzer::Results results);
void StartExtractFileFeatures();
void ExtractFileFeatures(const base::FilePath& file_path);
void StartExtractRarFeatures();
......@@ -114,8 +115,6 @@ class CheckClientDownloadRequest
bool CertificateChainIsWhitelisted(
const ClientDownloadRequest_CertificateChain& chain);
enum class ArchiveValid { UNSET, VALID, INVALID };
// The DownloadItem we are checking. Will be NULL if the request has been
// canceled. Must be accessed only on UI thread.
download::DownloadItem* item_;
......@@ -129,7 +128,7 @@ class CheckClientDownloadRequest
GURL tab_referrer_url_;
bool archived_executable_;
ArchiveValid archive_is_valid_;
FileAnalyzer::ArchiveValid archive_is_valid_;
#if defined(OS_MACOSX)
std::unique_ptr<std::vector<uint8_t>> disk_image_signature_;
......@@ -148,14 +147,7 @@ class CheckClientDownloadRequest
scoped_refptr<SafeBrowsingDatabaseManager> database_manager_;
const bool pingback_enabled_;
std::unique_ptr<network::SimpleURLLoader> loader_;
scoped_refptr<SandboxedRarAnalyzer> rar_analyzer_;
scoped_refptr<SandboxedZipAnalyzer> zip_analyzer_;
base::TimeTicks rar_analysis_start_time_;
base::TimeTicks zip_analysis_start_time_;
#if defined(OS_MACOSX)
scoped_refptr<SandboxedDMGAnalyzer> dmg_analyzer_;
base::TimeTicks dmg_analysis_start_time_;
#endif
std::unique_ptr<FileAnalyzer> file_analyzer_;
bool finished_;
ClientDownloadRequest::DownloadType type_;
std::string client_download_request_data_;
......
// Copyright 2017 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/check_client_download_request.h"
#include "base/memory/ref_counted.h"
#include "build/build_config.h"
#include "chrome/common/safe_browsing/file_type_policies_test_util.h"
#include "content/public/test/test_utils.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace safe_browsing {
// TODO(nparker): Move some testing from download_protection_service_unittest.cc
// to this file, and add speicific tests here.
class CheckClientDownloadRequestTest : public testing::Test {
public:
CheckClientDownloadRequestTest() {}
protected:
void SetUp() override {}
void TearDown() override {}
void SetMaxArchivedBinariesToReport(uint64_t target_limit) {
std::unique_ptr<DownloadFileTypeConfig> config =
policies_.DuplicateConfig();
config->set_max_archived_binaries_to_report(target_limit);
policies_.SwapConfig(config);
}
protected:
// This will effectivly mask the global Singleton while this is in scope.
FileTypePoliciesTestOverlay policies_;
};
TEST_F(CheckClientDownloadRequestTest, CheckLimitArchivedExtensions) {
CheckClientDownloadRequest::ArchivedBinaries src, dest;
const int max_to_try = 12;
for (int i = 0; i < max_to_try; i++) {
src.Add();
}
// First check against the value set in .asciipb, which is currently 10
// If that is raised above |max_to_try|, raise the latter.
dest.Clear();
CheckClientDownloadRequest::CopyArchivedBinaries(src, &dest);
EXPECT_EQ(10, dest.size());
SetMaxArchivedBinariesToReport(2);
dest.Clear();
CheckClientDownloadRequest::CopyArchivedBinaries(src, &dest);
EXPECT_EQ(2, dest.size());
SetMaxArchivedBinariesToReport(100000);
dest.Clear();
CheckClientDownloadRequest::CopyArchivedBinaries(src, &dest);
EXPECT_EQ(max_to_try, dest.size());
}
} // namespace safe_browsing
......@@ -44,6 +44,7 @@
#include "chrome/common/extensions/api/safe_browsing_private.h"
#include "chrome/common/safe_browsing/binary_feature_extractor.h"
#include "chrome/common/safe_browsing/file_type_policies_test_util.h"
#include "chrome/common/safe_browsing/mock_binary_feature_extractor.h"
#include "chrome/test/base/testing_browser_process.h"
#include "chrome/test/base/testing_profile.h"
#include "components/download/public/common/download_danger_type.h"
......@@ -173,25 +174,6 @@ class FakeSafeBrowsingService : public TestSafeBrowsingService {
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:
~MockBinaryFeatureExtractor() override {}
private:
DISALLOW_COPY_AND_ASSIGN(MockBinaryFeatureExtractor);
};
using NiceMockDownloadItem = NiceMock<download::MockDownloadItem>;
} // namespace
......@@ -1622,8 +1604,18 @@ TEST_F(DownloadProtectionServiceTest, CheckClientDownloadReportLargeDmg) {
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()));
// Set the max file size to unpack to 0, so that this DMG is now "too large"
std::unique_ptr<DownloadFileTypeConfig> config = policies_.DuplicateConfig();
for (int i = 0; i < config->file_types_size(); i++) {
if (config->file_types(i).extension() == "dmg") {
for (int j = 0; j < config->file_types(i).platform_settings_size(); j++) {
config->mutable_file_types(i)
->mutable_platform_settings(j)
->set_max_file_size_to_analyze(0);
}
}
}
policies_.SwapConfig(config);
RunLoop run_loop;
download_service_->CheckClientDownload(
......
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/safe_browsing/download_protection/file_analyzer.h"
#include "base/feature_list.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/task/post_task.h"
#include "base/task/task_traits.h"
#include "chrome/common/safe_browsing/archive_analyzer_results.h"
#include "chrome/common/safe_browsing/download_protection_util.h"
#include "chrome/common/safe_browsing/file_type_policies.h"
#include "components/safe_browsing/features.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/common/service_manager_connection.h"
namespace safe_browsing {
namespace {
using content::BrowserThread;
void CopyArchivedBinaries(
const google::protobuf::RepeatedPtrField<
ClientDownloadRequest::ArchivedBinary>& src_binaries,
google::protobuf::RepeatedPtrField<ClientDownloadRequest::ArchivedBinary>*
dest_binaries) {
// Limit the number of entries so we don't clog the backend.
// We can expand this limit by pushing a new download_file_types update.
int limit = FileTypePolicies::GetInstance()->GetMaxArchivedBinariesToReport();
dest_binaries->Clear();
for (int i = 0; i < limit && i < src_binaries.size(); i++) {
*dest_binaries->Add() = src_binaries[i];
}
}
void RecordArchivedArchiveFileExtensionType(const base::FilePath& file) {
base::UmaHistogramSparse(
"SBClientDownload.ArchivedArchiveExtensions",
FileTypePolicies::GetInstance()->UmaValueForFile(file));
}
FileAnalyzer::Results ExtractFileFeatures(
scoped_refptr<BinaryFeatureExtractor> binary_feature_extractor,
base::FilePath file_path) {
FileAnalyzer::Results results;
base::TimeTicks start_time = base::TimeTicks::Now();
binary_feature_extractor->CheckSignature(file_path, &results.signature_info);
bool is_signed = (results.signature_info.certificate_chain_size() > 0);
UMA_HISTOGRAM_BOOLEAN("SBClientDownload.SignedBinaryDownload", is_signed);
UMA_HISTOGRAM_TIMES("SBClientDownload.ExtractSignatureFeaturesTime",
base::TimeTicks::Now() - start_time);
start_time = base::TimeTicks::Now();
if (!binary_feature_extractor->ExtractImageFeatures(
file_path, BinaryFeatureExtractor::kDefaultOptions,
&results.image_headers, nullptr)) {
results.image_headers = ClientDownloadRequest::ImageHeaders();
}
UMA_HISTOGRAM_TIMES("SBClientDownload.ExtractImageHeadersTime",
base::TimeTicks::Now() - start_time);
return results;
}
} // namespace
FileAnalyzer::Results::Results() {}
FileAnalyzer::Results::~Results() {}
FileAnalyzer::Results::Results(const FileAnalyzer::Results& other) = default;
FileAnalyzer::FileAnalyzer(
scoped_refptr<BinaryFeatureExtractor> binary_feature_extractor)
: binary_feature_extractor_(binary_feature_extractor),
weakptr_factory_(this) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
}
FileAnalyzer::~FileAnalyzer() {}
void FileAnalyzer::Start(const base::FilePath& target_path,
const base::FilePath& tmp_path,
base::OnceCallback<void(Results)> callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
target_path_ = target_path;
tmp_path_ = tmp_path;
callback_ = std::move(callback);
results_.type = download_protection_util::GetDownloadType(target_path_);
if (target_path_.MatchesExtension(FILE_PATH_LITERAL(".zip"))) {
StartExtractZipFeatures();
} else if (target_path_.MatchesExtension(FILE_PATH_LITERAL(".rar")) &&
base::FeatureList::IsEnabled(kInspectDownloadedRarFiles)) {
StartExtractRarFeatures();
#if defined(OS_MACOSX)
} else if (target_path_.MatchesExtension(FILE_PATH_LITERAL(".dmg")) ||
target_path_.MatchesExtension(FILE_PATH_LITERAL(".img")) ||
target_path_.MatchesExtension(FILE_PATH_LITERAL(".iso")) ||
target_path_.MatchesExtension(FILE_PATH_LITERAL(".smi")) ||
target_path_.MatchesExtension(FILE_PATH_LITERAL(".cdr")) ||
target_path_.MatchesExtension(FILE_PATH_LITERAL(".dart")) ||
target_path_.MatchesExtension(FILE_PATH_LITERAL(".dc42")) ||
target_path_.MatchesExtension(FILE_PATH_LITERAL(".diskcopy42")) ||
target_path_.MatchesExtension(FILE_PATH_LITERAL(".dmgpart")) ||
target_path_.MatchesExtension(FILE_PATH_LITERAL(".dvdr")) ||
target_path_.MatchesExtension(FILE_PATH_LITERAL(".imgpart")) ||
target_path_.MatchesExtension(FILE_PATH_LITERAL(".ndif")) ||
target_path_.MatchesExtension(
FILE_PATH_LITERAL(".sparsebundle")) ||
target_path_.MatchesExtension(FILE_PATH_LITERAL(".sparseimage")) ||
target_path_.MatchesExtension(FILE_PATH_LITERAL(".toast")) ||
target_path_.MatchesExtension(FILE_PATH_LITERAL(".udif"))) {
StartExtractDmgFeatures();
#endif
} else {
#if defined(OS_MACOSX)
// Checks for existence of "koly" signature even if file doesn't have
// archive-type extension, then calls ExtractFileOrDmgFeatures() with
// result.
base::PostTaskWithTraitsAndReplyWithResult(
FROM_HERE, {base::MayBlock(), base::TaskPriority::BEST_EFFORT},
base::BindOnce(DiskImageTypeSnifferMac::IsAppleDiskImage, tmp_path_),
base::BindOnce(&FileAnalyzer::ExtractFileOrDmgFeatures,
weakptr_factory_.GetWeakPtr()));
#else
StartExtractFileFeatures();
#endif
}
}
void FileAnalyzer::StartExtractFileFeatures() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
base::PostTaskWithTraitsAndReplyWithResult(
FROM_HERE,
{base::MayBlock(), base::TaskPriority::BEST_EFFORT,
base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
base::BindOnce(&ExtractFileFeatures, binary_feature_extractor_,
tmp_path_),
base::BindOnce(&FileAnalyzer::OnFileAnalysisFinished,
weakptr_factory_.GetWeakPtr()));
}
void FileAnalyzer::OnFileAnalysisFinished(FileAnalyzer::Results results) {
results.type = download_protection_util::GetDownloadType(target_path_);
results.archive_is_valid = ArchiveValid::UNSET;
std::move(callback_).Run(results);
}
void FileAnalyzer::StartExtractZipFeatures() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
zip_analysis_start_time_ = base::TimeTicks::Now();
// We give the zip analyzer a weak pointer to this object. Since the
// analyzer is refcounted, it might outlive the request.
zip_analyzer_ = new SandboxedZipAnalyzer(
tmp_path_,
base::BindRepeating(&FileAnalyzer::OnZipAnalysisFinished,
weakptr_factory_.GetWeakPtr()),
content::ServiceManagerConnection::GetForProcess()->GetConnector());
zip_analyzer_->Start();
}
void FileAnalyzer::OnZipAnalysisFinished(
const ArchiveAnalyzerResults& archive_results) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
// Even if !results.success, some of the zip may have been parsed.
// Some unzippers will successfully unpack archives that we cannot,
// so we're lenient here.
results_.archive_is_valid =
(archive_results.success ? ArchiveValid::VALID : ArchiveValid::INVALID);
results_.archived_executable = archive_results.has_executable;
results_.archived_archive = archive_results.has_archive;
CopyArchivedBinaries(archive_results.archived_binary,
&results_.archived_binaries);
// Log metrics for ZIP analysis
if (results_.archived_executable) {
UMA_HISTOGRAM_COUNTS_1M("SBClientDownload.ZipFileArchivedBinariesCount",
archive_results.archived_binary.size());
}
UMA_HISTOGRAM_BOOLEAN("SBClientDownload.ZipFileSuccess",
archive_results.success);
UMA_HISTOGRAM_BOOLEAN("SBClientDownload.ZipFileHasExecutable",
archive_results.has_executable);
UMA_HISTOGRAM_BOOLEAN(
"SBClientDownload.ZipFileHasArchiveButNoExecutable",
archive_results.has_archive && !archive_results.has_executable);
UMA_HISTOGRAM_TIMES("SBClientDownload.ExtractZipFeaturesTime",
base::TimeTicks::Now() - zip_analysis_start_time_);
for (const auto& file_name : archive_results.archived_archive_filenames)
RecordArchivedArchiveFileExtensionType(file_name);
if (!results_.archived_executable) {
if (archive_results.has_archive) {
results_.type = ClientDownloadRequest::ZIPPED_ARCHIVE;
} else if (!archive_results.success) {
// .zip files that look invalid to Chrome can often be successfully
// unpacked by other archive tools, so they may be a real threat.
results_.type = ClientDownloadRequest::INVALID_ZIP;
}
} else {
results_.type = ClientDownloadRequest::ZIPPED_EXECUTABLE;
}
std::move(callback_).Run(std::move(results_));
}
void FileAnalyzer::StartExtractRarFeatures() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
rar_analysis_start_time_ = base::TimeTicks::Now();
// We give the rar analyzer a weak pointer to this object. Since the
// analyzer is refcounted, it might outlive the request.
rar_analyzer_ = new SandboxedRarAnalyzer(
tmp_path_,
base::BindRepeating(&FileAnalyzer::OnRarAnalysisFinished,
weakptr_factory_.GetWeakPtr()),
content::ServiceManagerConnection::GetForProcess()->GetConnector());
rar_analyzer_->Start();
}
void FileAnalyzer::OnRarAnalysisFinished(
const ArchiveAnalyzerResults& archive_results) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
results_.archive_is_valid =
(archive_results.success ? ArchiveValid::VALID : ArchiveValid::INVALID);
results_.archived_executable = archive_results.has_executable;
results_.archived_archive = archive_results.has_archive;
CopyArchivedBinaries(archive_results.archived_binary,
&results_.archived_binaries);
// Log metrics for Rar Analysis
if (results_.archived_executable) {
UMA_HISTOGRAM_COUNTS_100("SBClientDownload.RarFileArchivedBinariesCount",
archive_results.archived_binary.size());
}
UMA_HISTOGRAM_BOOLEAN("SBClientDownload.RarFileSuccess",
archive_results.success);
UMA_HISTOGRAM_BOOLEAN("SBClientDownload.RarFileHasExecutable",
results_.archived_executable);
UMA_HISTOGRAM_BOOLEAN(
"SBClientDownload.RarFileHasArchiveButNoExecutable",
archive_results.has_archive && !archive_results.has_executable);
UMA_HISTOGRAM_TIMES("SBClientDownload.ExtractRarFeaturesTime",
base::TimeTicks::Now() - rar_analysis_start_time_);
for (const auto& file_name : archive_results.archived_archive_filenames)
RecordArchivedArchiveFileExtensionType(file_name);
if (!results_.archived_executable) {
if (archive_results.has_archive) {
results_.type = ClientDownloadRequest::RAR_COMPRESSED_ARCHIVE;
} else if (!archive_results.success) {
// .rar files that look invalid to Chrome may be successfully unpacked by
// other archive tools, so they may be a real threat.
results_.type = ClientDownloadRequest::INVALID_RAR;
}
} else {
results_.type = ClientDownloadRequest::RAR_COMPRESSED_EXECUTABLE;
}
std::move(callback_).Run(std::move(results_));
}
#if defined(OS_MACOSX)
// This is called for .DMGs and other files that can be parsed by
// SandboxedDMGAnalyzer.
void FileAnalyzer::StartExtractDmgFeatures() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
// Directly use 'dmg' extension since download file may not have any
// extension, but has still been deemed a DMG through file type sniffing.
base::File file(tmp_path_, base::File::FLAG_OPEN | base::File::FLAG_READ);
if (!file.IsValid()) {
std::move(callback_).Run(std::move(results_));
return;
}
int64_t size = file.GetLength();
bool too_big_to_unpack =
base::checked_cast<uint64_t>(size) >
FileTypePolicies::GetInstance()->GetMaxFileSizeToAnalyze("dmg");
UMA_HISTOGRAM_BOOLEAN("SBClientDownload.DmgTooBigToUnpack",
too_big_to_unpack);
if (too_big_to_unpack) {
std::move(callback_).Run(std::move(results_));
} else {
dmg_analyzer_ = new SandboxedDMGAnalyzer(
tmp_path_,
base::BindRepeating(&FileAnalyzer::OnDmgAnalysisFinished,
weakptr_factory_.GetWeakPtr()),
content::ServiceManagerConnection::GetForProcess()->GetConnector());
dmg_analyzer_->Start();
dmg_analysis_start_time_ = base::TimeTicks::Now();
}
}
void FileAnalyzer::ExtractFileOrDmgFeatures(
bool download_file_has_koly_signature) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
UMA_HISTOGRAM_BOOLEAN(
"SBClientDownload."
"DownloadFileWithoutDiskImageExtensionHasKolySignature",
download_file_has_koly_signature);
if (download_file_has_koly_signature)
StartExtractDmgFeatures();
else
StartExtractFileFeatures();
}
void FileAnalyzer::OnDmgAnalysisFinished(
const safe_browsing::ArchiveAnalyzerResults& archive_results) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (archive_results.signature_blob.size() > 0) {
results_.disk_image_signature =
std::vector<uint8_t>(archive_results.signature_blob);
}
results_.detached_code_signatures.CopyFrom(
archive_results.detached_code_signatures);
// Even if !results.success, some of the DMG may have been parsed.
results_.archive_is_valid =
(archive_results.success ? ArchiveValid::VALID : ArchiveValid::INVALID);
results_.archived_executable = archive_results.has_executable;
results_.archived_archive = archive_results.has_archive;
CopyArchivedBinaries(archive_results.archived_binary,
&results_.archived_binaries);
// Log metrics for DMG analysis.
int64_t uma_file_type =
FileTypePolicies::GetInstance()->UmaValueForFile(target_path_);
if (archive_results.success) {
base::UmaHistogramSparse("SBClientDownload.DmgFileSuccessByType",
uma_file_type);
results_.type = ClientDownloadRequest::MAC_EXECUTABLE;
} else {
base::UmaHistogramSparse("SBClientDownload.DmgFileFailureByType",
uma_file_type);
results_.type = ClientDownloadRequest::MAC_ARCHIVE_FAILED_PARSING;
}
if (results_.archived_executable) {
base::UmaHistogramSparse("SBClientDownload.DmgFileHasExecutableByType",
uma_file_type);
UMA_HISTOGRAM_COUNTS_1M("SBClientDownload.DmgFileArchivedBinariesCount",
archive_results.archived_binary.size());
} else {
base::UmaHistogramSparse("SBClientDownload.DmgFileHasNoExecutableByType",
uma_file_type);
}
UMA_HISTOGRAM_TIMES("SBClientDownload.ExtractDmgFeaturesTime",
base::TimeTicks::Now() - dmg_analysis_start_time_);
std::move(callback_).Run(std::move(results_));
}
#endif // defined(OS_MACOSX)
} // namespace safe_browsing
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_BROWSER_SAFE_BROWSING_DOWNLOAD_PROTECTION_FILE_ANALYZER_H_
#define CHROME_BROWSER_SAFE_BROWSING_DOWNLOAD_PROTECTION_FILE_ANALYZER_H_
#include "base/callback.h"
#include "base/files/file_path.h"
#include "base/memory/scoped_refptr.h"
#include "base/memory/weak_ptr.h"
#include "build/build_config.h"
#include "chrome/common/safe_browsing/binary_feature_extractor.h"
#include "chrome/services/file_util/public/cpp/sandboxed_rar_analyzer.h"
#include "chrome/services/file_util/public/cpp/sandboxed_zip_analyzer.h"
#include "components/safe_browsing/proto/csd.pb.h"
#include "third_party/protobuf/src/google/protobuf/repeated_field.h"
#if defined(OS_MACOSX)
#include "chrome/common/safe_browsing/disk_image_type_sniffer_mac.h"
#include "chrome/services/file_util/public/cpp/sandboxed_dmg_analyzer_mac.h"
#endif
namespace safe_browsing {
// This class does the file content analysis for a user download, extracting the
// features that will be sent to the SB backend. This class lives on the UI
// thread, which is where the result callback will be invoked.
class FileAnalyzer {
public:
enum class ArchiveValid { UNSET, VALID, INVALID };
// This struct holds the possible features extracted from a file.
struct Results {
Results();
Results(const Results& other);
~Results();
// When analyzing a ZIP or RAR, the type becomes clarified by content
// inspection (does it contain binaries/archives?). So we return a type.
ClientDownloadRequest::DownloadType type;
// For archive files, whether the archive is valid. Has unspecified contents
// for non-archive files.
ArchiveValid archive_is_valid;
// For archive files, whether the archive contains an executable. Has
// unspecified contents for non-archive files.
bool archived_executable;
// For archive files, whether the archive contains an archive. Has
// unspecified contents for non-archive files.
bool archived_archive;
// For archive files, the features extracted from each contained
// archive/binary.
google::protobuf::RepeatedPtrField<ClientDownloadRequest::ArchivedBinary>
archived_binaries;
// For executables, information about the signature of the executable.
ClientDownloadRequest::SignatureInfo signature_info;
// For executables, information about the file headers.
ClientDownloadRequest::ImageHeaders image_headers;
#if defined(OS_MACOSX)
// For DMG files, the signature of the DMG.
std::vector<uint8_t> disk_image_signature;
// For DMG files, any detached code signatures in the DMG.
google::protobuf::RepeatedPtrField<
ClientDownloadRequest::DetachedCodeSignature>
detached_code_signatures;
#endif
};
explicit FileAnalyzer(
scoped_refptr<BinaryFeatureExtractor> binary_feature_extractor);
~FileAnalyzer();
void Start(const base::FilePath& target_path,
const base::FilePath& tmp_path,
base::OnceCallback<void(Results)> callback);
private:
void StartExtractFileFeatures();
void OnFileAnalysisFinished(FileAnalyzer::Results results);
void StartExtractZipFeatures();
void OnZipAnalysisFinished(const ArchiveAnalyzerResults& archive_results);
void StartExtractRarFeatures();
void OnRarAnalysisFinished(const ArchiveAnalyzerResults& archive_results);
#if defined(OS_MACOSX)
void StartExtractDmgFeatures();
void ExtractFileOrDmgFeatures(bool download_file_has_koly_signature);
void OnDmgAnalysisFinished(
const safe_browsing::ArchiveAnalyzerResults& archive_results);
#endif
base::FilePath target_path_;
base::FilePath tmp_path_;
scoped_refptr<BinaryFeatureExtractor> binary_feature_extractor_;
base::OnceCallback<void(Results)> callback_;
Results results_;
scoped_refptr<SandboxedZipAnalyzer> zip_analyzer_;
base::TimeTicks zip_analysis_start_time_;
scoped_refptr<SandboxedRarAnalyzer> rar_analyzer_;
base::TimeTicks rar_analysis_start_time_;
#if defined(OS_MACOSX)
scoped_refptr<SandboxedDMGAnalyzer> dmg_analyzer_;
base::TimeTicks dmg_analysis_start_time_;
#endif
base::WeakPtrFactory<FileAnalyzer> weakptr_factory_;
};
} // namespace safe_browsing
#endif // CHROME_BROWSER_SAFE_BROWSING_DOWNLOAD_PROTECTION_FILE_ANALYZER_H_
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/safe_browsing/download_protection/file_analyzer.h"
#include "base/callback.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/path_service.h"
#include "base/run_loop.h"
#include "build/build_config.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/safe_browsing/file_type_policies_test_util.h"
#include "chrome/common/safe_browsing/mock_binary_feature_extractor.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "content/public/test/test_utils.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/zlib/google/zip.h"
namespace safe_browsing {
using ::testing::_;
using ::testing::IsEmpty;
using ::testing::Return;
using ::testing::SetArgPointee;
using ::testing::SizeIs;
using ::testing::StrEq;
using ::testing::DoAll;
class FileAnalyzerTest : public testing::Test {
public:
FileAnalyzerTest() {}
void DoneCallback(base::OnceCallback<void()> quit_callback,
FileAnalyzer::Results result) {
result_ = result;
has_result_ = true;
std::move(quit_callback).Run();
}
protected:
void SetUp() override { has_result_ = false; }
void TearDown() override {}
protected:
bool has_result_;
FileAnalyzer::Results result_;
private:
content::TestBrowserThreadBundle test_browser_thread_bundle_;
content::InProcessUtilityThreadHelper in_process_utility_thread_helper_;
};
TEST_F(FileAnalyzerTest, TypeWinExecutable) {
scoped_refptr<MockBinaryFeatureExtractor> extractor =
new testing::StrictMock<MockBinaryFeatureExtractor>();
FileAnalyzer analyzer(extractor);
base::RunLoop run_loop;
base::FilePath target_path(FILE_PATH_LITERAL("target.exe"));
base::FilePath tmp_path(FILE_PATH_LITERAL("tmp.crdownload"));
EXPECT_CALL(*extractor, CheckSignature(tmp_path, _)).WillOnce(Return());
EXPECT_CALL(*extractor, ExtractImageFeatures(tmp_path, _, _, _))
.WillRepeatedly(Return(true));
analyzer.Start(
target_path, tmp_path,
base::BindOnce(&FileAnalyzerTest::DoneCallback, base::Unretained(this),
run_loop.QuitClosure()));
run_loop.Run();
ASSERT_TRUE(has_result_);
EXPECT_EQ(result_.type, ClientDownloadRequest::WIN_EXECUTABLE);
}
TEST_F(FileAnalyzerTest, TypeChromeExtension) {
scoped_refptr<MockBinaryFeatureExtractor> extractor =
new testing::StrictMock<MockBinaryFeatureExtractor>();
FileAnalyzer analyzer(extractor);
base::RunLoop run_loop;
base::FilePath target_path(FILE_PATH_LITERAL("target.crx"));
base::FilePath tmp_path(FILE_PATH_LITERAL("tmp.crdownload"));
EXPECT_CALL(*extractor, CheckSignature(tmp_path, _)).WillOnce(Return());
EXPECT_CALL(*extractor, ExtractImageFeatures(tmp_path, _, _, _))
.WillRepeatedly(Return(true));
analyzer.Start(
target_path, tmp_path,
base::BindOnce(&FileAnalyzerTest::DoneCallback, base::Unretained(this),
run_loop.QuitClosure()));
run_loop.Run();
ASSERT_TRUE(has_result_);
EXPECT_EQ(result_.type, ClientDownloadRequest::CHROME_EXTENSION);
}
TEST_F(FileAnalyzerTest, TypeAndroidApk) {
scoped_refptr<MockBinaryFeatureExtractor> extractor =
new testing::StrictMock<MockBinaryFeatureExtractor>();
FileAnalyzer analyzer(extractor);
base::RunLoop run_loop;
base::FilePath target_path(FILE_PATH_LITERAL("target.apk"));
base::FilePath tmp_path(FILE_PATH_LITERAL("tmp.crdownload"));
EXPECT_CALL(*extractor, CheckSignature(tmp_path, _)).WillOnce(Return());
EXPECT_CALL(*extractor, ExtractImageFeatures(tmp_path, _, _, _))
.WillRepeatedly(Return(true));
analyzer.Start(
target_path, tmp_path,
base::BindOnce(&FileAnalyzerTest::DoneCallback, base::Unretained(this),
run_loop.QuitClosure()));
run_loop.Run();
ASSERT_TRUE(has_result_);
EXPECT_EQ(result_.type, ClientDownloadRequest::ANDROID_APK);
}
TEST_F(FileAnalyzerTest, TypeZippedExecutable) {
scoped_refptr<MockBinaryFeatureExtractor> extractor =
new testing::StrictMock<MockBinaryFeatureExtractor>();
FileAnalyzer analyzer(extractor);
base::RunLoop run_loop;
base::FilePath target_path(FILE_PATH_LITERAL("target.zip"));
base::FilePath tmp_path(FILE_PATH_LITERAL("tmp.crdownload"));
base::ScopedTempDir zip_source_dir;
ASSERT_TRUE(zip_source_dir.CreateUniqueTempDir());
std::string file_contents = "dummy file";
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,
/* include_hidden_files= */ false));
analyzer.Start(
target_path, tmp_path,
base::BindOnce(&FileAnalyzerTest::DoneCallback, base::Unretained(this),
run_loop.QuitClosure()));
run_loop.Run();
ASSERT_TRUE(has_result_);
EXPECT_EQ(result_.type, ClientDownloadRequest::ZIPPED_EXECUTABLE);
}
TEST_F(FileAnalyzerTest, TypeMacExecutable) {
scoped_refptr<MockBinaryFeatureExtractor> extractor =
new testing::StrictMock<MockBinaryFeatureExtractor>();
FileAnalyzer analyzer(extractor);
base::RunLoop run_loop;
base::FilePath target_path(FILE_PATH_LITERAL("target.pkg"));
base::FilePath tmp_path(FILE_PATH_LITERAL("tmp.crdownload"));
EXPECT_CALL(*extractor, CheckSignature(tmp_path, _)).WillOnce(Return());
EXPECT_CALL(*extractor, ExtractImageFeatures(tmp_path, _, _, _))
.WillRepeatedly(Return(true));
analyzer.Start(
target_path, tmp_path,
base::BindOnce(&FileAnalyzerTest::DoneCallback, base::Unretained(this),
run_loop.QuitClosure()));
run_loop.Run();
ASSERT_TRUE(has_result_);
EXPECT_EQ(result_.type, ClientDownloadRequest::MAC_EXECUTABLE);
}
TEST_F(FileAnalyzerTest, TypeZippedArchive) {
scoped_refptr<MockBinaryFeatureExtractor> extractor =
new testing::StrictMock<MockBinaryFeatureExtractor>();
FileAnalyzer analyzer(extractor);
base::RunLoop run_loop;
base::FilePath target_path(FILE_PATH_LITERAL("target.zip"));
base::FilePath tmp_path(FILE_PATH_LITERAL("tmp.crdownload"));
base::ScopedTempDir zip_source_dir;
ASSERT_TRUE(zip_source_dir.CreateUniqueTempDir());
std::string file_contents = "dummy file";
ASSERT_EQ(static_cast<int>(file_contents.size()),
base::WriteFile(
zip_source_dir.GetPath().Append(FILE_PATH_LITERAL("file.zip")),
file_contents.data(), file_contents.size()));
ASSERT_TRUE(zip::Zip(zip_source_dir.GetPath(), tmp_path,
/* include_hidden_files= */ false));
analyzer.Start(
target_path, tmp_path,
base::BindOnce(&FileAnalyzerTest::DoneCallback, base::Unretained(this),
run_loop.QuitClosure()));
run_loop.Run();
ASSERT_TRUE(has_result_);
EXPECT_EQ(result_.type, ClientDownloadRequest::ZIPPED_ARCHIVE);
}
TEST_F(FileAnalyzerTest, TypeInvalidZip) {
scoped_refptr<MockBinaryFeatureExtractor> extractor =
new testing::StrictMock<MockBinaryFeatureExtractor>();
FileAnalyzer analyzer(extractor);
base::RunLoop run_loop;
base::FilePath target_path(FILE_PATH_LITERAL("target.zip"));
base::FilePath tmp_path(FILE_PATH_LITERAL("tmp.crdownload"));
std::string file_contents = "invalid contents";
ASSERT_EQ(
static_cast<int>(file_contents.size()),
base::WriteFile(tmp_path, file_contents.data(), file_contents.size()));
analyzer.Start(
target_path, tmp_path,
base::BindOnce(&FileAnalyzerTest::DoneCallback, base::Unretained(this),
run_loop.QuitClosure()));
run_loop.Run();
ASSERT_TRUE(has_result_);
EXPECT_EQ(result_.type, ClientDownloadRequest::INVALID_ZIP);
}
// Since we only inspect contents of DMGs on OS X, we only get
// MAC_ARCHIVE_FAILED_PARSING on OS X.
#if defined(OS_MACOSX)
TEST_F(FileAnalyzerTest, TypeInvalidDmg) {
scoped_refptr<MockBinaryFeatureExtractor> extractor =
new testing::StrictMock<MockBinaryFeatureExtractor>();
FileAnalyzer analyzer(extractor);
base::RunLoop run_loop;
base::FilePath target_path(FILE_PATH_LITERAL("target.dmg"));
base::FilePath tmp_path(FILE_PATH_LITERAL("tmp.crdownload"));
std::string file_contents = "invalid contents";
ASSERT_EQ(
static_cast<int>(file_contents.size()),
base::WriteFile(tmp_path, file_contents.data(), file_contents.size()));
analyzer.Start(
target_path, tmp_path,
base::BindOnce(&FileAnalyzerTest::DoneCallback, base::Unretained(this),
run_loop.QuitClosure()));
run_loop.Run();
ASSERT_TRUE(has_result_);
EXPECT_EQ(result_.type, ClientDownloadRequest::MAC_ARCHIVE_FAILED_PARSING);
}
#endif
// TODO(drubery): Add tests verifying Rar inspection
TEST_F(FileAnalyzerTest, ArchiveIsValidUnsetForNonArchive) {
scoped_refptr<MockBinaryFeatureExtractor> extractor =
new testing::StrictMock<MockBinaryFeatureExtractor>();
FileAnalyzer analyzer(extractor);
base::RunLoop run_loop;
base::FilePath target_path(FILE_PATH_LITERAL("target.exe"));
base::FilePath tmp_path(FILE_PATH_LITERAL("tmp.crdownload"));
EXPECT_CALL(*extractor, CheckSignature(tmp_path, _)).WillOnce(Return());
EXPECT_CALL(*extractor, ExtractImageFeatures(tmp_path, _, _, _))
.WillRepeatedly(Return(true));
analyzer.Start(
target_path, tmp_path,
base::BindOnce(&FileAnalyzerTest::DoneCallback, base::Unretained(this),
run_loop.QuitClosure()));
run_loop.Run();
ASSERT_TRUE(has_result_);
EXPECT_EQ(result_.archive_is_valid, FileAnalyzer::ArchiveValid::UNSET);
}
TEST_F(FileAnalyzerTest, ArchiveIsValidSetForValidArchive) {
scoped_refptr<MockBinaryFeatureExtractor> extractor =
new testing::StrictMock<MockBinaryFeatureExtractor>();
FileAnalyzer analyzer(extractor);
base::RunLoop run_loop;
base::FilePath target_path(FILE_PATH_LITERAL("target.zip"));
base::FilePath tmp_path(FILE_PATH_LITERAL("tmp.crdownload"));
base::ScopedTempDir zip_source_dir;
ASSERT_TRUE(zip_source_dir.CreateUniqueTempDir());
std::string file_contents = "dummy file";
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,
/* include_hidden_files= */ false));
analyzer.Start(
target_path, tmp_path,
base::BindOnce(&FileAnalyzerTest::DoneCallback, base::Unretained(this),
run_loop.QuitClosure()));
run_loop.Run();
ASSERT_TRUE(has_result_);
EXPECT_EQ(result_.archive_is_valid, FileAnalyzer::ArchiveValid::VALID);
}
TEST_F(FileAnalyzerTest, ArchiveIsValidSetForInvalidArchive) {
scoped_refptr<MockBinaryFeatureExtractor> extractor =
new testing::StrictMock<MockBinaryFeatureExtractor>();
FileAnalyzer analyzer(extractor);
base::RunLoop run_loop;
base::FilePath target_path(FILE_PATH_LITERAL("target.zip"));
base::FilePath tmp_path(FILE_PATH_LITERAL("tmp.crdownload"));
std::string file_contents = "invalid zip";
ASSERT_EQ(
static_cast<int>(file_contents.size()),
base::WriteFile(tmp_path, file_contents.data(), file_contents.size()));
analyzer.Start(
target_path, tmp_path,
base::BindOnce(&FileAnalyzerTest::DoneCallback, base::Unretained(this),
run_loop.QuitClosure()));
run_loop.Run();
ASSERT_TRUE(has_result_);
EXPECT_EQ(result_.archive_is_valid, FileAnalyzer::ArchiveValid::INVALID);
}
TEST_F(FileAnalyzerTest, ArchivedExecutableSetForZipWithExecutable) {
scoped_refptr<MockBinaryFeatureExtractor> extractor =
new testing::StrictMock<MockBinaryFeatureExtractor>();
FileAnalyzer analyzer(extractor);
base::RunLoop run_loop;
base::FilePath target_path(FILE_PATH_LITERAL("target.zip"));
base::FilePath tmp_path(FILE_PATH_LITERAL("tmp.crdownload"));
base::ScopedTempDir zip_source_dir;
ASSERT_TRUE(zip_source_dir.CreateUniqueTempDir());
std::string file_contents = "dummy file";
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,
/* include_hidden_files= */ false));
analyzer.Start(
target_path, tmp_path,
base::BindOnce(&FileAnalyzerTest::DoneCallback, base::Unretained(this),
run_loop.QuitClosure()));
run_loop.Run();
ASSERT_TRUE(has_result_);
EXPECT_TRUE(result_.archived_executable);
}
TEST_F(FileAnalyzerTest, ArchivedExecutableFalseForZipNoExecutable) {
scoped_refptr<MockBinaryFeatureExtractor> extractor =
new testing::StrictMock<MockBinaryFeatureExtractor>();
FileAnalyzer analyzer(extractor);
base::RunLoop run_loop;
base::FilePath target_path(FILE_PATH_LITERAL("target.zip"));
base::FilePath tmp_path(FILE_PATH_LITERAL("tmp.crdownload"));
base::ScopedTempDir zip_source_dir;
ASSERT_TRUE(zip_source_dir.CreateUniqueTempDir());
std::string file_contents = "dummy 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,
/* include_hidden_files= */ false));
analyzer.Start(
target_path, tmp_path,
base::BindOnce(&FileAnalyzerTest::DoneCallback, base::Unretained(this),
run_loop.QuitClosure()));
run_loop.Run();
ASSERT_TRUE(has_result_);
EXPECT_FALSE(result_.archived_executable);
}
TEST_F(FileAnalyzerTest, ArchivedArchiveSetForZipWithArchive) {
scoped_refptr<MockBinaryFeatureExtractor> extractor =
new testing::StrictMock<MockBinaryFeatureExtractor>();
FileAnalyzer analyzer(extractor);
base::RunLoop run_loop;
base::FilePath target_path(FILE_PATH_LITERAL("target.zip"));
base::FilePath tmp_path(FILE_PATH_LITERAL("tmp.crdownload"));
base::ScopedTempDir zip_source_dir;
ASSERT_TRUE(zip_source_dir.CreateUniqueTempDir());
std::string file_contents = "dummy file";
ASSERT_EQ(static_cast<int>(file_contents.size()),
base::WriteFile(
zip_source_dir.GetPath().Append(FILE_PATH_LITERAL("file.zip")),
file_contents.data(), file_contents.size()));
ASSERT_TRUE(zip::Zip(zip_source_dir.GetPath(), tmp_path,
/* include_hidden_files= */ false));
analyzer.Start(
target_path, tmp_path,
base::BindOnce(&FileAnalyzerTest::DoneCallback, base::Unretained(this),
run_loop.QuitClosure()));
run_loop.Run();
ASSERT_TRUE(has_result_);
EXPECT_TRUE(result_.archived_archive);
}
TEST_F(FileAnalyzerTest, ArchivedArchiveSetForZipNoArchive) {
scoped_refptr<MockBinaryFeatureExtractor> extractor =
new testing::StrictMock<MockBinaryFeatureExtractor>();
FileAnalyzer analyzer(extractor);
base::RunLoop run_loop;
base::FilePath target_path(FILE_PATH_LITERAL("target.zip"));
base::FilePath tmp_path(FILE_PATH_LITERAL("tmp.crdownload"));
base::ScopedTempDir zip_source_dir;
ASSERT_TRUE(zip_source_dir.CreateUniqueTempDir());
std::string file_contents = "dummy 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,
/* include_hidden_files= */ false));
analyzer.Start(
target_path, tmp_path,
base::BindOnce(&FileAnalyzerTest::DoneCallback, base::Unretained(this),
run_loop.QuitClosure()));
run_loop.Run();
ASSERT_TRUE(has_result_);
EXPECT_FALSE(result_.archived_archive);
}
TEST_F(FileAnalyzerTest, ArchivedBinariesHasArchiveAndExecutable) {
scoped_refptr<MockBinaryFeatureExtractor> extractor =
new testing::StrictMock<MockBinaryFeatureExtractor>();
FileAnalyzer analyzer(extractor);
base::RunLoop run_loop;
base::FilePath target_path(FILE_PATH_LITERAL("target.zip"));
base::FilePath tmp_path(FILE_PATH_LITERAL("tmp.crdownload"));
base::ScopedTempDir zip_source_dir;
ASSERT_TRUE(zip_source_dir.CreateUniqueTempDir());
std::string file_contents = "dummy file";
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_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,
/* include_hidden_files= */ false));
analyzer.Start(
target_path, tmp_path,
base::BindOnce(&FileAnalyzerTest::DoneCallback, base::Unretained(this),
run_loop.QuitClosure()));
run_loop.Run();
ASSERT_TRUE(has_result_);
EXPECT_THAT(result_.archived_binaries, SizeIs(2));
}
TEST_F(FileAnalyzerTest, ArchivedBinariesSkipsSafeFiles) {
scoped_refptr<MockBinaryFeatureExtractor> extractor =
new testing::StrictMock<MockBinaryFeatureExtractor>();
FileAnalyzer analyzer(extractor);
base::RunLoop run_loop;
base::FilePath target_path(FILE_PATH_LITERAL("target.zip"));
base::FilePath tmp_path(FILE_PATH_LITERAL("tmp.crdownload"));
base::ScopedTempDir zip_source_dir;
ASSERT_TRUE(zip_source_dir.CreateUniqueTempDir());
std::string file_contents = "dummy 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,
/* include_hidden_files= */ false));
analyzer.Start(
target_path, tmp_path,
base::BindOnce(&FileAnalyzerTest::DoneCallback, base::Unretained(this),
run_loop.QuitClosure()));
run_loop.Run();
ASSERT_TRUE(has_result_);
EXPECT_THAT(result_.archived_binaries, IsEmpty());
}
TEST_F(FileAnalyzerTest, ArchivedBinariesRespectsPolicyMaximum) {
// Set the policy maximum to 1
FileTypePoliciesTestOverlay policies;
std::unique_ptr<DownloadFileTypeConfig> config = policies.DuplicateConfig();
config->set_max_archived_binaries_to_report(1);
policies.SwapConfig(config);
// Analyze an archive with 2 binaries
scoped_refptr<MockBinaryFeatureExtractor> extractor =
new testing::StrictMock<MockBinaryFeatureExtractor>();
FileAnalyzer analyzer(extractor);
base::RunLoop run_loop;
base::FilePath target_path(FILE_PATH_LITERAL("target.zip"));
base::FilePath tmp_path(FILE_PATH_LITERAL("tmp.crdownload"));
base::ScopedTempDir zip_source_dir;
ASSERT_TRUE(zip_source_dir.CreateUniqueTempDir());
std::string file_contents = "dummy file";
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_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,
/* include_hidden_files= */ false));
analyzer.Start(
target_path, tmp_path,
base::BindOnce(&FileAnalyzerTest::DoneCallback, base::Unretained(this),
run_loop.QuitClosure()));
run_loop.Run();
ASSERT_TRUE(has_result_);
EXPECT_THAT(result_.archived_binaries, SizeIs(1));
}
TEST_F(FileAnalyzerTest, ExtractsFileSignatureForExe) {
scoped_refptr<MockBinaryFeatureExtractor> extractor =
new testing::StrictMock<MockBinaryFeatureExtractor>();
FileAnalyzer analyzer(extractor);
base::RunLoop run_loop;
base::FilePath target_path(FILE_PATH_LITERAL("target.exe"));
base::FilePath tmp_path(FILE_PATH_LITERAL("tmp.exe"));
ClientDownloadRequest::SignatureInfo signature;
*signature.add_signed_data() = "signature";
EXPECT_CALL(*extractor, CheckSignature(tmp_path, _))
.WillOnce(SetArgPointee<1>(signature));
EXPECT_CALL(*extractor, ExtractImageFeatures(tmp_path, _, _, _))
.WillRepeatedly(Return(true));
analyzer.Start(
target_path, tmp_path,
base::BindOnce(&FileAnalyzerTest::DoneCallback, base::Unretained(this),
run_loop.QuitClosure()));
run_loop.Run();
ASSERT_TRUE(has_result_);
EXPECT_THAT(result_.signature_info.signed_data(), SizeIs(1));
EXPECT_THAT(result_.signature_info.signed_data(0), StrEq("signature"));
}
TEST_F(FileAnalyzerTest, ExtractsImageHeadersForExe) {
scoped_refptr<MockBinaryFeatureExtractor> extractor =
new testing::StrictMock<MockBinaryFeatureExtractor>();
FileAnalyzer analyzer(extractor);
base::RunLoop run_loop;
base::FilePath target_path(FILE_PATH_LITERAL("target.exe"));
base::FilePath tmp_path(FILE_PATH_LITERAL("tmp.exe"));
ClientDownloadRequest::ImageHeaders image_headers;
image_headers.mutable_pe_headers()->set_file_header("image header");
EXPECT_CALL(*extractor, CheckSignature(tmp_path, _)).WillOnce(Return());
EXPECT_CALL(*extractor, ExtractImageFeatures(tmp_path, _, _, _))
.WillRepeatedly(DoAll(SetArgPointee<2>(image_headers), Return(true)));
analyzer.Start(
target_path, tmp_path,
base::BindOnce(&FileAnalyzerTest::DoneCallback, base::Unretained(this),
run_loop.QuitClosure()));
run_loop.Run();
ASSERT_TRUE(has_result_);
EXPECT_TRUE(result_.image_headers.has_pe_headers());
EXPECT_EQ(result_.image_headers.pe_headers().file_header(), "image header");
}
#if defined(OS_MACOSX)
TEST_F(FileAnalyzerTest, ExtractsSignatureForDmg) {
scoped_refptr<MockBinaryFeatureExtractor> extractor =
new testing::StrictMock<MockBinaryFeatureExtractor>();
FileAnalyzer analyzer(extractor);
base::RunLoop run_loop;
base::FilePath target_path(FILE_PATH_LITERAL("target.dmg"));
base::FilePath signed_dmg;
EXPECT_TRUE(base::PathService::Get(chrome::DIR_TEST_DATA, &signed_dmg));
signed_dmg = signed_dmg.AppendASCII("safe_browsing")
.AppendASCII("mach_o")
.AppendASCII("signed-archive.dmg");
analyzer.Start(
target_path, signed_dmg,
base::BindOnce(&FileAnalyzerTest::DoneCallback, base::Unretained(this),
run_loop.QuitClosure()));
run_loop.Run();
ASSERT_TRUE(has_result_);
EXPECT_EQ(2215u, result_.disk_image_signature.size());
base::FilePath signed_dmg_signature;
EXPECT_TRUE(
base::PathService::Get(chrome::DIR_TEST_DATA, &signed_dmg_signature));
signed_dmg_signature = signed_dmg_signature.AppendASCII("safe_browsing")
.AppendASCII("mach_o")
.AppendASCII("signed-archive-signature.data");
std::string signature;
base::ReadFileToString(signed_dmg_signature, &signature);
EXPECT_EQ(2215u, signature.length());
std::vector<uint8_t> signature_vector(signature.begin(), signature.end());
EXPECT_EQ(signature_vector, result_.disk_image_signature);
}
TEST_F(FileAnalyzerTest, TypeSniffsDmgWithoutExtension) {
scoped_refptr<MockBinaryFeatureExtractor> extractor =
new testing::StrictMock<MockBinaryFeatureExtractor>();
FileAnalyzer analyzer(extractor);
base::RunLoop run_loop;
base::FilePath target_path(FILE_PATH_LITERAL("target.dmg"));
base::FilePath dmg_no_extension;
EXPECT_TRUE(base::PathService::Get(chrome::DIR_TEST_DATA, &dmg_no_extension));
dmg_no_extension = dmg_no_extension.AppendASCII("safe_browsing")
.AppendASCII("dmg")
.AppendASCII("data")
.AppendASCII("mach_o_in_dmg.txt");
analyzer.Start(
target_path, dmg_no_extension,
base::BindOnce(&FileAnalyzerTest::DoneCallback, base::Unretained(this),
run_loop.QuitClosure()));
run_loop.Run();
ASSERT_TRUE(has_result_);
EXPECT_EQ(result_.type, ClientDownloadRequest::MAC_EXECUTABLE);
EXPECT_EQ(result_.archive_is_valid, FileAnalyzer::ArchiveValid::VALID);
}
#endif
} // namespace safe_browsing
......@@ -100,6 +100,20 @@ if (safe_browsing_mode == 1) {
"//base:base",
]
}
source_set("mock_binary_feature_extractor") {
testonly = true
sources = [
"mock_binary_feature_extractor.cc",
"mock_binary_feature_extractor.h",
]
deps = [
":safe_browsing",
"//testing/gmock",
]
}
}
source_set("safe_browsing") {
......
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/common/safe_browsing/mock_binary_feature_extractor.h"
namespace safe_browsing {
MockBinaryFeatureExtractor::MockBinaryFeatureExtractor() {}
MockBinaryFeatureExtractor::~MockBinaryFeatureExtractor() {}
} // namespace safe_browsing
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef CHROME_COMMON_SAFE_BROWSING_MOCK_BINARY_FEATURE_EXTRACTOR_H_
#define CHROME_COMMON_SAFE_BROWSING_MOCK_BINARY_FEATURE_EXTRACTOR_H_
#include "chrome/common/safe_browsing/binary_feature_extractor.h"
#include "testing/gmock/include/gmock/gmock.h"
namespace safe_browsing {
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:
~MockBinaryFeatureExtractor() override;
private:
DISALLOW_COPY_AND_ASSIGN(MockBinaryFeatureExtractor);
};
} // namespace safe_browsing
#endif // CHROME_COMMON_SAFE_BROWSING_MOCK_BINARY_FEATURE_EXTRACTOR_H_
......@@ -3921,10 +3921,10 @@ test("unit_tests") {
"../browser/safe_browsing/client_side_detection_host_unittest.cc",
"../browser/safe_browsing/client_side_detection_service_unittest.cc",
"../browser/safe_browsing/client_side_model_loader_unittest.cc",
"../browser/safe_browsing/download_protection/check_client_download_request_unittest.cc",
"../browser/safe_browsing/download_protection/download_feedback_service_unittest.cc",
"../browser/safe_browsing/download_protection/download_feedback_unittest.cc",
"../browser/safe_browsing/download_protection/download_protection_service_unittest.cc",
"../browser/safe_browsing/download_protection/file_analyzer_unittest.cc",
"../browser/safe_browsing/download_protection/path_sanitizer_unittest.cc",
"../browser/safe_browsing/download_protection/two_phase_uploader_unittest.cc",
"../browser/safe_browsing/incident_reporting/binary_integrity_analyzer_mac_unittest.cc",
......@@ -3975,6 +3975,7 @@ test("unit_tests") {
]
deps += [
":test_proto",
"../common/safe_browsing:mock_binary_feature_extractor",
"//chrome/services/file_util/public/cpp:unit_tests",
"//components/safe_browsing:ping_manager_unittest",
"//components/safe_browsing/browser:unittests",
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment