blob: 77d82e45c915a26c2c8d54b65f656ff27be107ff [file] [log] [blame]
// Copyright 2016 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 "components/safe_browsing/db/v4_local_database_manager.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/memory/ref_counted.h"
#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
#include "base/test/test_simple_task_runner.h"
#include "base/threading/thread_task_runner_handle.h"
#include "components/safe_browsing/db/notification_types.h"
#include "components/safe_browsing/db/v4_database.h"
#include "components/safe_browsing/db/v4_protocol_manager_util.h"
#include "components/safe_browsing/db/v4_test_util.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "content/public/test/test_utils.h"
#include "crypto/sha2.h"
#include "net/url_request/test_url_fetcher_factory.h"
#include "testing/platform_test.h"
namespace safe_browsing {
namespace {
typedef std::vector<FullHashInfo> FullHashInfos;
// Utility function for populating hashes.
FullHash HashForUrl(const GURL& url) {
std::vector<FullHash> full_hashes;
V4ProtocolManagerUtil::UrlToFullHashes(url, &full_hashes);
// ASSERT_GE(full_hashes.size(), 1u);
return full_hashes[0];
}
// Use this if you want GetFullHashes() to always return prescribed results.
class FakeGetHashProtocolManager : public V4GetHashProtocolManager {
public:
FakeGetHashProtocolManager(
net::URLRequestContextGetter* request_context_getter,
const StoresToCheck& stores_to_check,
const V4ProtocolConfig& config,
const FullHashInfos& full_hash_infos)
: V4GetHashProtocolManager(request_context_getter,
stores_to_check,
config),
full_hash_infos_(full_hash_infos) {}
void GetFullHashes(const FullHashToStoreAndHashPrefixesMap&,
const std::vector<std::string>&,
FullHashCallback callback) override {
// Async, since the real manager might use a fetcher.
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::Bind(callback, full_hash_infos_));
}
private:
FullHashInfos full_hash_infos_;
};
class FakeGetHashProtocolManagerFactory
: public V4GetHashProtocolManagerFactory {
public:
FakeGetHashProtocolManagerFactory(const FullHashInfos& full_hash_infos)
: full_hash_infos_(full_hash_infos) {}
std::unique_ptr<V4GetHashProtocolManager> CreateProtocolManager(
net::URLRequestContextGetter* request_context_getter,
const StoresToCheck& stores_to_check,
const V4ProtocolConfig& config) override {
return std::make_unique<FakeGetHashProtocolManager>(
request_context_getter, stores_to_check, config, full_hash_infos_);
}
private:
FullHashInfos full_hash_infos_;
};
// Use FakeGetHashProtocolManagerFactory in scope, then reset.
// You should make sure the DatabaseManager is created _after_ this.
class ScopedFakeGetHashProtocolManagerFactory {
public:
ScopedFakeGetHashProtocolManagerFactory(
const FullHashInfos& full_hash_infos) {
V4GetHashProtocolManager::RegisterFactory(
std::make_unique<FakeGetHashProtocolManagerFactory>(full_hash_infos));
}
~ScopedFakeGetHashProtocolManagerFactory() {
V4GetHashProtocolManager::RegisterFactory(nullptr);
}
};
} // namespace
class FakeV4Database : public V4Database {
public:
static void Create(
const scoped_refptr<base::SequencedTaskRunner>& db_task_runner,
std::unique_ptr<StoreMap> store_map,
const StoreAndHashPrefixes& store_and_hash_prefixes,
NewDatabaseReadyCallback new_db_callback,
bool stores_available) {
// Mimics V4Database::Create
const scoped_refptr<base::SingleThreadTaskRunner>& callback_task_runner =
base::MessageLoop::current()->task_runner();
db_task_runner->PostTask(
FROM_HERE,
base::Bind(&FakeV4Database::CreateOnTaskRunner, db_task_runner,
base::Passed(&store_map), store_and_hash_prefixes,
callback_task_runner, new_db_callback, stores_available));
}
// V4Database implementation
void GetStoresMatchingFullHash(
const FullHash& full_hash,
const StoresToCheck& stores_to_check,
StoreAndHashPrefixes* store_and_hash_prefixes) override {
store_and_hash_prefixes->clear();
for (const StoreAndHashPrefix& stored_sahp : store_and_hash_prefixes_) {
if (stores_to_check.count(stored_sahp.list_id) == 0)
continue;
const PrefixSize& prefix_size = stored_sahp.hash_prefix.size();
if (!full_hash.compare(0, prefix_size, stored_sahp.hash_prefix)) {
store_and_hash_prefixes->push_back(stored_sahp);
}
}
}
bool AreAllStoresAvailable(
const StoresToCheck& stores_to_check) const override {
return stores_available_;
}
bool AreAnyStoresAvailable(
const StoresToCheck& stores_to_check) const override {
return stores_available_;
}
private:
static void CreateOnTaskRunner(
const scoped_refptr<base::SequencedTaskRunner>& db_task_runner,
std::unique_ptr<StoreMap> store_map,
const StoreAndHashPrefixes& store_and_hash_prefixes,
const scoped_refptr<base::SingleThreadTaskRunner>& callback_task_runner,
NewDatabaseReadyCallback new_db_callback,
bool stores_available) {
// Mimics the semantics of V4Database::CreateOnTaskRunner
std::unique_ptr<FakeV4Database> fake_v4_database(
new FakeV4Database(db_task_runner, std::move(store_map),
store_and_hash_prefixes, stores_available));
callback_task_runner->PostTask(
FROM_HERE,
base::Bind(new_db_callback, base::Passed(&fake_v4_database)));
}
FakeV4Database(const scoped_refptr<base::SequencedTaskRunner>& db_task_runner,
std::unique_ptr<StoreMap> store_map,
const StoreAndHashPrefixes& store_and_hash_prefixes,
bool stores_available)
: V4Database(db_task_runner, std::move(store_map)),
store_and_hash_prefixes_(store_and_hash_prefixes),
stores_available_(stores_available) {}
const StoreAndHashPrefixes store_and_hash_prefixes_;
const bool stores_available_;
};
// TODO(nparker): This might be simpler with a mock and EXPECT calls.
// That would also catch unexpected calls.
class TestClient : public SafeBrowsingDatabaseManager::Client {
public:
TestClient(SBThreatType sb_threat_type,
const GURL& url,
V4LocalDatabaseManager* manager_to_cancel = nullptr)
: expected_sb_threat_type(sb_threat_type),
expected_urls(1, url),
manager_to_cancel_(manager_to_cancel) {}
TestClient(SBThreatType sb_threat_type, const std::vector<GURL>& url_chain)
: expected_sb_threat_type(sb_threat_type), expected_urls(url_chain) {}
void OnCheckBrowseUrlResult(const GURL& url,
SBThreatType threat_type,
const ThreatMetadata& metadata) override {
ASSERT_EQ(expected_urls[0], url);
ASSERT_EQ(expected_sb_threat_type, threat_type);
on_check_browse_url_result_called_ = true;
if (manager_to_cancel_) {
manager_to_cancel_->CancelCheck(this);
}
}
void OnCheckResourceUrlResult(const GURL& url,
SBThreatType threat_type,
const std::string& threat_hash) override {
ASSERT_EQ(expected_urls[0], url);
ASSERT_EQ(expected_sb_threat_type, threat_type);
ASSERT_EQ(threat_type == SB_THREAT_TYPE_SAFE, threat_hash.empty());
on_check_resource_url_result_called_ = true;
}
void OnCheckDownloadUrlResult(const std::vector<GURL>& url_chain,
SBThreatType threat_type) override {
ASSERT_EQ(expected_urls, url_chain);
ASSERT_EQ(expected_sb_threat_type, threat_type);
on_check_download_urls_result_called_ = true;
}
SBThreatType expected_sb_threat_type;
std::vector<GURL> expected_urls;
bool on_check_browse_url_result_called_ = false;
bool on_check_download_urls_result_called_ = false;
bool on_check_resource_url_result_called_ = false;
V4LocalDatabaseManager* manager_to_cancel_;
};
class TestWhitelistClient : public SafeBrowsingDatabaseManager::Client {
public:
explicit TestWhitelistClient(bool whitelist_expected)
: whitelist_expected_(whitelist_expected) {}
void OnCheckWhitelistUrlResult(bool is_whitelisted) override {
EXPECT_EQ(whitelist_expected_, is_whitelisted);
callback_called_ = true;
}
const bool whitelist_expected_;
bool callback_called_ = false;
};
class TestExtensionClient : public SafeBrowsingDatabaseManager::Client {
public:
TestExtensionClient(const std::set<FullHash>& expected_bad_crxs)
: expected_bad_crxs(expected_bad_crxs),
on_check_extensions_result_called_(false) {}
void OnCheckExtensionsResult(const std::set<FullHash>& bad_crxs) override {
EXPECT_EQ(expected_bad_crxs, bad_crxs);
on_check_extensions_result_called_ = true;
}
const std::set<FullHash> expected_bad_crxs;
bool on_check_extensions_result_called_;
};
class FakeV4LocalDatabaseManager : public V4LocalDatabaseManager {
public:
FakeV4LocalDatabaseManager(
const base::FilePath& base_path,
ExtendedReportingLevelCallback extended_reporting_level_callback,
scoped_refptr<base::SequencedTaskRunner> task_runner)
: V4LocalDatabaseManager(base_path,
extended_reporting_level_callback,
task_runner),
perform_full_hash_check_called_(false) {}
// V4LocalDatabaseManager impl:
void PerformFullHashCheck(std::unique_ptr<PendingCheck> check,
const FullHashToStoreAndHashPrefixesMap&
full_hash_to_store_and_hash_prefixes) override {
perform_full_hash_check_called_ = true;
}
static bool PerformFullHashCheckCalled(
scoped_refptr<safe_browsing::V4LocalDatabaseManager>& v4_ldbm) {
FakeV4LocalDatabaseManager* fake =
static_cast<FakeV4LocalDatabaseManager*>(v4_ldbm.get());
return fake->perform_full_hash_check_called_;
}
private:
~FakeV4LocalDatabaseManager() override {}
bool perform_full_hash_check_called_;
};
class V4LocalDatabaseManagerTest : public PlatformTest {
public:
V4LocalDatabaseManagerTest() : task_runner_(new base::TestSimpleTaskRunner) {}
void SetUp() override {
PlatformTest::SetUp();
ASSERT_TRUE(base_dir_.CreateUniqueTempDir());
DVLOG(1) << "base_dir_: " << base_dir_.GetPath().value();
extended_reporting_level_ = SBER_LEVEL_OFF;
erl_callback_ =
base::Bind(&V4LocalDatabaseManagerTest::GetExtendedReportingLevel,
base::Unretained(this));
v4_local_database_manager_ =
base::WrapRefCounted(new V4LocalDatabaseManager(
base_dir_.GetPath(), erl_callback_, task_runner_));
StartLocalDatabaseManager();
}
void TearDown() override {
StopLocalDatabaseManager();
PlatformTest::TearDown();
}
void ForceDisableLocalDatabaseManager() {
v4_local_database_manager_->enabled_ = false;
}
void ForceEnableLocalDatabaseManager() {
v4_local_database_manager_->enabled_ = true;
}
const V4LocalDatabaseManager::QueuedChecks& GetQueuedChecks() {
return v4_local_database_manager_->queued_checks_;
}
ExtendedReportingLevel GetExtendedReportingLevel() {
return extended_reporting_level_;
}
void ReplaceV4Database(const StoreAndHashPrefixes& store_and_hash_prefixes,
bool stores_available = false) {
// Disable the V4LocalDatabaseManager first so that if the callback to
// verify checksum has been scheduled, then it doesn't do anything when it
// is called back.
ForceDisableLocalDatabaseManager();
// Wait to make sure that the callback gets executed if it has already been
// scheduled.
WaitForTasksOnTaskRunner();
// Re-enable the V4LocalDatabaseManager otherwise the checks won't work and
// the fake database won't be set either.
ForceEnableLocalDatabaseManager();
NewDatabaseReadyCallback db_ready_callback =
base::Bind(&V4LocalDatabaseManager::DatabaseReadyForChecks,
base::Unretained(v4_local_database_manager_.get()));
FakeV4Database::Create(task_runner_, std::make_unique<StoreMap>(),
store_and_hash_prefixes, db_ready_callback,
stores_available);
WaitForTasksOnTaskRunner();
}
void ResetLocalDatabaseManager() {
StopLocalDatabaseManager();
v4_local_database_manager_ =
base::WrapRefCounted(new V4LocalDatabaseManager(
base_dir_.GetPath(), erl_callback_, task_runner_));
StartLocalDatabaseManager();
}
void ResetV4Database() {
V4Database::Destroy(std::move(v4_local_database_manager_->v4_database_));
}
void StartLocalDatabaseManager() {
v4_local_database_manager_->StartOnIOThread(nullptr,
GetTestV4ProtocolConfig());
}
void StopLocalDatabaseManager() {
if (v4_local_database_manager_) {
v4_local_database_manager_->StopOnIOThread(true);
}
// Force destruction of the database.
WaitForTasksOnTaskRunner();
}
void WaitForTasksOnTaskRunner() {
// Wait for tasks on the task runner so we're sure that the
// V4LocalDatabaseManager has read the data from disk.
task_runner_->RunPendingTasks();
base::RunLoop().RunUntilIdle();
}
// For those tests that need the fake manager
void SetupFakeManager() {
// StopLocalDatabaseManager before resetting it because that's what
// ~V4LocalDatabaseManager expects.
StopLocalDatabaseManager();
v4_local_database_manager_ =
base::WrapRefCounted(new FakeV4LocalDatabaseManager(
base_dir_.GetPath(), erl_callback_, task_runner_));
StartLocalDatabaseManager();
WaitForTasksOnTaskRunner();
}
const SBThreatTypeSet usual_threat_types_ = CreateSBThreatTypeSet(
{SB_THREAT_TYPE_URL_PHISHING, SB_THREAT_TYPE_URL_MALWARE,
SB_THREAT_TYPE_URL_UNWANTED});
base::ScopedTempDir base_dir_;
ExtendedReportingLevel extended_reporting_level_;
ExtendedReportingLevelCallback erl_callback_;
scoped_refptr<base::TestSimpleTaskRunner> task_runner_;
content::TestBrowserThreadBundle thread_bundle_;
scoped_refptr<V4LocalDatabaseManager> v4_local_database_manager_;
};
TEST_F(V4LocalDatabaseManagerTest, TestGetThreatSource) {
WaitForTasksOnTaskRunner();
EXPECT_EQ(ThreatSource::LOCAL_PVER4,
v4_local_database_manager_->GetThreatSource());
}
TEST_F(V4LocalDatabaseManagerTest, TestIsSupported) {
WaitForTasksOnTaskRunner();
EXPECT_TRUE(v4_local_database_manager_->IsSupported());
}
TEST_F(V4LocalDatabaseManagerTest, TestCanCheckUrl) {
WaitForTasksOnTaskRunner();
EXPECT_TRUE(
v4_local_database_manager_->CanCheckUrl(GURL("http://example.com/a/")));
EXPECT_TRUE(
v4_local_database_manager_->CanCheckUrl(GURL("https://example.com/a/")));
EXPECT_TRUE(
v4_local_database_manager_->CanCheckUrl(GURL("ftp://example.com/a/")));
EXPECT_FALSE(
v4_local_database_manager_->CanCheckUrl(GURL("adp://example.com/a/")));
}
TEST_F(V4LocalDatabaseManagerTest,
TestCheckBrowseUrlWithEmptyStoresReturnsNoMatch) {
WaitForTasksOnTaskRunner();
// Both the stores are empty right now so CheckBrowseUrl should return true.
EXPECT_TRUE(v4_local_database_manager_->CheckBrowseUrl(
GURL("http://example.com/a/"), usual_threat_types_, nullptr));
}
TEST_F(V4LocalDatabaseManagerTest, TestCheckBrowseUrlWithFakeDbReturnsMatch) {
WaitForTasksOnTaskRunner();
net::TestURLFetcherFactory factory;
std::string url_bad_no_scheme("example.com/bad/");
FullHash bad_full_hash(crypto::SHA256HashString(url_bad_no_scheme));
const HashPrefix bad_hash_prefix(bad_full_hash.substr(0, 5));
StoreAndHashPrefixes store_and_hash_prefixes;
store_and_hash_prefixes.emplace_back(GetUrlMalwareId(), bad_hash_prefix);
ReplaceV4Database(store_and_hash_prefixes);
const GURL url_bad("https://" + url_bad_no_scheme);
EXPECT_FALSE(v4_local_database_manager_->CheckBrowseUrl(
url_bad, usual_threat_types_, nullptr));
// Wait for PerformFullHashCheck to complete.
WaitForTasksOnTaskRunner();
}
TEST_F(V4LocalDatabaseManagerTest, TestCheckCsdWhitelistWithPrefixMatch) {
// Setup to receive full-hash misses. We won't make URL requests.
ScopedFakeGetHashProtocolManagerFactory pin(FullHashInfos({}));
ResetLocalDatabaseManager();
WaitForTasksOnTaskRunner();
std::string url_white_no_scheme("example.com/white/");
FullHash white_full_hash(crypto::SHA256HashString(url_white_no_scheme));
const HashPrefix white_hash_prefix(white_full_hash.substr(0, 5));
StoreAndHashPrefixes store_and_hash_prefixes;
store_and_hash_prefixes.emplace_back(GetUrlCsdWhitelistId(),
white_hash_prefix);
ReplaceV4Database(store_and_hash_prefixes, true /* stores_available */);
TestWhitelistClient client(false /* whitelist_expected */);
const GURL url_check("https://" + url_white_no_scheme);
EXPECT_EQ(AsyncMatch::ASYNC, v4_local_database_manager_->CheckCsdWhitelistUrl(
url_check, &client));
EXPECT_FALSE(client.callback_called_);
// Wait for PerformFullHashCheck to complete.
WaitForTasksOnTaskRunner();
EXPECT_TRUE(client.callback_called_);
}
// This is like CsdWhitelistWithPrefixMatch, but we also verify the
// full-hash-match results in an appropriate callback value.
TEST_F(V4LocalDatabaseManagerTest,
TestCheckCsdWhitelistWithPrefixTheFullMatch) {
std::string url_white_no_scheme("example.com/white/");
FullHash white_full_hash(crypto::SHA256HashString(url_white_no_scheme));
// Setup to receive full-hash hit. We won't make URL requests.
FullHashInfos infos(
{{white_full_hash, GetUrlCsdWhitelistId(), base::Time::Now()}});
ScopedFakeGetHashProtocolManagerFactory pin(infos);
ResetLocalDatabaseManager();
WaitForTasksOnTaskRunner();
const HashPrefix white_hash_prefix(white_full_hash.substr(0, 5));
StoreAndHashPrefixes store_and_hash_prefixes;
store_and_hash_prefixes.emplace_back(GetUrlCsdWhitelistId(),
white_hash_prefix);
ReplaceV4Database(store_and_hash_prefixes, true /* stores_available */);
TestWhitelistClient client(true /* whitelist_expected */);
const GURL url_check("https://" + url_white_no_scheme);
EXPECT_EQ(AsyncMatch::ASYNC, v4_local_database_manager_->CheckCsdWhitelistUrl(
url_check, &client));
EXPECT_FALSE(client.callback_called_);
// Wait for PerformFullHashCheck to complete.
WaitForTasksOnTaskRunner();
EXPECT_TRUE(client.callback_called_);
}
TEST_F(V4LocalDatabaseManagerTest, TestCheckCsdWhitelistWithFullMatch) {
// Setup to receive full-hash misses. We won't make URL requests.
ScopedFakeGetHashProtocolManagerFactory pin(FullHashInfos({}));
ResetLocalDatabaseManager();
WaitForTasksOnTaskRunner();
std::string url_white_no_scheme("example.com/white/");
FullHash white_full_hash(crypto::SHA256HashString(url_white_no_scheme));
StoreAndHashPrefixes store_and_hash_prefixes;
store_and_hash_prefixes.emplace_back(GetUrlCsdWhitelistId(), white_full_hash);
ReplaceV4Database(store_and_hash_prefixes, true /* stores_available */);
TestWhitelistClient client(false /* whitelist_expected */);
const GURL url_check("https://" + url_white_no_scheme);
EXPECT_EQ(AsyncMatch::MATCH, v4_local_database_manager_->CheckCsdWhitelistUrl(
url_check, &client));
WaitForTasksOnTaskRunner();
EXPECT_FALSE(client.callback_called_);
}
TEST_F(V4LocalDatabaseManagerTest, TestCheckCsdWhitelistWithNoMatch) {
// Setup to receive full-hash misses. We won't make URL requests.
ScopedFakeGetHashProtocolManagerFactory pin(FullHashInfos({}));
ResetLocalDatabaseManager();
WaitForTasksOnTaskRunner();
// Add a full hash that won't match the URL we check.
std::string url_white_no_scheme("example.com/white/");
FullHash white_full_hash(crypto::SHA256HashString(url_white_no_scheme));
StoreAndHashPrefixes store_and_hash_prefixes;
store_and_hash_prefixes.emplace_back(GetUrlMalwareId(), white_full_hash);
ReplaceV4Database(store_and_hash_prefixes, true /* stores_available */);
TestWhitelistClient client(true /* whitelist_expected */);
const GURL url_check("https://other.com/");
EXPECT_EQ(
AsyncMatch::NO_MATCH,
v4_local_database_manager_->CheckCsdWhitelistUrl(url_check, &client));
WaitForTasksOnTaskRunner();
EXPECT_FALSE(client.callback_called_);
}
// When whitelist is unavailable, all URLS should be whitelisted.
TEST_F(V4LocalDatabaseManagerTest, TestCheckCsdWhitelistUnavailable) {
// Setup to receive full-hash misses. We won't make URL requests.
ScopedFakeGetHashProtocolManagerFactory pin(FullHashInfos({}));
ResetLocalDatabaseManager();
WaitForTasksOnTaskRunner();
StoreAndHashPrefixes store_and_hash_prefixes;
ReplaceV4Database(store_and_hash_prefixes, false /* stores_available */);
TestWhitelistClient client(false /* whitelist_expected */);
const GURL url_check("https://other.com/");
EXPECT_EQ(AsyncMatch::MATCH, v4_local_database_manager_->CheckCsdWhitelistUrl(
url_check, &client));
WaitForTasksOnTaskRunner();
EXPECT_FALSE(client.callback_called_);
}
TEST_F(V4LocalDatabaseManagerTest,
TestCheckBrowseUrlReturnsNoMatchWhenDisabled) {
WaitForTasksOnTaskRunner();
// The same URL returns |false| in the previous test because
// v4_local_database_manager_ is enabled.
ForceDisableLocalDatabaseManager();
EXPECT_TRUE(v4_local_database_manager_->CheckBrowseUrl(
GURL("http://example.com/a/"), usual_threat_types_, nullptr));
}
TEST_F(V4LocalDatabaseManagerTest, TestGetSeverestThreatTypeAndMetadata) {
WaitForTasksOnTaskRunner();
FullHash fh_malware("Malware");
FullHashInfo fhi_malware(fh_malware, GetUrlMalwareId(), base::Time::Now());
fhi_malware.metadata.population_id = "malware_popid";
FullHash fh_api("api");
FullHashInfo fhi_api(fh_api, GetChromeUrlApiId(), base::Time::Now());
fhi_api.metadata.population_id = "api_popid";
FullHash fh_example("example");
std::vector<FullHashInfo> fhis({fhi_malware, fhi_api});
std::vector<FullHash> full_hashes({fh_malware, fh_example, fh_api});
std::vector<SBThreatType> full_hash_threat_types(full_hashes.size(),
SB_THREAT_TYPE_SAFE);
SBThreatType result_threat_type;
ThreatMetadata metadata;
FullHash matching_full_hash;
const std::vector<SBThreatType> expected_full_hash_threat_types(
{SB_THREAT_TYPE_URL_MALWARE, SB_THREAT_TYPE_SAFE,
SB_THREAT_TYPE_API_ABUSE});
v4_local_database_manager_->GetSeverestThreatTypeAndMetadata(
fhis, full_hashes, &full_hash_threat_types, &result_threat_type,
&metadata, &matching_full_hash);
EXPECT_EQ(expected_full_hash_threat_types, full_hash_threat_types);
EXPECT_EQ(SB_THREAT_TYPE_URL_MALWARE, result_threat_type);
EXPECT_EQ("malware_popid", metadata.population_id);
EXPECT_EQ(fh_malware, matching_full_hash);
// Reversing the list has no effect.
std::reverse(std::begin(fhis), std::end(fhis));
full_hash_threat_types.assign(full_hashes.size(), SB_THREAT_TYPE_SAFE);
v4_local_database_manager_->GetSeverestThreatTypeAndMetadata(
fhis, full_hashes, &full_hash_threat_types, &result_threat_type,
&metadata, &matching_full_hash);
EXPECT_EQ(expected_full_hash_threat_types, full_hash_threat_types);
EXPECT_EQ(SB_THREAT_TYPE_URL_MALWARE, result_threat_type);
EXPECT_EQ("malware_popid", metadata.population_id);
EXPECT_EQ(fh_malware, matching_full_hash);
}
TEST_F(V4LocalDatabaseManagerTest, TestChecksAreQueued) {
const GURL url("https://www.example.com/");
TestClient client(SB_THREAT_TYPE_SAFE, url);
EXPECT_TRUE(GetQueuedChecks().empty());
v4_local_database_manager_->CheckBrowseUrl(url, usual_threat_types_, &client);
// The database is unavailable so the check should get queued.
EXPECT_EQ(1ul, GetQueuedChecks().size());
// The following function waits for the DB to load.
WaitForTasksOnTaskRunner();
EXPECT_TRUE(GetQueuedChecks().empty());
ResetV4Database();
v4_local_database_manager_->CheckBrowseUrl(url, usual_threat_types_, &client);
// The database is unavailable so the check should get queued.
EXPECT_EQ(1ul, GetQueuedChecks().size());
StopLocalDatabaseManager();
EXPECT_TRUE(GetQueuedChecks().empty());
}
// Verify that a window where checks cannot be cancelled is closed.
TEST_F(V4LocalDatabaseManagerTest, CancelPending) {
// Setup to receive full-hash misses.
ScopedFakeGetHashProtocolManagerFactory pin(FullHashInfos({}));
// Reset the database manager so it picks up the replacement protocol manager.
ResetLocalDatabaseManager();
WaitForTasksOnTaskRunner();
// Put a match in the db that will cause a protocol-manager request.
std::string url_bad_no_scheme("example.com/bad/");
FullHash bad_full_hash(crypto::SHA256HashString(url_bad_no_scheme));
const HashPrefix bad_hash_prefix(bad_full_hash.substr(0, 5));
StoreAndHashPrefixes store_and_hash_prefixes;
store_and_hash_prefixes.emplace_back(GetUrlMalwareId(), bad_hash_prefix);
ReplaceV4Database(store_and_hash_prefixes);
const GURL url_bad("https://" + url_bad_no_scheme);
// Test that a request flows through to the callback.
{
TestClient client(SB_THREAT_TYPE_SAFE, url_bad);
EXPECT_FALSE(v4_local_database_manager_->CheckBrowseUrl(
url_bad, usual_threat_types_, &client));
EXPECT_FALSE(client.on_check_browse_url_result_called_);
WaitForTasksOnTaskRunner();
EXPECT_TRUE(client.on_check_browse_url_result_called_);
}
// Test that cancel prevents the callback from being called.
{
TestClient client(SB_THREAT_TYPE_SAFE, url_bad);
EXPECT_FALSE(v4_local_database_manager_->CheckBrowseUrl(
url_bad, usual_threat_types_, &client));
v4_local_database_manager_->CancelCheck(&client);
EXPECT_FALSE(client.on_check_browse_url_result_called_);
WaitForTasksOnTaskRunner();
EXPECT_FALSE(client.on_check_browse_url_result_called_);
}
}
// When the database load flushes the queued requests, make sure that
// CancelCheck() is not fatal in the client callback.
TEST_F(V4LocalDatabaseManagerTest, CancelQueued) {
const GURL url("http://example.com/a/");
TestClient client1(SB_THREAT_TYPE_SAFE, url,
v4_local_database_manager_.get());
TestClient client2(SB_THREAT_TYPE_SAFE, url);
EXPECT_FALSE(v4_local_database_manager_->CheckBrowseUrl(
url, usual_threat_types_, &client1));
EXPECT_FALSE(v4_local_database_manager_->CheckBrowseUrl(
url, usual_threat_types_, &client2));
EXPECT_EQ(2ul, GetQueuedChecks().size());
EXPECT_FALSE(client1.on_check_browse_url_result_called_);
EXPECT_FALSE(client2.on_check_browse_url_result_called_);
WaitForTasksOnTaskRunner();
EXPECT_TRUE(client1.on_check_browse_url_result_called_);
EXPECT_TRUE(client2.on_check_browse_url_result_called_);
}
// This test is somewhat similar to TestCheckBrowseUrlWithFakeDbReturnsMatch but
// it uses a fake V4LocalDatabaseManager to assert that PerformFullHashCheck is
// called async.
TEST_F(V4LocalDatabaseManagerTest, PerformFullHashCheckCalledAsync) {
SetupFakeManager();
net::TestURLFetcherFactory factory;
std::string url_bad_no_scheme("example.com/bad/");
FullHash bad_full_hash(crypto::SHA256HashString(url_bad_no_scheme));
const HashPrefix bad_hash_prefix(bad_full_hash.substr(0, 5));
StoreAndHashPrefixes store_and_hash_prefixes;
store_and_hash_prefixes.emplace_back(GetUrlMalwareId(), bad_hash_prefix);
ReplaceV4Database(store_and_hash_prefixes);
const GURL url_bad("https://" + url_bad_no_scheme);
// The fake database returns a matched hash prefix.
EXPECT_FALSE(v4_local_database_manager_->CheckBrowseUrl(
url_bad, usual_threat_types_, nullptr));
EXPECT_FALSE(FakeV4LocalDatabaseManager::PerformFullHashCheckCalled(
v4_local_database_manager_));
// Wait for PerformFullHashCheck to complete.
WaitForTasksOnTaskRunner();
EXPECT_TRUE(FakeV4LocalDatabaseManager::PerformFullHashCheckCalled(
v4_local_database_manager_));
}
TEST_F(V4LocalDatabaseManagerTest, UsingWeakPtrDropsCallback) {
SetupFakeManager();
net::TestURLFetcherFactory factory;
std::string url_bad_no_scheme("example.com/bad/");
FullHash bad_full_hash(crypto::SHA256HashString(url_bad_no_scheme));
const HashPrefix bad_hash_prefix(bad_full_hash.substr(0, 5));
StoreAndHashPrefixes store_and_hash_prefixes;
store_and_hash_prefixes.emplace_back(GetUrlMalwareId(), bad_hash_prefix);
ReplaceV4Database(store_and_hash_prefixes);
const GURL url_bad("https://" + url_bad_no_scheme);
EXPECT_FALSE(v4_local_database_manager_->CheckBrowseUrl(
url_bad, usual_threat_types_, nullptr));
v4_local_database_manager_->StopOnIOThread(true);
// Release the V4LocalDatabaseManager object right away before the callback
// gets called. When the callback gets called, without using a weak-ptr
// factory, this leads to a use after free. However, using the weak-ptr means
// that the callback is simply dropped.
v4_local_database_manager_ = nullptr;
// Wait for the tasks scheduled by StopOnIOThread to complete.
WaitForTasksOnTaskRunner();
}
TEST_F(V4LocalDatabaseManagerTest, TestMatchDownloadWhitelistString) {
SetupFakeManager();
const std::string good_cert = "Good Cert";
const std::string other_cert = "Other Cert";
FullHash good_hash(crypto::SHA256HashString(good_cert));
StoreAndHashPrefixes store_and_hash_prefixes;
store_and_hash_prefixes.emplace_back(GetCertCsdDownloadWhitelistId(),
good_hash);
ReplaceV4Database(store_and_hash_prefixes, false /* not available */);
// Verify it defaults to false when DB is not available.
EXPECT_FALSE(
v4_local_database_manager_->MatchDownloadWhitelistString(good_cert));
ReplaceV4Database(store_and_hash_prefixes, true /* available */);
// Not whitelisted.
EXPECT_FALSE(
v4_local_database_manager_->MatchDownloadWhitelistString(other_cert));
// Whitelisted.
EXPECT_TRUE(
v4_local_database_manager_->MatchDownloadWhitelistString(good_cert));
EXPECT_FALSE(FakeV4LocalDatabaseManager::PerformFullHashCheckCalled(
v4_local_database_manager_));
}
TEST_F(V4LocalDatabaseManagerTest, TestMatchDownloadWhitelistUrl) {
SetupFakeManager();
GURL good_url("http://safe.com");
GURL other_url("http://iffy.com");
StoreAndHashPrefixes store_and_hash_prefixes;
store_and_hash_prefixes.emplace_back(GetUrlCsdDownloadWhitelistId(),
HashForUrl(good_url));
ReplaceV4Database(store_and_hash_prefixes, false /* not available */);
// Verify it defaults to false when DB is not available.
EXPECT_FALSE(v4_local_database_manager_->MatchDownloadWhitelistUrl(good_url));
ReplaceV4Database(store_and_hash_prefixes, true /* available */);
// Not whitelisted.
EXPECT_FALSE(
v4_local_database_manager_->MatchDownloadWhitelistUrl(other_url));
// Whitelisted.
EXPECT_TRUE(v4_local_database_manager_->MatchDownloadWhitelistUrl(good_url));
EXPECT_FALSE(FakeV4LocalDatabaseManager::PerformFullHashCheckCalled(
v4_local_database_manager_));
}
TEST_F(V4LocalDatabaseManagerTest, TestMatchMalwareIP) {
SetupFakeManager();
// >>> hashlib.sha1(socket.inet_pton(socket.AF_INET6,
// '::ffff:192.168.1.2')).digest() + chr(128)
// '\xb3\xe0z\xafAv#h\x9a\xcf<\xf3ee\x94\xda\xf6y\xb1\xad\x80'
StoreAndHashPrefixes store_and_hash_prefixes;
store_and_hash_prefixes.emplace_back(GetIpMalwareId(),
FullHash("\xB3\xE0z\xAF"
"Av#h\x9A\xCF<\xF3"
"ee\x94\xDA\xF6y\xB1\xAD\x80"));
ReplaceV4Database(store_and_hash_prefixes);
EXPECT_FALSE(v4_local_database_manager_->MatchMalwareIP(""));
// Not blacklisted.
EXPECT_FALSE(v4_local_database_manager_->MatchMalwareIP("192.168.1.1"));
// Blacklisted.
EXPECT_TRUE(v4_local_database_manager_->MatchMalwareIP("192.168.1.2"));
EXPECT_FALSE(FakeV4LocalDatabaseManager::PerformFullHashCheckCalled(
v4_local_database_manager_));
}
// This verifies the fix for race in http://crbug.com/660293
TEST_F(V4LocalDatabaseManagerTest, TestCheckBrowseUrlWithSameClientAndCancel) {
ScopedFakeGetHashProtocolManagerFactory pin(FullHashInfos({}));
// Reset the database manager so it picks up the replacement protocol manager.
ResetLocalDatabaseManager();
WaitForTasksOnTaskRunner();
StoreAndHashPrefixes store_and_hash_prefixes;
store_and_hash_prefixes.emplace_back(GetUrlMalwareId(),
HashPrefix("sن\340\t\006_"));
ReplaceV4Database(store_and_hash_prefixes);
GURL first_url("http://example.com/a");
GURL second_url("http://example.com/");
TestClient client(SB_THREAT_TYPE_SAFE, first_url);
// The fake database returns a matched hash prefix.
EXPECT_FALSE(v4_local_database_manager_->CheckBrowseUrl(
first_url, usual_threat_types_, &client));
// That check gets queued. Now, let's cancel the check. After this, we should
// not receive a call for |OnCheckBrowseUrlResult| with |first_url|.
v4_local_database_manager_->CancelCheck(&client);
// Now, re-use that client but for |second_url|.
client.expected_urls.assign(1, second_url);
EXPECT_FALSE(v4_local_database_manager_->CheckBrowseUrl(
second_url, usual_threat_types_, &client));
// Wait for PerformFullHashCheck to complete.
WaitForTasksOnTaskRunner();
// |on_check_browse_url_result_called_| is true only if OnCheckBrowseUrlResult
// gets called with the |url| equal to |expected_url|, which is |second_url|
// in
// this test.
EXPECT_TRUE(client.on_check_browse_url_result_called_);
}
TEST_F(V4LocalDatabaseManagerTest, TestCheckResourceUrl) {
// Setup to receive full-hash misses.
ScopedFakeGetHashProtocolManagerFactory pin(FullHashInfos({}));
// Reset the database manager so it picks up the replacement protocol manager.
ResetLocalDatabaseManager();
WaitForTasksOnTaskRunner();
std::string url_bad_no_scheme("example.com/bad/");
FullHash bad_full_hash(crypto::SHA256HashString(url_bad_no_scheme));
const HashPrefix bad_hash_prefix(bad_full_hash.substr(0, 5));
StoreAndHashPrefixes store_and_hash_prefixes;
store_and_hash_prefixes.emplace_back(GetChromeUrlClientIncidentId(),
bad_hash_prefix);
ReplaceV4Database(store_and_hash_prefixes, true /* stores_available */);
const GURL url_bad("https://" + url_bad_no_scheme);
TestClient client(SB_THREAT_TYPE_SAFE, url_bad);
EXPECT_FALSE(v4_local_database_manager_->CheckResourceUrl(url_bad, &client));
EXPECT_FALSE(client.on_check_resource_url_result_called_);
WaitForTasksOnTaskRunner();
EXPECT_TRUE(client.on_check_resource_url_result_called_);
}
TEST_F(V4LocalDatabaseManagerTest, TestSubresourceFilterCallback) {
// Setup to receive full-hash misses.
ScopedFakeGetHashProtocolManagerFactory pin(FullHashInfos({}));
// Reset the database manager so it picks up the replacement protocol manager.
ResetLocalDatabaseManager();
WaitForTasksOnTaskRunner();
std::string url_bad_no_scheme("example.com/bad/");
FullHash bad_full_hash(crypto::SHA256HashString(url_bad_no_scheme));
const HashPrefix bad_hash_prefix(bad_full_hash.substr(0, 5));
// Put a match in the db that will cause a protocol-manager request.
StoreAndHashPrefixes store_and_hash_prefixes;
store_and_hash_prefixes.emplace_back(GetUrlSubresourceFilterId(),
bad_hash_prefix);
ReplaceV4Database(store_and_hash_prefixes, true /* stores_available */);
const GURL url_bad("https://" + url_bad_no_scheme);
// Test that a request flows through to the callback.
{
TestClient client(SB_THREAT_TYPE_SAFE, url_bad);
EXPECT_FALSE(v4_local_database_manager_->CheckUrlForSubresourceFilter(
url_bad, &client));
EXPECT_FALSE(client.on_check_browse_url_result_called_);
WaitForTasksOnTaskRunner();
EXPECT_TRUE(client.on_check_browse_url_result_called_);
}
}
TEST_F(V4LocalDatabaseManagerTest, TestCheckResourceUrlReturnsBad) {
// Setup to receive full-hash hit.
std::string url_bad_no_scheme("example.com/bad/");
FullHash bad_full_hash(crypto::SHA256HashString(url_bad_no_scheme));
FullHashInfo fhi(bad_full_hash, GetChromeUrlClientIncidentId(), base::Time());
ScopedFakeGetHashProtocolManagerFactory pin(FullHashInfos({fhi}));
// Reset the database manager so it picks up the replacement protocol manager.
ResetLocalDatabaseManager();
WaitForTasksOnTaskRunner();
// Put a match in the db that will cause a protocol-manager request.
const HashPrefix bad_hash_prefix(bad_full_hash.substr(0, 5));
StoreAndHashPrefixes store_and_hash_prefixes;
store_and_hash_prefixes.emplace_back(GetChromeUrlClientIncidentId(),
bad_hash_prefix);
ReplaceV4Database(store_and_hash_prefixes, true /* stores_available */);
const GURL url_bad("https://" + url_bad_no_scheme);
TestClient client(SB_THREAT_TYPE_BLACKLISTED_RESOURCE, url_bad);
EXPECT_FALSE(v4_local_database_manager_->CheckResourceUrl(url_bad, &client));
EXPECT_FALSE(client.on_check_resource_url_result_called_);
WaitForTasksOnTaskRunner();
EXPECT_TRUE(client.on_check_resource_url_result_called_);
}
TEST_F(V4LocalDatabaseManagerTest, TestCheckExtensionIDsNothingBlacklisted) {
// Setup to receive full-hash misses.
ScopedFakeGetHashProtocolManagerFactory pin(FullHashInfos({}));
// Reset the database manager so it picks up the replacement protocol manager.
ResetLocalDatabaseManager();
WaitForTasksOnTaskRunner();
// bad_extension_id is in the local DB but the full hash won't match.
const FullHash bad_extension_id("aaaabbbbccccdddd"),
good_extension_id("ddddccccbbbbaaaa");
// Put a match in the db that will cause a protocol-manager request.
StoreAndHashPrefixes store_and_hash_prefixes;
store_and_hash_prefixes.emplace_back(GetChromeExtMalwareId(),
bad_extension_id);
ReplaceV4Database(store_and_hash_prefixes, true /* stores_available */);
const std::set<FullHash> expected_bad_crxs({});
const std::set<FullHash> extension_ids({good_extension_id, bad_extension_id});
TestExtensionClient client(expected_bad_crxs);
EXPECT_FALSE(
v4_local_database_manager_->CheckExtensionIDs(extension_ids, &client));
EXPECT_FALSE(client.on_check_extensions_result_called_);
WaitForTasksOnTaskRunner();
EXPECT_TRUE(client.on_check_extensions_result_called_);
}
TEST_F(V4LocalDatabaseManagerTest, TestCheckExtensionIDsOneIsBlacklisted) {
// bad_extension_id is in the local DB and the full hash will match.
const FullHash bad_extension_id("aaaabbbbccccdddd"),
good_extension_id("ddddccccbbbbaaaa");
FullHashInfo fhi(bad_extension_id, GetChromeExtMalwareId(), base::Time());
// Setup to receive full-hash hit.
ScopedFakeGetHashProtocolManagerFactory pin(FullHashInfos({fhi}));
// Reset the database manager so it picks up the replacement protocol manager.
ResetLocalDatabaseManager();
WaitForTasksOnTaskRunner();
// Put a match in the db that will cause a protocol-manager request.
StoreAndHashPrefixes store_and_hash_prefixes;
store_and_hash_prefixes.emplace_back(GetChromeExtMalwareId(),
bad_extension_id);
ReplaceV4Database(store_and_hash_prefixes, true /* stores_available */);
const std::set<FullHash> expected_bad_crxs({bad_extension_id});
const std::set<FullHash> extension_ids({good_extension_id, bad_extension_id});
TestExtensionClient client(expected_bad_crxs);
EXPECT_FALSE(
v4_local_database_manager_->CheckExtensionIDs(extension_ids, &client));
EXPECT_FALSE(client.on_check_extensions_result_called_);
WaitForTasksOnTaskRunner();
EXPECT_TRUE(client.on_check_extensions_result_called_);
}
TEST_F(V4LocalDatabaseManagerTest, TestCheckDownloadUrlNothingBlacklisted) {
// Setup to receive full-hash misses.
ScopedFakeGetHashProtocolManagerFactory pin(FullHashInfos({}));
// Reset the database manager so it picks up the replacement protocol manager.
ResetLocalDatabaseManager();
WaitForTasksOnTaskRunner();
// Put a match in the db that will cause a protocol-manager request.
std::string url_bad_no_scheme("example.com/bad/");
FullHash bad_full_hash(crypto::SHA256HashString(url_bad_no_scheme));
const HashPrefix bad_hash_prefix(bad_full_hash.substr(0, 5));
StoreAndHashPrefixes store_and_hash_prefixes;
store_and_hash_prefixes.emplace_back(GetUrlMalBinId(), bad_hash_prefix);
ReplaceV4Database(store_and_hash_prefixes, true /* stores_available */);
const GURL url_bad("https://" + url_bad_no_scheme),
url_good("https://example.com/good/");
const std::vector<GURL> url_chain({url_good, url_bad});
TestClient client(SB_THREAT_TYPE_SAFE, url_chain);
EXPECT_FALSE(
v4_local_database_manager_->CheckDownloadUrl(url_chain, &client));
EXPECT_FALSE(client.on_check_download_urls_result_called_);
WaitForTasksOnTaskRunner();
EXPECT_TRUE(client.on_check_download_urls_result_called_);
}
TEST_F(V4LocalDatabaseManagerTest, TestCheckDownloadUrlWithOneBlacklisted) {
// Setup to receive full-hash hit.
std::string url_bad_no_scheme("example.com/bad/");
FullHash bad_full_hash(crypto::SHA256HashString(url_bad_no_scheme));
FullHashInfo fhi(bad_full_hash, GetUrlMalBinId(), base::Time());
ScopedFakeGetHashProtocolManagerFactory pin(FullHashInfos({fhi}));
// Reset the database manager so it picks up the replacement protocol manager.
ResetLocalDatabaseManager();
WaitForTasksOnTaskRunner();
const GURL url_bad("https://" + url_bad_no_scheme),
url_good("https://example.com/good/");
const std::vector<GURL> url_chain({url_good, url_bad});
// Put a match in the db that will cause a protocol-manager request.
const HashPrefix bad_hash_prefix(bad_full_hash.substr(0, 5));
StoreAndHashPrefixes store_and_hash_prefixes;
store_and_hash_prefixes.emplace_back(GetUrlMalBinId(), bad_hash_prefix);
ReplaceV4Database(store_and_hash_prefixes, true /* stores_available */);
TestClient client(SB_THREAT_TYPE_URL_BINARY_MALWARE, url_chain);
EXPECT_FALSE(
v4_local_database_manager_->CheckDownloadUrl(url_chain, &client));
EXPECT_FALSE(client.on_check_download_urls_result_called_);
WaitForTasksOnTaskRunner();
EXPECT_TRUE(client.on_check_download_urls_result_called_);
}
TEST_F(V4LocalDatabaseManagerTest, DeleteUnusedStoreFileDoesNotExist) {
auto store_file_path = base_dir_.GetPath().AppendASCII("AnyIpMalware.store");
ASSERT_FALSE(base::PathExists(store_file_path));
// Reset the database manager so that DeleteUnusedStoreFiles is called.
ResetLocalDatabaseManager();
WaitForTasksOnTaskRunner();
ASSERT_FALSE(base::PathExists(store_file_path));
}
TEST_F(V4LocalDatabaseManagerTest, DeleteUnusedStoreFileSuccess) {
auto store_file_path = base_dir_.GetPath().AppendASCII("AnyIpMalware.store");
ASSERT_FALSE(base::PathExists(store_file_path));
// Now write an empty file.
base::WriteFile(store_file_path, "", 0);
ASSERT_TRUE(base::PathExists(store_file_path));
// Reset the database manager so that DeleteUnusedStoreFiles is called.
ResetLocalDatabaseManager();
WaitForTasksOnTaskRunner();
ASSERT_FALSE(base::PathExists(store_file_path));
}
TEST_F(V4LocalDatabaseManagerTest, DeleteUnusedStoreFileRandomFileNotDeleted) {
auto random_store_file_path = base_dir_.GetPath().AppendASCII("random.store");
ASSERT_FALSE(base::PathExists(random_store_file_path));
// Now write an empty file.
base::WriteFile(random_store_file_path, "", 0);
ASSERT_TRUE(base::PathExists(random_store_file_path));
// Reset the database manager so that DeleteUnusedStoreFiles is called.
ResetLocalDatabaseManager();
WaitForTasksOnTaskRunner();
ASSERT_TRUE(base::PathExists(random_store_file_path));
// Cleanup
base::DeleteFile(random_store_file_path, false /* recursive */);
}
TEST_F(V4LocalDatabaseManagerTest, NotificationOnUpdate) {
content::WindowedNotificationObserver observer(
NOTIFICATION_SAFE_BROWSING_UPDATE_COMPLETE,
content::Source<SafeBrowsingDatabaseManager>(
v4_local_database_manager_.get()));
// Creates and associates a V4Database instance.
StoreAndHashPrefixes store_and_hash_prefixes;
ReplaceV4Database(store_and_hash_prefixes);
v4_local_database_manager_->DatabaseUpdated();
// This observer waits until it receives the notification.
observer.Wait();
}
} // namespace safe_browsing