blob: b6659f4b1decfd8d2b8f4cebcf515536894f1ceb [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 "base/files/scoped_temp_dir.h"
#include "base/memory/ptr_util.h"
#include "base/memory/ref_counted.h"
#include "base/run_loop.h"
#include "base/test/test_simple_task_runner.h"
#include "components/safe_browsing_db/v4_database.h"
#include "components/safe_browsing_db/v4_local_database_manager.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "net/url_request/test_url_fetcher_factory.h"
#include "testing/platform_test.h"
namespace safe_browsing {
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) {
// 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));
}
void GetStoresMatchingFullHash(
const FullHash& full_hash,
const StoresToCheck& stores_to_check,
StoreAndHashPrefixes* store_and_hash_prefixes) override {
*store_and_hash_prefixes = store_and_hash_prefixes_;
}
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) {
// 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));
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)
: V4Database(db_task_runner, std::move(store_map)),
store_and_hash_prefixes_(store_and_hash_prefixes) {}
const StoreAndHashPrefixes store_and_hash_prefixes_;
};
class TestClient : public SafeBrowsingDatabaseManager::Client {
public:
TestClient(SBThreatType sb_threat_type, const GURL& url)
: expected_sb_threat_type(sb_threat_type), expected_url(url) {}
void OnCheckBrowseUrlResult(const GURL& url,
SBThreatType threat_type,
const ThreatMetadata& metadata) override {
DCHECK_EQ(expected_url, url);
DCHECK_EQ(expected_sb_threat_type, threat_type);
}
SBThreatType expected_sb_threat_type;
GURL expected_url;
};
class FakeV4LocalDatabaseManager : public V4LocalDatabaseManager {
public:
void PerformFullHashCheck(std::unique_ptr<PendingCheck> check,
const FullHashToStoreAndHashPrefixesMap&
full_hash_to_store_and_hash_prefixes) override {
perform_full_hash_check_called_ = true;
}
FakeV4LocalDatabaseManager(const base::FilePath& base_path)
: V4LocalDatabaseManager(base_path),
perform_full_hash_check_called_(false) {}
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();
v4_local_database_manager_ =
make_scoped_refptr(new V4LocalDatabaseManager(base_dir_.GetPath()));
SetTaskRunnerForTest();
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_;
}
void ReplaceV4Database(const StoreAndHashPrefixes& store_and_hash_prefixes) {
// 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_, base::MakeUnique<StoreMap>(),
store_and_hash_prefixes, db_ready_callback);
WaitForTasksOnTaskRunner();
}
void ResetV4Database() {
V4Database::Destroy(std::move(v4_local_database_manager_->v4_database_));
}
void SetTaskRunnerForTest() {
v4_local_database_manager_->SetTaskRunnerForTest(task_runner_);
}
void StartLocalDatabaseManager() {
v4_local_database_manager_->StartOnIOThread(NULL, V4ProtocolConfig());
}
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();
}
base::ScopedTempDir base_dir_;
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/"), nullptr));
}
TEST_F(V4LocalDatabaseManagerTest, TestCheckBrowseUrlWithFakeDbReturnsMatch) {
WaitForTasksOnTaskRunner();
net::TestURLFetcherFactory factory;
StoreAndHashPrefixes store_and_hash_prefixes;
store_and_hash_prefixes.emplace_back(GetUrlMalwareId(), HashPrefix("aaaa"));
ReplaceV4Database(store_and_hash_prefixes);
// The fake database returns a matched hash prefix.
EXPECT_FALSE(v4_local_database_manager_->CheckBrowseUrl(
GURL("http://example.com/a/"), nullptr));
// Wait for PerformFullHashCheck to complete.
WaitForTasksOnTaskRunner();
}
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/"), nullptr));
}
TEST_F(V4LocalDatabaseManagerTest, TestGetSeverestThreatTypeAndMetadata) {
WaitForTasksOnTaskRunner();
FullHashInfo fhi_malware(FullHash("Malware"), GetUrlMalwareId(),
base::Time::Now());
fhi_malware.metadata.population_id = "malware_popid";
FullHashInfo fhi_api(FullHash("api"), GetChromeUrlApiId(), base::Time::Now());
fhi_api.metadata.population_id = "api_popid";
std::vector<FullHashInfo> fhis({fhi_malware, fhi_api});
SBThreatType result_threat_type;
ThreatMetadata metadata;
v4_local_database_manager_->GetSeverestThreatTypeAndMetadata(
&result_threat_type, &metadata, fhis);
EXPECT_EQ(SB_THREAT_TYPE_URL_MALWARE, result_threat_type);
EXPECT_EQ("malware_popid", metadata.population_id);
// Reversing the list has no effect.
std::reverse(std::begin(fhis), std::end(fhis));
v4_local_database_manager_->GetSeverestThreatTypeAndMetadata(
&result_threat_type, &metadata, fhis);
EXPECT_EQ(SB_THREAT_TYPE_URL_MALWARE, result_threat_type);
EXPECT_EQ("malware_popid", metadata.population_id);
}
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, &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, &client);
// The database is unavailable so the check should get queued.
EXPECT_EQ(1ul, GetQueuedChecks().size());
StopLocalDatabaseManager();
EXPECT_TRUE(GetQueuedChecks().empty());
}
// This test is somewhat similar to TestCheckBrowseUrlWithFakeDbReturnsMatch but
// it uses a fake V4LocalDatabaseManager to assert that PerformFullHashCheck is
// called async.
TEST_F(V4LocalDatabaseManagerTest, PerformFullHashCheckCalledAsync) {
// StopLocalDatabaseManager before resetting it because that's what
// ~V4LocalDatabaseManager expects.
StopLocalDatabaseManager();
v4_local_database_manager_ =
make_scoped_refptr(new FakeV4LocalDatabaseManager(base_dir_.GetPath()));
SetTaskRunnerForTest();
StartLocalDatabaseManager();
WaitForTasksOnTaskRunner();
net::TestURLFetcherFactory factory;
StoreAndHashPrefixes store_and_hash_prefixes;
store_and_hash_prefixes.emplace_back(GetUrlMalwareId(), HashPrefix("aaaa"));
ReplaceV4Database(store_and_hash_prefixes);
// The fake database returns a matched hash prefix.
EXPECT_FALSE(v4_local_database_manager_->CheckBrowseUrl(
GURL("http://example.com/a/"), 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) {
// StopLocalDatabaseManager before resetting it because that's what
// ~V4LocalDatabaseManager expects.
StopLocalDatabaseManager();
v4_local_database_manager_ =
make_scoped_refptr(new FakeV4LocalDatabaseManager(base_dir_.GetPath()));
SetTaskRunnerForTest();
StartLocalDatabaseManager();
WaitForTasksOnTaskRunner();
net::TestURLFetcherFactory factory;
StoreAndHashPrefixes store_and_hash_prefixes;
store_and_hash_prefixes.emplace_back(GetUrlMalwareId(), HashPrefix("aaaa"));
ReplaceV4Database(store_and_hash_prefixes);
// The fake database returns a matched hash prefix.
EXPECT_FALSE(v4_local_database_manager_->CheckBrowseUrl(
GURL("http://example.com/a/"), 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();
}
} // namespace safe_browsing