1 // Copyright 2016 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "components/safe_browsing/core/db/v4_database.h"
6 
7 #include <memory>
8 #include <utility>
9 
10 #include "base/bind.h"
11 #include "base/callback.h"
12 #include "base/files/file_util.h"
13 #include "base/lazy_instance.h"
14 #include "base/memory/ptr_util.h"
15 #include "base/metrics/histogram_macros.h"
16 #include "base/task_runner_util.h"
17 #include "base/threading/thread_task_runner_handle.h"
18 #include "components/safe_browsing/core/common/thread_utils.h"
19 #include "components/safe_browsing/core/proto/webui.pb.h"
20 
21 using base::TimeTicks;
22 
23 namespace safe_browsing {
24 
25 namespace {
26 
27 const char kV4DatabaseSizeMetric[] = "SafeBrowsing.V4Database.Size";
28 
29 // The factory that controls the creation of the V4Database object.
30 base::LazyInstance<std::unique_ptr<V4DatabaseFactory>>::Leaky g_db_factory =
31     LAZY_INSTANCE_INITIALIZER;
32 
33 // The factory that controls the creation of V4Store objects.
34 base::LazyInstance<std::unique_ptr<V4StoreFactory>>::Leaky g_store_factory =
35     LAZY_INSTANCE_INITIALIZER;
36 
37 // Verifies the checksums on a collection of stores.
38 // Returns the IDs of stores whose checksums failed to verify.
VerifyChecksums(std::vector<std::pair<ListIdentifier,V4Store * >> stores)39 std::vector<ListIdentifier> VerifyChecksums(
40     std::vector<std::pair<ListIdentifier, V4Store*>> stores) {
41   std::vector<ListIdentifier> stores_to_reset;
42   for (const auto& store_map_iter : stores) {
43     if (!store_map_iter.second->VerifyChecksum()) {
44       stores_to_reset.push_back(store_map_iter.first);
45     }
46   }
47   return stores_to_reset;
48 }
49 
50 }  // namespace
51 
Create(const scoped_refptr<base::SequencedTaskRunner> & db_task_runner,std::unique_ptr<StoreMap> store_map)52 std::unique_ptr<V4Database> V4DatabaseFactory::Create(
53     const scoped_refptr<base::SequencedTaskRunner>& db_task_runner,
54     std::unique_ptr<StoreMap> store_map) {
55   // Not using MakeUnique since the constructor of V4Database is protected.
56   return base::WrapUnique(new V4Database(db_task_runner, std::move(store_map)));
57 }
58 
59 // static
Create(const scoped_refptr<base::SequencedTaskRunner> & db_task_runner,const base::FilePath & base_path,const ListInfos & list_infos,NewDatabaseReadyCallback new_db_callback)60 void V4Database::Create(
61     const scoped_refptr<base::SequencedTaskRunner>& db_task_runner,
62     const base::FilePath& base_path,
63     const ListInfos& list_infos,
64     NewDatabaseReadyCallback new_db_callback) {
65   DCHECK(base_path.IsAbsolute());
66   DCHECK(!list_infos.empty());
67 
68   const scoped_refptr<base::SingleThreadTaskRunner> callback_task_runner =
69       base::ThreadTaskRunnerHandle::Get();
70   db_task_runner->PostTask(
71       FROM_HERE, base::BindOnce(&V4Database::CreateOnTaskRunner, db_task_runner,
72                                 base_path, list_infos, callback_task_runner,
73                                 std::move(new_db_callback)));
74 }
75 
76 // static
CreateOnTaskRunner(const scoped_refptr<base::SequencedTaskRunner> & db_task_runner,const base::FilePath & base_path,const ListInfos & list_infos,const scoped_refptr<base::SingleThreadTaskRunner> & callback_task_runner,NewDatabaseReadyCallback new_db_callback)77 void V4Database::CreateOnTaskRunner(
78     const scoped_refptr<base::SequencedTaskRunner>& db_task_runner,
79     const base::FilePath& base_path,
80     const ListInfos& list_infos,
81     const scoped_refptr<base::SingleThreadTaskRunner>& callback_task_runner,
82     NewDatabaseReadyCallback new_db_callback) {
83   DCHECK(db_task_runner->RunsTasksInCurrentSequence());
84 
85   if (!g_store_factory.Get())
86     g_store_factory.Get() = std::make_unique<V4StoreFactory>();
87 
88   if (!base::CreateDirectory(base_path))
89     NOTREACHED();
90 
91   std::unique_ptr<StoreMap> store_map = std::make_unique<StoreMap>();
92   for (const auto& it : list_infos) {
93     if (!it.fetch_updates()) {
94       // This list doesn't need to be fetched or stored on disk.
95       continue;
96     }
97 
98     const base::FilePath store_path = base_path.AppendASCII(it.filename());
99     (*store_map)[it.list_id()] =
100         g_store_factory.Get()->CreateV4Store(db_task_runner, store_path);
101   }
102 
103   if (!g_db_factory.Get())
104     g_db_factory.Get() = std::make_unique<V4DatabaseFactory>();
105 
106   std::unique_ptr<V4Database> v4_database =
107       g_db_factory.Get()->Create(db_task_runner, std::move(store_map));
108 
109   // Database is done loading, pass it to the new_db_callback on the caller's
110   // thread. This would unblock resource loads.
111   callback_task_runner->PostTask(
112       FROM_HERE,
113       base::BindOnce(std::move(new_db_callback), std::move(v4_database)));
114 }
115 
116 // static
RegisterDatabaseFactoryForTest(std::unique_ptr<V4DatabaseFactory> factory)117 void V4Database::RegisterDatabaseFactoryForTest(
118     std::unique_ptr<V4DatabaseFactory> factory) {
119   g_db_factory.Get() = std::move(factory);
120 }
121 
122 // static
RegisterStoreFactoryForTest(std::unique_ptr<V4StoreFactory> factory)123 void V4Database::RegisterStoreFactoryForTest(
124     std::unique_ptr<V4StoreFactory> factory) {
125   g_store_factory.Get() = std::move(factory);
126 }
127 
V4Database(const scoped_refptr<base::SequencedTaskRunner> & db_task_runner,std::unique_ptr<StoreMap> store_map)128 V4Database::V4Database(
129     const scoped_refptr<base::SequencedTaskRunner>& db_task_runner,
130     std::unique_ptr<StoreMap> store_map)
131     : store_map_(std::move(store_map)),
132       db_task_runner_(db_task_runner),
133       pending_store_updates_(0) {
134   DCHECK(db_task_runner->RunsTasksInCurrentSequence());
135 }
136 
137 // static
Destroy(std::unique_ptr<V4Database> v4_database)138 void V4Database::Destroy(std::unique_ptr<V4Database> v4_database) {
139   DCHECK(CurrentlyOnThread(ThreadID::IO));
140   V4Database* v4_database_raw = v4_database.release();
141   if (v4_database_raw) {
142     v4_database_raw->weak_factory_on_io_.InvalidateWeakPtrs();
143     v4_database_raw->db_task_runner_->DeleteSoon(FROM_HERE, v4_database_raw);
144   }
145 }
146 
~V4Database()147 V4Database::~V4Database() {
148   DCHECK(db_task_runner_->RunsTasksInCurrentSequence());
149 }
150 
ApplyUpdate(std::unique_ptr<ParsedServerResponse> parsed_server_response,DatabaseUpdatedCallback db_updated_callback)151 void V4Database::ApplyUpdate(
152     std::unique_ptr<ParsedServerResponse> parsed_server_response,
153     DatabaseUpdatedCallback db_updated_callback) {
154   DCHECK(CurrentlyOnThread(ThreadID::IO));
155   DCHECK(!pending_store_updates_);
156   DCHECK(db_updated_callback_.is_null());
157 
158   db_updated_callback_ = db_updated_callback;
159 
160   // Post the V4Store update task on the task runner but get the callback on the
161   // current thread.
162   const scoped_refptr<base::SingleThreadTaskRunner> current_task_runner =
163       base::ThreadTaskRunnerHandle::Get();
164   for (std::unique_ptr<ListUpdateResponse>& response :
165        *parsed_server_response) {
166     ListIdentifier identifier(*response);
167     StoreMap::const_iterator iter = store_map_->find(identifier);
168     if (iter != store_map_->end()) {
169       const std::unique_ptr<V4Store>& old_store = iter->second;
170       if (old_store->state() != response->new_client_state()) {
171         // A different state implies there are updates to process.
172         pending_store_updates_++;
173         UpdatedStoreReadyCallback store_ready_callback =
174             base::BindOnce(&V4Database::UpdatedStoreReady,
175                            weak_factory_on_io_.GetWeakPtr(), identifier);
176         db_task_runner_->PostTask(
177             FROM_HERE, base::BindOnce(&V4Store::ApplyUpdate,
178                                       base::Unretained(old_store.get()),
179                                       std::move(response), current_task_runner,
180                                       std::move(store_ready_callback)));
181       }
182     } else {
183       NOTREACHED() << "Got update for unexpected identifier: " << identifier;
184     }
185   }
186 
187   if (!pending_store_updates_) {
188     current_task_runner->PostTask(FROM_HERE, db_updated_callback_);
189     db_updated_callback_.Reset();
190   }
191 }
192 
UpdatedStoreReady(ListIdentifier identifier,std::unique_ptr<V4Store> new_store)193 void V4Database::UpdatedStoreReady(ListIdentifier identifier,
194                                    std::unique_ptr<V4Store> new_store) {
195   DCHECK(CurrentlyOnThread(ThreadID::IO));
196   DCHECK(pending_store_updates_);
197   if (new_store) {
198     (*store_map_)[identifier].swap(new_store);
199     // |new_store| now is the store that needs to be destroyed on task runner.
200     V4Store::Destroy(std::move(new_store));
201   }
202 
203   pending_store_updates_--;
204   if (!pending_store_updates_) {
205     db_updated_callback_.Run();
206     db_updated_callback_.Reset();
207   }
208 }
209 
GetStoreStateMap()210 std::unique_ptr<StoreStateMap> V4Database::GetStoreStateMap() {
211   std::unique_ptr<StoreStateMap> store_state_map =
212       std::make_unique<StoreStateMap>();
213   for (const auto& store_map_iter : *store_map_) {
214     (*store_state_map)[store_map_iter.first] = store_map_iter.second->state();
215   }
216   return store_state_map;
217 }
218 
AreAnyStoresAvailable(const StoresToCheck & stores_to_check) const219 bool V4Database::AreAnyStoresAvailable(
220     const StoresToCheck& stores_to_check) const {
221   DCHECK(CurrentlyOnThread(ThreadID::IO));
222   for (const ListIdentifier& identifier : stores_to_check) {
223     if (IsStoreAvailable(identifier))
224       return true;
225   }
226   return false;
227 }
228 
AreAllStoresAvailable(const StoresToCheck & stores_to_check) const229 bool V4Database::AreAllStoresAvailable(
230     const StoresToCheck& stores_to_check) const {
231   DCHECK(CurrentlyOnThread(ThreadID::IO));
232   for (const ListIdentifier& identifier : stores_to_check) {
233     if (!IsStoreAvailable(identifier))
234       return false;
235   }
236   return true;
237 }
238 
GetStoresMatchingFullHash(const FullHash & full_hash,const StoresToCheck & stores_to_check,StoreAndHashPrefixes * matched_store_and_hash_prefixes)239 void V4Database::GetStoresMatchingFullHash(
240     const FullHash& full_hash,
241     const StoresToCheck& stores_to_check,
242     StoreAndHashPrefixes* matched_store_and_hash_prefixes) {
243   DCHECK(CurrentlyOnThread(ThreadID::IO));
244   matched_store_and_hash_prefixes->clear();
245   for (const ListIdentifier& identifier : stores_to_check) {
246     if (!IsStoreAvailable(identifier))
247       continue;
248     const auto& store_pair = store_map_->find(identifier);
249     DCHECK(store_pair != store_map_->end());
250     const std::unique_ptr<V4Store>& store = store_pair->second;
251     HashPrefix hash_prefix = store->GetMatchingHashPrefix(full_hash);
252     if (!hash_prefix.empty()) {
253       matched_store_and_hash_prefixes->emplace_back(identifier, hash_prefix);
254     }
255   }
256 }
257 
ResetStores(const std::vector<ListIdentifier> & stores_to_reset)258 void V4Database::ResetStores(
259     const std::vector<ListIdentifier>& stores_to_reset) {
260   DCHECK(CurrentlyOnThread(ThreadID::IO));
261   for (const ListIdentifier& identifier : stores_to_reset) {
262     store_map_->at(identifier)->Reset();
263   }
264 }
265 
VerifyChecksum(DatabaseReadyForUpdatesCallback db_ready_for_updates_callback)266 void V4Database::VerifyChecksum(
267     DatabaseReadyForUpdatesCallback db_ready_for_updates_callback) {
268   DCHECK(CurrentlyOnThread(ThreadID::IO));
269 
270   // Make a threadsafe copy of store_map_ w/raw pointers that we can hand to
271   // the DB thread. The V4Stores ptrs are guaranteed to be valid because their
272   // deletion would be sequenced on the DB thread, after this posted task is
273   // serviced.
274   std::vector<std::pair<ListIdentifier, V4Store*>> stores;
275   for (const auto& next_store : *store_map_) {
276     stores.push_back(std::make_pair(next_store.first, next_store.second.get()));
277   }
278 
279   base::PostTaskAndReplyWithResult(
280       db_task_runner_.get(), FROM_HERE,
281       base::BindOnce(&VerifyChecksums, stores),
282       base::BindOnce(&V4Database::OnChecksumVerified,
283                      weak_factory_on_io_.GetWeakPtr(),
284                      std::move(db_ready_for_updates_callback)));
285 }
286 
OnChecksumVerified(DatabaseReadyForUpdatesCallback db_ready_for_updates_callback,const std::vector<ListIdentifier> & stores_to_reset)287 void V4Database::OnChecksumVerified(
288     DatabaseReadyForUpdatesCallback db_ready_for_updates_callback,
289     const std::vector<ListIdentifier>& stores_to_reset) {
290   DCHECK(CurrentlyOnThread(ThreadID::IO));
291   std::move(db_ready_for_updates_callback).Run(stores_to_reset);
292 }
293 
IsStoreAvailable(const ListIdentifier & identifier) const294 bool V4Database::IsStoreAvailable(const ListIdentifier& identifier) const {
295   const auto& store_pair = store_map_->find(identifier);
296   return (store_pair != store_map_->end()) &&
297          store_pair->second->HasValidData();
298 }
299 
RecordFileSizeHistograms()300 void V4Database::RecordFileSizeHistograms() {
301   int64_t db_size = 0;
302   for (const auto& store_map_iter : *store_map_) {
303     const int64_t size =
304         store_map_iter.second->RecordAndReturnFileSize(kV4DatabaseSizeMetric);
305     db_size += size;
306   }
307   const int64_t db_size_kilobytes = static_cast<int64_t>(db_size / 1024);
308   UMA_HISTOGRAM_COUNTS_1M(kV4DatabaseSizeMetric, db_size_kilobytes);
309 }
310 
CollectDatabaseInfo(DatabaseManagerInfo::DatabaseInfo * database_info)311 void V4Database::CollectDatabaseInfo(
312     DatabaseManagerInfo::DatabaseInfo* database_info) {
313   // Records the database size in bytes.
314   int64_t db_size = 0;
315 
316   for (const auto& store_map_iter : *store_map_) {
317     DatabaseManagerInfo::DatabaseInfo::StoreInfo* store_info =
318         database_info->add_store_info();
319     store_map_iter.second->CollectStoreInfo(store_info, kV4DatabaseSizeMetric);
320     db_size += store_info->file_size_bytes();
321   }
322 
323   database_info->set_database_size_bytes(db_size);
324 }
325 
ListInfo(const bool fetch_updates,const std::string & filename,const ListIdentifier & list_id,const SBThreatType sb_threat_type)326 ListInfo::ListInfo(const bool fetch_updates,
327                    const std::string& filename,
328                    const ListIdentifier& list_id,
329                    const SBThreatType sb_threat_type)
330     : fetch_updates_(fetch_updates),
331       filename_(filename),
332       list_id_(list_id),
333       sb_threat_type_(sb_threat_type) {
334   DCHECK(!fetch_updates_ || !filename_.empty());
335   DCHECK_NE(SB_THREAT_TYPE_SAFE, sb_threat_type_);
336 }
337 
~ListInfo()338 ListInfo::~ListInfo() {}
339 
340 }  // namespace safe_browsing
341