Line data Source code
1 : //* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* This Source Code Form is subject to the terms of the Mozilla Public
3 : * License, v. 2.0. If a copy of the MPL was not distributed with this
4 : * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 :
6 : #include "Classifier.h"
7 : #include "LookupCacheV4.h"
8 : #include "nsIPrefBranch.h"
9 : #include "nsIPrefService.h"
10 : #include "nsISimpleEnumerator.h"
11 : #include "nsIRandomGenerator.h"
12 : #include "nsIInputStream.h"
13 : #include "nsISeekableStream.h"
14 : #include "nsIFile.h"
15 : #include "nsNetCID.h"
16 : #include "nsPrintfCString.h"
17 : #include "nsThreadUtils.h"
18 : #include "mozilla/Telemetry.h"
19 : #include "mozilla/IntegerPrintfMacros.h"
20 : #include "mozilla/Logging.h"
21 : #include "mozilla/SyncRunnable.h"
22 : #include "mozilla/Base64.h"
23 : #include "mozilla/Unused.h"
24 : #include "mozilla/SizePrintfMacros.h"
25 : #include "mozilla/UniquePtr.h"
26 : #include "nsIUrlClassifierUtils.h"
27 : #include "nsUrlClassifierDBService.h"
28 :
29 : // MOZ_LOG=UrlClassifierDbService:5
30 : extern mozilla::LazyLogModule gUrlClassifierDbServiceLog;
31 : #define LOG(args) MOZ_LOG(gUrlClassifierDbServiceLog, mozilla::LogLevel::Debug, args)
32 : #define LOG_ENABLED() MOZ_LOG_TEST(gUrlClassifierDbServiceLog, mozilla::LogLevel::Debug)
33 :
34 : #define STORE_DIRECTORY NS_LITERAL_CSTRING("safebrowsing")
35 : #define TO_DELETE_DIR_SUFFIX NS_LITERAL_CSTRING("-to_delete")
36 : #define BACKUP_DIR_SUFFIX NS_LITERAL_CSTRING("-backup")
37 : #define UPDATING_DIR_SUFFIX NS_LITERAL_CSTRING("-updating")
38 :
39 : #define METADATA_SUFFIX NS_LITERAL_CSTRING(".metadata")
40 :
41 : namespace mozilla {
42 : namespace safebrowsing {
43 :
44 : namespace {
45 :
46 : // A scoped-clearer for nsTArray<TableUpdate*>.
47 : // The owning elements will be deleted and the array itself
48 : // will be cleared on exiting the scope.
49 : class ScopedUpdatesClearer {
50 : public:
51 1 : explicit ScopedUpdatesClearer(nsTArray<TableUpdate*> *aUpdates)
52 1 : : mUpdatesArrayRef(aUpdates)
53 : {
54 7 : for (auto update : *aUpdates) {
55 6 : mUpdatesPointerHolder.AppendElement(update);
56 : }
57 1 : }
58 :
59 1 : ~ScopedUpdatesClearer()
60 1 : {
61 1 : mUpdatesArrayRef->Clear();
62 1 : }
63 :
64 : private:
65 : nsTArray<TableUpdate*>* mUpdatesArrayRef;
66 : nsTArray<UniquePtr<TableUpdate>> mUpdatesPointerHolder;
67 : };
68 :
69 : } // End of unnamed namespace.
70 :
71 : void
72 9 : Classifier::SplitTables(const nsACString& str, nsTArray<nsCString>& tables)
73 : {
74 9 : tables.Clear();
75 :
76 9 : nsACString::const_iterator begin, iter, end;
77 9 : str.BeginReading(begin);
78 9 : str.EndReading(end);
79 179 : while (begin != end) {
80 85 : iter = begin;
81 85 : FindCharInReadable(',', iter, end);
82 170 : nsDependentCSubstring table = Substring(begin,iter);
83 85 : if (!table.IsEmpty()) {
84 85 : tables.AppendElement(Substring(begin, iter));
85 : }
86 85 : begin = iter;
87 85 : if (begin != end) {
88 76 : begin++;
89 : }
90 : }
91 9 : }
92 :
93 : nsresult
94 62 : Classifier::GetPrivateStoreDirectory(nsIFile* aRootStoreDirectory,
95 : const nsACString& aTableName,
96 : const nsACString& aProvider,
97 : nsIFile** aPrivateStoreDirectory)
98 : {
99 62 : NS_ENSURE_ARG_POINTER(aPrivateStoreDirectory);
100 :
101 62 : if (!StringEndsWith(aTableName, NS_LITERAL_CSTRING("-proto"))) {
102 : // Only V4 table names (ends with '-proto') would be stored
103 : // to per-provider sub-directory.
104 62 : nsCOMPtr<nsIFile>(aRootStoreDirectory).forget(aPrivateStoreDirectory);
105 62 : return NS_OK;
106 : }
107 :
108 0 : if (aProvider.IsEmpty()) {
109 : // When failing to get provider, just store in the root folder.
110 0 : nsCOMPtr<nsIFile>(aRootStoreDirectory).forget(aPrivateStoreDirectory);
111 0 : return NS_OK;
112 : }
113 :
114 0 : nsCOMPtr<nsIFile> providerDirectory;
115 :
116 : // Clone first since we are gonna create a new directory.
117 0 : nsresult rv = aRootStoreDirectory->Clone(getter_AddRefs(providerDirectory));
118 0 : NS_ENSURE_SUCCESS(rv, rv);
119 :
120 : // Append the provider name to the root store directory.
121 0 : rv = providerDirectory->AppendNative(aProvider);
122 0 : NS_ENSURE_SUCCESS(rv, rv);
123 :
124 : // Ensure existence of the provider directory.
125 : bool dirExists;
126 0 : rv = providerDirectory->Exists(&dirExists);
127 0 : NS_ENSURE_SUCCESS(rv, rv);
128 :
129 0 : if (!dirExists) {
130 0 : LOG(("Creating private directory for %s", nsCString(aTableName).get()));
131 0 : rv = providerDirectory->Create(nsIFile::DIRECTORY_TYPE, 0755);
132 0 : NS_ENSURE_SUCCESS(rv, rv);
133 0 : providerDirectory.forget(aPrivateStoreDirectory);
134 0 : return rv;
135 : }
136 :
137 : // Store directory exists. Check if it's a directory.
138 : bool isDir;
139 0 : rv = providerDirectory->IsDirectory(&isDir);
140 0 : NS_ENSURE_SUCCESS(rv, rv);
141 0 : if (!isDir) {
142 0 : return NS_ERROR_FILE_DESTINATION_NOT_DIR;
143 : }
144 :
145 0 : providerDirectory.forget(aPrivateStoreDirectory);
146 :
147 0 : return NS_OK;
148 : }
149 :
150 1 : Classifier::Classifier()
151 : : mIsTableRequestResultOutdated(true)
152 1 : , mUpdateInterrupted(true)
153 : {
154 3 : NS_NewNamedThread(NS_LITERAL_CSTRING("Classifier Update"),
155 4 : getter_AddRefs(mUpdateThread));
156 1 : }
157 :
158 0 : Classifier::~Classifier()
159 : {
160 0 : Close();
161 0 : }
162 :
163 : nsresult
164 1 : Classifier::SetupPathNames()
165 : {
166 : // Get the root directory where to store all the databases.
167 1 : nsresult rv = mCacheDirectory->Clone(getter_AddRefs(mRootStoreDirectory));
168 1 : NS_ENSURE_SUCCESS(rv, rv);
169 :
170 1 : rv = mRootStoreDirectory->AppendNative(STORE_DIRECTORY);
171 1 : NS_ENSURE_SUCCESS(rv, rv);
172 :
173 : // Make sure LookupCaches (which are persistent and survive updates)
174 : // are reading/writing in the right place. We will be moving their
175 : // files "underneath" them during backup/restore.
176 1 : for (uint32_t i = 0; i < mLookupCaches.Length(); i++) {
177 0 : mLookupCaches[i]->UpdateRootDirHandle(mRootStoreDirectory);
178 : }
179 :
180 : // Directory where to move a backup before an update.
181 1 : rv = mCacheDirectory->Clone(getter_AddRefs(mBackupDirectory));
182 1 : NS_ENSURE_SUCCESS(rv, rv);
183 :
184 1 : rv = mBackupDirectory->AppendNative(STORE_DIRECTORY + BACKUP_DIR_SUFFIX);
185 1 : NS_ENSURE_SUCCESS(rv, rv);
186 :
187 : // Directory where to be working on the update.
188 1 : rv = mCacheDirectory->Clone(getter_AddRefs(mUpdatingDirectory));
189 1 : NS_ENSURE_SUCCESS(rv, rv);
190 :
191 1 : rv = mUpdatingDirectory->AppendNative(STORE_DIRECTORY + UPDATING_DIR_SUFFIX);
192 1 : NS_ENSURE_SUCCESS(rv, rv);
193 :
194 : // Directory where to move the backup so we can atomically
195 : // delete (really move) it.
196 1 : rv = mCacheDirectory->Clone(getter_AddRefs(mToDeleteDirectory));
197 1 : NS_ENSURE_SUCCESS(rv, rv);
198 :
199 1 : rv = mToDeleteDirectory->AppendNative(STORE_DIRECTORY + TO_DELETE_DIR_SUFFIX);
200 1 : NS_ENSURE_SUCCESS(rv, rv);
201 :
202 1 : return NS_OK;
203 : }
204 :
205 : nsresult
206 1 : Classifier::CreateStoreDirectory()
207 : {
208 : // Ensure the safebrowsing directory exists.
209 : bool storeExists;
210 1 : nsresult rv = mRootStoreDirectory->Exists(&storeExists);
211 1 : NS_ENSURE_SUCCESS(rv, rv);
212 :
213 1 : if (!storeExists) {
214 0 : rv = mRootStoreDirectory->Create(nsIFile::DIRECTORY_TYPE, 0755);
215 0 : NS_ENSURE_SUCCESS(rv, rv);
216 : } else {
217 : bool storeIsDir;
218 1 : rv = mRootStoreDirectory->IsDirectory(&storeIsDir);
219 1 : NS_ENSURE_SUCCESS(rv, rv);
220 1 : if (!storeIsDir)
221 0 : return NS_ERROR_FILE_DESTINATION_NOT_DIR;
222 : }
223 :
224 1 : return NS_OK;
225 : }
226 :
227 : nsresult
228 1 : Classifier::Open(nsIFile& aCacheDirectory)
229 : {
230 : // Remember the Local profile directory.
231 1 : nsresult rv = aCacheDirectory.Clone(getter_AddRefs(mCacheDirectory));
232 1 : NS_ENSURE_SUCCESS(rv, rv);
233 :
234 : // Create the handles to the update and backup directories.
235 1 : rv = SetupPathNames();
236 1 : NS_ENSURE_SUCCESS(rv, rv);
237 :
238 : // Clean up any to-delete directories that haven't been deleted yet.
239 : // This is still required for backward compatibility.
240 1 : rv = CleanToDelete();
241 1 : NS_ENSURE_SUCCESS(rv, rv);
242 :
243 : // If we met a crash during the previous update, "safebrowsing-updating"
244 : // directory will exist and let's remove it.
245 1 : rv = mUpdatingDirectory->Remove(true);
246 1 : if (NS_SUCCEEDED(rv)) {
247 : // If the "safebrowsing-updating" exists, it implies a crash occurred
248 : // in the previous update.
249 0 : LOG(("We may have hit a crash in the previous update."));
250 : }
251 :
252 : // Check whether we have an incomplete update and recover from the
253 : // backup if so.
254 1 : rv = RecoverBackups();
255 1 : NS_ENSURE_SUCCESS(rv, rv);
256 :
257 : // Make sure the main store directory exists.
258 1 : rv = CreateStoreDirectory();
259 1 : NS_ENSURE_SUCCESS(rv, rv);
260 :
261 1 : mCryptoHash = do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID, &rv);
262 1 : NS_ENSURE_SUCCESS(rv, rv);
263 :
264 : // Build the list of know urlclassifier lists
265 : // XXX: Disk IO potentially on the main thread during startup
266 1 : RegenActiveTables();
267 :
268 1 : return NS_OK;
269 : }
270 :
271 : void
272 0 : Classifier::Close()
273 : {
274 0 : DropStores();
275 0 : }
276 :
277 : void
278 0 : Classifier::Reset()
279 : {
280 0 : MOZ_ASSERT(NS_GetCurrentThread() != mUpdateThread,
281 : "Reset() MUST NOT be called on update thread");
282 :
283 0 : LOG(("Reset() is called so we interrupt the update."));
284 0 : mUpdateInterrupted = true;
285 :
286 0 : auto resetFunc = [=] {
287 0 : DropStores();
288 :
289 0 : mRootStoreDirectory->Remove(true);
290 0 : mBackupDirectory->Remove(true);
291 0 : mUpdatingDirectory->Remove(true);
292 0 : mToDeleteDirectory->Remove(true);
293 :
294 0 : CreateStoreDirectory();
295 :
296 0 : RegenActiveTables();
297 0 : };
298 :
299 0 : if (!mUpdateThread) {
300 0 : LOG(("Async update has been disabled. Just Reset() on worker thread."));
301 0 : resetFunc();
302 0 : return;
303 : }
304 :
305 : nsCOMPtr<nsIRunnable> r =
306 0 : NS_NewRunnableFunction("safebrowsing::Classifier::Reset", resetFunc);
307 0 : SyncRunnable::DispatchToThread(mUpdateThread, r);
308 : }
309 :
310 : void
311 0 : Classifier::ResetTables(ClearType aType, const nsTArray<nsCString>& aTables)
312 : {
313 0 : for (uint32_t i = 0; i < aTables.Length(); i++) {
314 0 : LOG(("Resetting table: %s", aTables[i].get()));
315 0 : LookupCache *cache = GetLookupCache(aTables[i]);
316 0 : if (cache) {
317 : // Remove any cached Completes for this table if clear type is Clear_Cache
318 0 : if (aType == Clear_Cache) {
319 0 : cache->ClearCache();
320 : } else {
321 0 : cache->ClearAll();
322 : }
323 : }
324 : }
325 :
326 : // Clear on-disk database if clear type is Clear_All
327 0 : if (aType == Clear_All) {
328 0 : DeleteTables(mRootStoreDirectory, aTables);
329 :
330 0 : RegenActiveTables();
331 : }
332 0 : }
333 :
334 : void
335 0 : Classifier::DeleteTables(nsIFile* aDirectory, const nsTArray<nsCString>& aTables)
336 : {
337 0 : nsCOMPtr<nsISimpleEnumerator> entries;
338 0 : nsresult rv = aDirectory->GetDirectoryEntries(getter_AddRefs(entries));
339 0 : NS_ENSURE_SUCCESS_VOID(rv);
340 :
341 : bool hasMore;
342 0 : while (NS_SUCCEEDED(rv = entries->HasMoreElements(&hasMore)) && hasMore) {
343 0 : nsCOMPtr<nsISupports> supports;
344 0 : rv = entries->GetNext(getter_AddRefs(supports));
345 0 : NS_ENSURE_SUCCESS_VOID(rv);
346 :
347 0 : nsCOMPtr<nsIFile> file = do_QueryInterface(supports);
348 0 : NS_ENSURE_TRUE_VOID(file);
349 :
350 : // If |file| is a directory, recurse to find its entries as well.
351 : bool isDirectory;
352 0 : if (NS_FAILED(file->IsDirectory(&isDirectory))) {
353 0 : continue;
354 : }
355 0 : if (isDirectory) {
356 0 : DeleteTables(file, aTables);
357 0 : continue;
358 : }
359 :
360 0 : nsCString leafName;
361 0 : rv = file->GetNativeLeafName(leafName);
362 0 : NS_ENSURE_SUCCESS_VOID(rv);
363 :
364 0 : leafName.Truncate(leafName.RFind("."));
365 0 : if (aTables.Contains(leafName)) {
366 0 : if (NS_FAILED(file->Remove(false))) {
367 0 : NS_WARNING(nsPrintfCString("Fail to remove file %s from the disk",
368 0 : leafName.get()).get());
369 : }
370 : }
371 : }
372 0 : NS_ENSURE_SUCCESS_VOID(rv);
373 : }
374 :
375 : void
376 2 : Classifier::TableRequest(nsACString& aResult)
377 : {
378 2 : MOZ_ASSERT(!NS_IsMainThread(),
379 : "TableRequest must be called on the classifier worker thread.");
380 :
381 : // This function and all disk I/O are guaranteed to occur
382 : // on the same thread so we don't need to add a lock around.
383 2 : if (!mIsTableRequestResultOutdated) {
384 0 : aResult = mTableRequestResult;
385 0 : return;
386 : }
387 :
388 : // Generating v2 table info.
389 4 : nsTArray<nsCString> tables;
390 2 : ActiveTables(tables);
391 14 : for (uint32_t i = 0; i < tables.Length(); i++) {
392 24 : HashStore store(tables[i], GetProvider(tables[i]), mRootStoreDirectory);
393 :
394 12 : nsresult rv = store.Open();
395 12 : if (NS_FAILED(rv)) {
396 0 : continue;
397 : }
398 :
399 12 : ChunkSet &adds = store.AddChunks();
400 12 : ChunkSet &subs = store.SubChunks();
401 :
402 : // Open HashStore will always succeed even that is not a v2 table.
403 : // So skip tables without add and sub chunks.
404 12 : if (adds.Length() == 0 && subs.Length() == 0) {
405 0 : continue;
406 : }
407 :
408 12 : aResult.Append(store.TableName());
409 12 : aResult.Append(';');
410 :
411 12 : if (adds.Length() > 0) {
412 12 : aResult.AppendLiteral("a:");
413 24 : nsAutoCString addList;
414 12 : adds.Serialize(addList);
415 12 : aResult.Append(addList);
416 : }
417 :
418 12 : if (subs.Length() > 0) {
419 0 : if (adds.Length() > 0)
420 0 : aResult.Append(':');
421 0 : aResult.AppendLiteral("s:");
422 0 : nsAutoCString subList;
423 0 : subs.Serialize(subList);
424 0 : aResult.Append(subList);
425 : }
426 :
427 12 : aResult.Append('\n');
428 : }
429 :
430 : // Load meta data from *.metadata files in the root directory.
431 : // Specifically for v4 tables.
432 4 : nsCString metadata;
433 2 : nsresult rv = LoadMetadata(mRootStoreDirectory, metadata);
434 2 : if (NS_SUCCEEDED(rv)) {
435 2 : aResult.Append(metadata);
436 : }
437 :
438 : // Update the TableRequest result in-memory cache.
439 2 : mTableRequestResult = aResult;
440 2 : mIsTableRequestResultOutdated = false;
441 : }
442 :
443 : nsresult
444 1 : Classifier::Check(const nsACString& aSpec,
445 : const nsACString& aTables,
446 : LookupResultArray& aResults)
447 : {
448 2 : Telemetry::AutoTimer<Telemetry::URLCLASSIFIER_CL_CHECK_TIME> timer;
449 :
450 : // Get the set of fragments based on the url. This is necessary because we
451 : // only look up at most 5 URLs per aSpec, even if aSpec has more than 5
452 : // components.
453 2 : nsTArray<nsCString> fragments;
454 1 : nsresult rv = LookupCache::GetLookupFragments(aSpec, &fragments);
455 1 : NS_ENSURE_SUCCESS(rv, rv);
456 :
457 2 : nsTArray<nsCString> activeTables;
458 1 : SplitTables(aTables, activeTables);
459 :
460 2 : nsTArray<LookupCache*> cacheArray;
461 3 : for (uint32_t i = 0; i < activeTables.Length(); i++) {
462 2 : LOG(("Checking table %s", activeTables[i].get()));
463 2 : LookupCache *cache = GetLookupCache(activeTables[i]);
464 2 : if (cache) {
465 2 : cacheArray.AppendElement(cache);
466 : } else {
467 0 : return NS_ERROR_FAILURE;
468 : }
469 : }
470 :
471 : // Now check each lookup fragment against the entries in the DB.
472 3 : for (uint32_t i = 0; i < fragments.Length(); i++) {
473 : Completion lookupHash;
474 2 : lookupHash.FromPlaintext(fragments[i], mCryptoHash);
475 :
476 2 : if (LOG_ENABLED()) {
477 0 : nsAutoCString checking;
478 0 : lookupHash.ToHexString(checking);
479 0 : LOG(("Checking fragment %s, hash %s (%X)", fragments[i].get(),
480 : checking.get(), lookupHash.ToUint32()));
481 : }
482 :
483 6 : for (uint32_t i = 0; i < cacheArray.Length(); i++) {
484 4 : LookupCache *cache = cacheArray[i];
485 : bool has, confirmed;
486 : uint32_t matchLength;
487 :
488 4 : rv = cache->Has(lookupHash, &has, &matchLength, &confirmed);
489 4 : NS_ENSURE_SUCCESS(rv, rv);
490 :
491 4 : if (has) {
492 0 : LookupResult *result = aResults.AppendElement();
493 0 : if (!result)
494 0 : return NS_ERROR_OUT_OF_MEMORY;
495 :
496 0 : LOG(("Found a result in %s: %s",
497 : cache->TableName().get(),
498 : confirmed ? "confirmed." : "Not confirmed."));
499 :
500 0 : result->hash.complete = lookupHash;
501 0 : result->mConfirmed = confirmed;
502 0 : result->mTableName.Assign(cache->TableName());
503 0 : result->mPartialHashLength = confirmed ? COMPLETE_SIZE : matchLength;
504 0 : result->mProtocolV2 = LookupCache::Cast<LookupCacheV2>(cache);
505 : }
506 : }
507 : }
508 :
509 1 : return NS_OK;
510 : }
511 :
512 : static nsresult
513 1 : SwapDirectoryContent(nsIFile* aDir1,
514 : nsIFile* aDir2,
515 : nsIFile* aParentDir,
516 : nsIFile* aTempDir)
517 : {
518 : // Pre-condition: |aDir1| and |aDir2| are directory and their parent
519 : // are both |aParentDir|.
520 : //
521 : // Post-condition: The locations where aDir1 and aDir2 point to will not
522 : // change but their contents will be exchanged. If we failed
523 : // to swap their content, everything will be rolled back.
524 :
525 2 : nsAutoCString tempDirName;
526 1 : aTempDir->GetNativeLeafName(tempDirName);
527 :
528 : nsresult rv;
529 :
530 2 : nsAutoCString dirName1, dirName2;
531 1 : aDir1->GetNativeLeafName(dirName1);
532 1 : aDir2->GetNativeLeafName(dirName2);
533 :
534 1 : LOG(("Swapping directories %s and %s...", dirName1.get(),
535 : dirName2.get()));
536 :
537 : // 1. Rename "dirName1" to "temp"
538 1 : rv = aDir1->RenameToNative(nullptr, tempDirName);
539 1 : if (NS_FAILED(rv)) {
540 0 : LOG(("Unable to rename %s to %s", dirName1.get(),
541 : tempDirName.get()));
542 0 : return rv; // Nothing to roll back.
543 : }
544 :
545 : // 1.1. Create a handle for temp directory. This is required since
546 : // |nsIFile.rename| will not change the location where the
547 : // object points to.
548 2 : nsCOMPtr<nsIFile> tempDirectory;
549 1 : rv = aParentDir->Clone(getter_AddRefs(tempDirectory));
550 1 : rv = tempDirectory->AppendNative(tempDirName);
551 :
552 : // 2. Rename "dirName2" to "dirName1".
553 1 : rv = aDir2->RenameToNative(nullptr, dirName1);
554 1 : if (NS_FAILED(rv)) {
555 0 : LOG(("Failed to rename %s to %s. Rename temp directory back to %s",
556 : dirName2.get(), dirName1.get(), dirName1.get()));
557 0 : nsresult rbrv = tempDirectory->RenameToNative(nullptr, dirName1);
558 0 : NS_ENSURE_SUCCESS(rbrv, rbrv);
559 0 : return rv;
560 : }
561 :
562 : // 3. Rename "temp" to "dirName2".
563 1 : rv = tempDirectory->RenameToNative(nullptr, dirName2);
564 1 : if (NS_FAILED(rv)) {
565 0 : LOG(("Failed to rename temp directory to %s. ", dirName2.get()));
566 : // We've done (1) renaming "dir1 to temp" and
567 : // (2) renaming "dir2 to dir1"
568 : // so the rollback is
569 : // (1) renaming "dir1 to dir2" and
570 : // (2) renaming "temp to dir1"
571 : nsresult rbrv; // rollback result
572 0 : rbrv = aDir1->RenameToNative(nullptr, dirName2);
573 0 : NS_ENSURE_SUCCESS(rbrv, rbrv);
574 0 : rbrv = tempDirectory->RenameToNative(nullptr, dirName1);
575 0 : NS_ENSURE_SUCCESS(rbrv, rbrv);
576 0 : return rv;
577 : }
578 :
579 1 : return rv;
580 : }
581 :
582 : void
583 1 : Classifier::RemoveUpdateIntermediaries()
584 : {
585 : // Remove old LookupCaches.
586 7 : for (auto c: mNewLookupCaches) {
587 6 : delete c;
588 : }
589 1 : mNewLookupCaches.Clear();
590 :
591 : // Remove the "old" directory. (despite its looking-new name)
592 1 : if (NS_FAILED(mUpdatingDirectory->Remove(true))) {
593 : // If the directory is locked from removal for some reason,
594 : // we will fail here and it doesn't matter until the next
595 : // update. (the next udpate will fail due to the removable
596 : // "safebrowsing-udpating" directory.)
597 0 : LOG(("Failed to remove updating directory."));
598 : }
599 1 : }
600 :
601 : void
602 1 : Classifier::CopyAndInvalidateFullHashCache()
603 : {
604 1 : MOZ_ASSERT(NS_GetCurrentThread() != mUpdateThread,
605 : "CopyAndInvalidateFullHashCache cannot be called on update thread "
606 : "since it mutates mLookupCaches which is only safe on "
607 : "worker thread.");
608 :
609 : // New lookup caches are built from disk, data likes cache which is
610 : // generated online won't exist. We have to manually copy cache from
611 : // old LookupCache to new LookupCache.
612 7 : for (auto& newCache: mNewLookupCaches) {
613 21 : for (auto& oldCache: mLookupCaches) {
614 21 : if (oldCache->TableName() == newCache->TableName()) {
615 6 : newCache->CopyFullHashCache(oldCache);
616 6 : break;
617 : }
618 : }
619 : }
620 :
621 : // Clear cache when update.
622 : // Invalidate cache entries in CopyAndInvalidateFullHashCache because only
623 : // at this point we will have cache data in LookupCache.
624 7 : for (auto& newCache: mNewLookupCaches) {
625 6 : newCache->InvalidateExpiredCacheEntries();
626 : }
627 1 : }
628 :
629 : void
630 1 : Classifier::MergeNewLookupCaches()
631 : {
632 1 : MOZ_ASSERT(NS_GetCurrentThread() != mUpdateThread,
633 : "MergeNewLookupCaches cannot be called on update thread "
634 : "since it mutates mLookupCaches which is only safe on "
635 : "worker thread.");
636 :
637 7 : for (auto& newCache: mNewLookupCaches) {
638 : // For each element in mNewLookCaches, it will be swapped with
639 : // - An old cache in mLookupCache with the same table name or
640 : // - nullptr (mLookupCache will be expaned) otherwise.
641 6 : size_t swapIndex = 0;
642 36 : for (; swapIndex < mLookupCaches.Length(); swapIndex++) {
643 21 : if (mLookupCaches[swapIndex]->TableName() == newCache->TableName()) {
644 6 : break;
645 : }
646 : }
647 6 : if (swapIndex == mLookupCaches.Length()) {
648 0 : mLookupCaches.AppendElement(nullptr);
649 : }
650 :
651 6 : Swap(mLookupCaches[swapIndex], newCache);
652 6 : mLookupCaches[swapIndex]->UpdateRootDirHandle(mRootStoreDirectory);
653 : }
654 :
655 : // At this point, mNewLookupCaches's length remains the same but
656 : // will contain either old cache (override) or nullptr (append).
657 1 : }
658 :
659 : nsresult
660 1 : Classifier::SwapInNewTablesAndCleanup()
661 : {
662 : nsresult rv;
663 :
664 : // Step 1. Swap in on-disk tables. The idea of using "safebrowsing-backup"
665 : // as the intermediary directory is we can get databases recovered if
666 : // crash occurred in any step of the swap. (We will recover from
667 : // "safebrowsing-backup" in OpenDb().)
668 1 : rv = SwapDirectoryContent(mUpdatingDirectory, // contains new tables
669 : mRootStoreDirectory, // contains old tables
670 : mCacheDirectory, // common parent dir
671 1 : mBackupDirectory); // intermediary dir for swap
672 1 : if (NS_FAILED(rv)) {
673 0 : LOG(("Failed to swap in on-disk tables."));
674 0 : RemoveUpdateIntermediaries();
675 0 : return rv;
676 : }
677 :
678 : // Step 2. Merge mNewLookupCaches into mLookupCaches. The outdated
679 : // LookupCaches will be stored in mNewLookupCaches and be cleaned
680 : // up later.
681 1 : MergeNewLookupCaches();
682 :
683 : // Step 3. Re-generate active tables based on on-disk tables.
684 1 : rv = RegenActiveTables();
685 1 : if (NS_FAILED(rv)) {
686 0 : LOG(("Failed to re-generate active tables!"));
687 : }
688 :
689 : // Step 4. Clean up intermediaries for update.
690 1 : RemoveUpdateIntermediaries();
691 :
692 : // Step 5. Invalidate cached tableRequest request.
693 1 : mIsTableRequestResultOutdated = true;
694 :
695 1 : LOG(("Done swap in updated tables."));
696 :
697 1 : return rv;
698 : }
699 :
700 0 : void Classifier::FlushAndDisableAsyncUpdate()
701 : {
702 0 : LOG(("Classifier::FlushAndDisableAsyncUpdate [%p, %p]", this, mUpdateThread.get()));
703 :
704 0 : if (!mUpdateThread) {
705 0 : LOG(("Async update has been disabled."));
706 0 : return;
707 : }
708 :
709 0 : mUpdateThread->Shutdown();
710 0 : mUpdateThread = nullptr;
711 : }
712 :
713 : nsresult
714 1 : Classifier::AsyncApplyUpdates(nsTArray<TableUpdate*>* aUpdates,
715 : const AsyncUpdateCallback& aCallback)
716 : {
717 1 : LOG(("Classifier::AsyncApplyUpdates"));
718 :
719 1 : if (!mUpdateThread) {
720 0 : LOG(("Async update has already been disabled."));
721 0 : return NS_ERROR_FAILURE;
722 : }
723 :
724 : // Caller thread | Update thread
725 : // --------------------------------------------------------
726 : // | ApplyUpdatesBackground
727 : // (processing other task) | (bg-update done. ping back to caller thread)
728 : // (processing other task) | idle...
729 : // ApplyUpdatesForeground |
730 : // callback |
731 :
732 1 : mUpdateInterrupted = false;
733 1 : nsresult rv = mRootStoreDirectory->Clone(getter_AddRefs(mRootStoreDirectoryForUpdate));
734 1 : if (NS_FAILED(rv)) {
735 0 : LOG(("Failed to clone mRootStoreDirectory for update."));
736 0 : return rv;
737 : }
738 :
739 2 : nsCOMPtr<nsIThread> callerThread = NS_GetCurrentThread();
740 1 : MOZ_ASSERT(callerThread != mUpdateThread);
741 :
742 : nsCOMPtr<nsIRunnable> bgRunnable =
743 7 : NS_NewRunnableFunction("safebrowsing::Classifier::AsyncApplyUpdates", [=] {
744 3 : MOZ_ASSERT(NS_GetCurrentThread() == mUpdateThread,
745 : "MUST be on update thread");
746 :
747 1 : LOG(("Step 1. ApplyUpdatesBackground on update thread."));
748 2 : nsCString failedTableName;
749 2 : nsresult bgRv = ApplyUpdatesBackground(aUpdates, failedTableName);
750 :
751 4 : nsCOMPtr<nsIRunnable> fgRunnable = NS_NewRunnableFunction(
752 4 : "safebrowsing::Classifier::AsyncApplyUpdates", [=] {
753 1 : MOZ_ASSERT(NS_GetCurrentThread() == callerThread,
754 : "MUST be on caller thread");
755 :
756 1 : LOG(("Step 2. ApplyUpdatesForeground on caller thread"));
757 1 : nsresult rv = ApplyUpdatesForeground(bgRv, failedTableName);
758 : ;
759 :
760 1 : LOG(("Step 3. Updates applied! Fire callback."));
761 :
762 1 : aCallback(rv);
763 4 : });
764 1 : callerThread->Dispatch(fgRunnable, NS_DISPATCH_NORMAL);
765 4 : });
766 :
767 1 : return mUpdateThread->Dispatch(bgRunnable, NS_DISPATCH_NORMAL);
768 : }
769 :
770 : nsresult
771 1 : Classifier::ApplyUpdatesBackground(nsTArray<TableUpdate*>* aUpdates,
772 : nsACString& aFailedTableName)
773 : {
774 : // |mUpdateInterrupted| is guaranteed to have been unset.
775 : // If |mUpdateInterrupted| is set at any point, Reset() must have
776 : // been called then we need to interrupt the update process.
777 : // We only add checkpoints for non-trivial tasks.
778 :
779 1 : if (!aUpdates || aUpdates->Length() == 0) {
780 0 : return NS_OK;
781 : }
782 :
783 : nsCOMPtr<nsIUrlClassifierUtils> urlUtil =
784 2 : do_GetService(NS_URLCLASSIFIERUTILS_CONTRACTID);
785 :
786 2 : nsCString provider;
787 : // Assume all TableUpdate objects should have the same provider.
788 1 : urlUtil->GetTelemetryProvider((*aUpdates)[0]->TableName(), provider);
789 :
790 : Telemetry::AutoTimer<Telemetry::URLCLASSIFIER_CL_KEYED_UPDATE_TIME>
791 2 : keyedTimer(provider);
792 :
793 1 : PRIntervalTime clockStart = 0;
794 1 : if (LOG_ENABLED()) {
795 0 : clockStart = PR_IntervalNow();
796 : }
797 :
798 : nsresult rv;
799 :
800 : {
801 2 : ScopedUpdatesClearer scopedUpdatesClearer(aUpdates);
802 :
803 : {
804 : // Check point 1: Copying file takes time so we check here.
805 1 : if (mUpdateInterrupted) {
806 0 : LOG(("Update is interrupted. Don't copy files."));
807 0 : return NS_OK;
808 : }
809 :
810 1 : rv = CopyInUseDirForUpdate(); // i.e. mUpdatingDirectory will be setup.
811 1 : if (NS_FAILED(rv)) {
812 0 : LOG(("Failed to copy in-use directory for update."));
813 0 : return rv;
814 : }
815 : }
816 :
817 1 : LOG(("Applying %" PRIuSIZE " table updates.", aUpdates->Length()));
818 :
819 7 : for (uint32_t i = 0; i < aUpdates->Length(); i++) {
820 : // Previous UpdateHashStore() may have consumed this update..
821 6 : if ((*aUpdates)[i]) {
822 : // Run all updates for one table
823 12 : nsCString updateTable(aUpdates->ElementAt(i)->TableName());
824 :
825 : // Check point 2: Processing downloaded data takes time.
826 6 : if (mUpdateInterrupted) {
827 0 : LOG(("Update is interrupted. Stop building new tables."));
828 0 : return NS_OK;
829 : }
830 :
831 : // Will update the mirrored in-memory and on-disk databases.
832 6 : if (TableUpdate::Cast<TableUpdateV2>((*aUpdates)[i])) {
833 6 : rv = UpdateHashStore(aUpdates, updateTable);
834 : } else {
835 0 : rv = UpdateTableV4(aUpdates, updateTable);
836 : }
837 :
838 6 : if (NS_FAILED(rv)) {
839 0 : aFailedTableName = updateTable;
840 0 : RemoveUpdateIntermediaries();
841 0 : return rv;
842 : }
843 : }
844 : }
845 :
846 : } // End of scopedUpdatesClearer scope.
847 :
848 1 : if (LOG_ENABLED()) {
849 0 : PRIntervalTime clockEnd = PR_IntervalNow();
850 0 : LOG(("update took %dms\n",
851 : PR_IntervalToMilliseconds(clockEnd - clockStart)));
852 : }
853 :
854 1 : return rv;
855 : }
856 :
857 : nsresult
858 1 : Classifier::ApplyUpdatesForeground(nsresult aBackgroundRv,
859 : const nsACString& aFailedTableName)
860 : {
861 1 : if (mUpdateInterrupted) {
862 0 : LOG(("Update is interrupted! Just remove update intermediaries."));
863 0 : RemoveUpdateIntermediaries();
864 0 : return NS_OK;
865 : }
866 1 : if (NS_SUCCEEDED(aBackgroundRv)) {
867 : // Copy and Invalidate fullhash cache here because this call requires
868 : // mLookupCaches which is only available on work-thread
869 1 : CopyAndInvalidateFullHashCache();
870 :
871 1 : return SwapInNewTablesAndCleanup();
872 : }
873 0 : if (NS_ERROR_OUT_OF_MEMORY != aBackgroundRv) {
874 0 : ResetTables(Clear_All, nsTArray<nsCString> { nsCString(aFailedTableName) });
875 : }
876 0 : return aBackgroundRv;
877 : }
878 :
879 : nsresult
880 0 : Classifier::ApplyFullHashes(nsTArray<TableUpdate*>* aUpdates)
881 : {
882 0 : LOG(("Applying %" PRIuSIZE " table gethashes.", aUpdates->Length()));
883 :
884 0 : ScopedUpdatesClearer scopedUpdatesClearer(aUpdates);
885 0 : for (uint32_t i = 0; i < aUpdates->Length(); i++) {
886 0 : TableUpdate *update = aUpdates->ElementAt(i);
887 :
888 0 : nsresult rv = UpdateCache(update);
889 0 : NS_ENSURE_SUCCESS(rv, rv);
890 :
891 0 : aUpdates->ElementAt(i) = nullptr;
892 : }
893 :
894 0 : return NS_OK;
895 : }
896 :
897 : void
898 0 : Classifier::GetCacheInfo(const nsACString& aTable,
899 : nsIUrlClassifierCacheInfo** aCache)
900 : {
901 0 : LookupCache* lookupCache = GetLookupCache(aTable);
902 0 : if (!lookupCache) {
903 0 : return;
904 : }
905 :
906 0 : lookupCache->GetCacheInfo(aCache);
907 : }
908 :
909 : void
910 0 : Classifier::DropStores()
911 : {
912 0 : for (uint32_t i = 0; i < mLookupCaches.Length(); i++) {
913 0 : delete mLookupCaches[i];
914 : }
915 0 : mLookupCaches.Clear();
916 0 : }
917 :
918 : nsresult
919 2 : Classifier::RegenActiveTables()
920 : {
921 2 : mActiveTablesCache.Clear();
922 :
923 4 : nsTArray<nsCString> foundTables;
924 2 : ScanStoreDir(mRootStoreDirectory, foundTables);
925 :
926 14 : for (uint32_t i = 0; i < foundTables.Length(); i++) {
927 24 : nsCString table(foundTables[i]);
928 :
929 12 : LookupCache *lookupCache = GetLookupCache(table);
930 12 : if (!lookupCache) {
931 0 : continue;
932 : }
933 :
934 12 : if (!lookupCache->IsPrimed()) {
935 0 : continue;
936 : }
937 :
938 12 : if (LookupCache::Cast<LookupCacheV4>(lookupCache)) {
939 0 : LOG(("Active v4 table: %s", table.get()));
940 : } else {
941 24 : HashStore store(table, GetProvider(table), mRootStoreDirectory);
942 :
943 12 : nsresult rv = store.Open();
944 12 : if (NS_FAILED(rv)) {
945 0 : continue;
946 : }
947 :
948 12 : const ChunkSet &adds = store.AddChunks();
949 12 : const ChunkSet &subs = store.SubChunks();
950 :
951 12 : if (adds.Length() == 0 && subs.Length() == 0) {
952 0 : continue;
953 : }
954 :
955 12 : LOG(("Active v2 table: %s", store.TableName().get()));
956 : }
957 :
958 12 : mActiveTablesCache.AppendElement(table);
959 : }
960 :
961 4 : return NS_OK;
962 : }
963 :
964 : nsresult
965 2 : Classifier::ScanStoreDir(nsIFile* aDirectory, nsTArray<nsCString>& aTables)
966 : {
967 4 : nsCOMPtr<nsISimpleEnumerator> entries;
968 2 : nsresult rv = aDirectory->GetDirectoryEntries(getter_AddRefs(entries));
969 2 : NS_ENSURE_SUCCESS(rv, rv);
970 :
971 : bool hasMore;
972 50 : while (NS_SUCCEEDED(rv = entries->HasMoreElements(&hasMore)) && hasMore) {
973 48 : nsCOMPtr<nsISupports> supports;
974 24 : rv = entries->GetNext(getter_AddRefs(supports));
975 24 : NS_ENSURE_SUCCESS(rv, rv);
976 :
977 48 : nsCOMPtr<nsIFile> file = do_QueryInterface(supports);
978 :
979 : // If |file| is a directory, recurse to find its entries as well.
980 : bool isDirectory;
981 24 : if (NS_FAILED(file->IsDirectory(&isDirectory))) {
982 0 : continue;
983 : }
984 24 : if (isDirectory) {
985 0 : ScanStoreDir(file, aTables);
986 0 : continue;
987 : }
988 :
989 48 : nsCString leafName;
990 24 : rv = file->GetNativeLeafName(leafName);
991 24 : NS_ENSURE_SUCCESS(rv, rv);
992 :
993 : // Both v2 and v4 contain .pset file
994 48 : nsCString suffix(NS_LITERAL_CSTRING(".pset"));
995 :
996 24 : int32_t dot = leafName.RFind(suffix, 0);
997 24 : if (dot != -1) {
998 12 : leafName.Cut(dot, suffix.Length());
999 12 : aTables.AppendElement(leafName);
1000 : }
1001 : }
1002 2 : NS_ENSURE_SUCCESS(rv, rv);
1003 :
1004 2 : return NS_OK;
1005 : }
1006 :
1007 : nsresult
1008 2 : Classifier::ActiveTables(nsTArray<nsCString>& aTables)
1009 : {
1010 2 : aTables = mActiveTablesCache;
1011 2 : return NS_OK;
1012 : }
1013 :
1014 : nsresult
1015 1 : Classifier::CleanToDelete()
1016 : {
1017 : bool exists;
1018 1 : nsresult rv = mToDeleteDirectory->Exists(&exists);
1019 1 : NS_ENSURE_SUCCESS(rv, rv);
1020 :
1021 1 : if (exists) {
1022 0 : rv = mToDeleteDirectory->Remove(true);
1023 0 : NS_ENSURE_SUCCESS(rv, rv);
1024 : }
1025 :
1026 1 : return NS_OK;
1027 : }
1028 :
1029 : #ifdef MOZ_SAFEBROWSING_DUMP_FAILED_UPDATES
1030 :
1031 : already_AddRefed<nsIFile>
1032 0 : Classifier::GetFailedUpdateDirectroy()
1033 : {
1034 0 : nsCString failedUpdatekDirName = STORE_DIRECTORY + nsCString("-failedupdate");
1035 :
1036 0 : nsCOMPtr<nsIFile> failedUpdatekDirectory;
1037 0 : if (NS_FAILED(mCacheDirectory->Clone(getter_AddRefs(failedUpdatekDirectory))) ||
1038 0 : NS_FAILED(failedUpdatekDirectory->AppendNative(failedUpdatekDirName))) {
1039 0 : LOG(("Failed to init failedUpdatekDirectory."));
1040 0 : return nullptr;
1041 : }
1042 :
1043 0 : return failedUpdatekDirectory.forget();
1044 : }
1045 :
1046 : nsresult
1047 0 : Classifier::DumpRawTableUpdates(const nsACString& aRawUpdates)
1048 : {
1049 0 : LOG(("Dumping raw table updates..."));
1050 :
1051 0 : DumpFailedUpdate();
1052 :
1053 0 : nsCOMPtr<nsIFile> failedUpdatekDirectory = GetFailedUpdateDirectroy();
1054 :
1055 : // Create tableupdate.bin and dump raw table update data.
1056 0 : nsCOMPtr<nsIFile> rawTableUpdatesFile;
1057 0 : nsCOMPtr<nsIOutputStream> outputStream;
1058 0 : if (NS_FAILED(failedUpdatekDirectory->Clone(getter_AddRefs(rawTableUpdatesFile))) ||
1059 0 : NS_FAILED(rawTableUpdatesFile->AppendNative(nsCString("tableupdates.bin"))) ||
1060 0 : NS_FAILED(NS_NewLocalFileOutputStream(getter_AddRefs(outputStream),
1061 : rawTableUpdatesFile,
1062 : PR_WRONLY | PR_TRUNCATE | PR_CREATE_FILE))) {
1063 0 : LOG(("Failed to create file to dump raw table updates."));
1064 0 : return NS_ERROR_FAILURE;
1065 : }
1066 :
1067 : // Write out the data.
1068 : uint32_t written;
1069 0 : nsresult rv = outputStream->Write(aRawUpdates.BeginReading(),
1070 0 : aRawUpdates.Length(), &written);
1071 0 : NS_ENSURE_SUCCESS(rv, rv);
1072 0 : NS_ENSURE_TRUE(written == aRawUpdates.Length(), NS_ERROR_FAILURE);
1073 :
1074 0 : return rv;
1075 : }
1076 :
1077 : nsresult
1078 0 : Classifier::DumpFailedUpdate()
1079 : {
1080 0 : LOG(("Dumping failed update..."));
1081 :
1082 0 : nsCOMPtr<nsIFile> failedUpdatekDirectory = GetFailedUpdateDirectroy();
1083 :
1084 : // Remove the "failed update" directory no matter it exists or not.
1085 : // Failure is fine because the directory may not exist.
1086 0 : failedUpdatekDirectory->Remove(true);
1087 :
1088 0 : nsCString failedUpdatekDirName;
1089 0 : nsresult rv = failedUpdatekDirectory->GetNativeLeafName(failedUpdatekDirName);
1090 0 : NS_ENSURE_SUCCESS(rv, rv);
1091 :
1092 : // Copy the in-use directory to a clean "failed update" directory.
1093 0 : nsCOMPtr<nsIFile> inUseDirectory;
1094 0 : if (NS_FAILED(mRootStoreDirectory->Clone(getter_AddRefs(inUseDirectory))) ||
1095 0 : NS_FAILED(inUseDirectory->CopyToNative(nullptr, failedUpdatekDirName))) {
1096 0 : LOG(("Failed to move in-use to the \"failed update\" directory %s",
1097 : failedUpdatekDirName.get()));
1098 0 : return NS_ERROR_FAILURE;
1099 : }
1100 :
1101 0 : return rv;
1102 : }
1103 :
1104 : #endif // MOZ_SAFEBROWSING_DUMP_FAILED_UPDATES
1105 :
1106 : nsresult
1107 1 : Classifier::CopyInUseDirForUpdate()
1108 : {
1109 1 : LOG(("Copy in-use directory content for update."));
1110 :
1111 : // We copy everything from in-use directory to a temporary directory
1112 : // for updating.
1113 :
1114 2 : nsCString updatingDirName;
1115 1 : nsresult rv = mUpdatingDirectory->GetNativeLeafName(updatingDirName);
1116 1 : NS_ENSURE_SUCCESS(rv, rv);
1117 :
1118 : // Remove the destination directory first (just in case) the do the copy.
1119 1 : mUpdatingDirectory->Remove(true);
1120 1 : if (!mRootStoreDirectoryForUpdate) {
1121 0 : LOG(("mRootStoreDirectoryForUpdate is null."));
1122 0 : return NS_ERROR_NULL_POINTER;
1123 : }
1124 1 : rv = mRootStoreDirectoryForUpdate->CopyToNative(nullptr, updatingDirName);
1125 1 : NS_ENSURE_SUCCESS(rv, rv);
1126 :
1127 1 : return NS_OK;
1128 : }
1129 :
1130 : nsresult
1131 1 : Classifier::RecoverBackups()
1132 : {
1133 : bool backupExists;
1134 1 : nsresult rv = mBackupDirectory->Exists(&backupExists);
1135 1 : NS_ENSURE_SUCCESS(rv, rv);
1136 :
1137 1 : if (backupExists) {
1138 : // Remove the safebrowsing dir if it exists
1139 0 : nsCString storeDirName;
1140 0 : rv = mRootStoreDirectory->GetNativeLeafName(storeDirName);
1141 0 : NS_ENSURE_SUCCESS(rv, rv);
1142 :
1143 : bool storeExists;
1144 0 : rv = mRootStoreDirectory->Exists(&storeExists);
1145 0 : NS_ENSURE_SUCCESS(rv, rv);
1146 :
1147 0 : if (storeExists) {
1148 0 : rv = mRootStoreDirectory->Remove(true);
1149 0 : NS_ENSURE_SUCCESS(rv, rv);
1150 : }
1151 :
1152 : // Move the backup to the store location
1153 0 : rv = mBackupDirectory->MoveToNative(nullptr, storeDirName);
1154 0 : NS_ENSURE_SUCCESS(rv, rv);
1155 :
1156 : // mBackupDirectory now points to storeDir, fix up.
1157 0 : rv = SetupPathNames();
1158 0 : NS_ENSURE_SUCCESS(rv, rv);
1159 : }
1160 :
1161 1 : return NS_OK;
1162 : }
1163 :
1164 : bool
1165 6 : Classifier::CheckValidUpdate(nsTArray<TableUpdate*>* aUpdates,
1166 : const nsACString& aTable)
1167 : {
1168 : // take the quick exit if there is no valid update for us
1169 : // (common case)
1170 6 : uint32_t validupdates = 0;
1171 :
1172 42 : for (uint32_t i = 0; i < aUpdates->Length(); i++) {
1173 36 : TableUpdate *update = aUpdates->ElementAt(i);
1174 36 : if (!update || !update->TableName().Equals(aTable))
1175 30 : continue;
1176 6 : if (update->Empty()) {
1177 0 : aUpdates->ElementAt(i) = nullptr;
1178 0 : continue;
1179 : }
1180 6 : validupdates++;
1181 : }
1182 :
1183 6 : if (!validupdates) {
1184 : // This can happen if the update was only valid for one table.
1185 0 : return false;
1186 : }
1187 :
1188 6 : return true;
1189 : }
1190 :
1191 : nsCString
1192 43 : Classifier::GetProvider(const nsACString& aTableName)
1193 : {
1194 : nsCOMPtr<nsIUrlClassifierUtils> urlUtil =
1195 86 : do_GetService(NS_URLCLASSIFIERUTILS_CONTRACTID);
1196 :
1197 86 : nsCString provider;
1198 43 : nsresult rv = urlUtil->GetProvider(aTableName, provider);
1199 :
1200 86 : return NS_SUCCEEDED(rv) ? provider : EmptyCString();
1201 : }
1202 :
1203 : /*
1204 : * This will consume+delete updates from the passed nsTArray.
1205 : */
1206 : nsresult
1207 6 : Classifier::UpdateHashStore(nsTArray<TableUpdate*>* aUpdates,
1208 : const nsACString& aTable)
1209 : {
1210 6 : if (nsUrlClassifierDBService::ShutdownHasStarted()) {
1211 0 : return NS_ERROR_UC_UPDATE_SHUTDOWNING;
1212 : }
1213 :
1214 6 : LOG(("Classifier::UpdateHashStore(%s)", PromiseFlatCString(aTable).get()));
1215 :
1216 12 : HashStore store(aTable, GetProvider(aTable), mUpdatingDirectory);
1217 :
1218 6 : if (!CheckValidUpdate(aUpdates, store.TableName())) {
1219 0 : return NS_OK;
1220 : }
1221 :
1222 6 : nsresult rv = store.Open();
1223 6 : NS_ENSURE_SUCCESS(rv, rv);
1224 6 : rv = store.BeginUpdate();
1225 6 : NS_ENSURE_SUCCESS(rv, rv);
1226 :
1227 : // Read the part of the store that is (only) in the cache
1228 : LookupCacheV2* lookupCache =
1229 6 : LookupCache::Cast<LookupCacheV2>(GetLookupCacheForUpdate(store.TableName()));
1230 6 : if (!lookupCache) {
1231 0 : return NS_ERROR_UC_UPDATE_TABLE_NOT_FOUND;
1232 : }
1233 :
1234 12 : FallibleTArray<uint32_t> AddPrefixHashes;
1235 6 : rv = lookupCache->GetPrefixes(AddPrefixHashes);
1236 6 : NS_ENSURE_SUCCESS(rv, rv);
1237 6 : rv = store.AugmentAdds(AddPrefixHashes);
1238 6 : NS_ENSURE_SUCCESS(rv, rv);
1239 6 : AddPrefixHashes.Clear();
1240 :
1241 6 : uint32_t applied = 0;
1242 :
1243 42 : for (uint32_t i = 0; i < aUpdates->Length(); i++) {
1244 36 : TableUpdate *update = aUpdates->ElementAt(i);
1245 36 : if (!update || !update->TableName().Equals(store.TableName()))
1246 30 : continue;
1247 :
1248 6 : rv = store.ApplyUpdate(*update);
1249 6 : NS_ENSURE_SUCCESS(rv, rv);
1250 :
1251 6 : applied++;
1252 :
1253 6 : auto updateV2 = TableUpdate::Cast<TableUpdateV2>(update);
1254 6 : if (updateV2) {
1255 6 : LOG(("Applied update to table %s:", store.TableName().get()));
1256 6 : LOG((" %d add chunks", updateV2->AddChunks().Length()));
1257 6 : LOG((" %" PRIuSIZE " add prefixes", updateV2->AddPrefixes().Length()));
1258 6 : LOG((" %" PRIuSIZE " add completions", updateV2->AddCompletes().Length()));
1259 6 : LOG((" %d sub chunks", updateV2->SubChunks().Length()));
1260 6 : LOG((" %" PRIuSIZE " sub prefixes", updateV2->SubPrefixes().Length()));
1261 6 : LOG((" %" PRIuSIZE " sub completions", updateV2->SubCompletes().Length()));
1262 6 : LOG((" %d add expirations", updateV2->AddExpirations().Length()));
1263 6 : LOG((" %d sub expirations", updateV2->SubExpirations().Length()));
1264 : }
1265 :
1266 6 : aUpdates->ElementAt(i) = nullptr;
1267 : }
1268 :
1269 6 : LOG(("Applied %d update(s) to %s.", applied, store.TableName().get()));
1270 :
1271 6 : rv = store.Rebuild();
1272 6 : NS_ENSURE_SUCCESS(rv, rv);
1273 :
1274 6 : LOG(("Table %s now has:", store.TableName().get()));
1275 6 : LOG((" %d add chunks", store.AddChunks().Length()));
1276 6 : LOG((" %" PRIuSIZE " add prefixes", store.AddPrefixes().Length()));
1277 6 : LOG((" %" PRIuSIZE " add completions", store.AddCompletes().Length()));
1278 6 : LOG((" %d sub chunks", store.SubChunks().Length()));
1279 6 : LOG((" %" PRIuSIZE " sub prefixes", store.SubPrefixes().Length()));
1280 6 : LOG((" %" PRIuSIZE " sub completions", store.SubCompletes().Length()));
1281 :
1282 6 : rv = store.WriteFile();
1283 6 : NS_ENSURE_SUCCESS(rv, rv);
1284 :
1285 : // At this point the store is updated and written out to disk, but
1286 : // the data is still in memory. Build our quick-lookup table here.
1287 6 : rv = lookupCache->Build(store.AddPrefixes(), store.AddCompletes());
1288 6 : NS_ENSURE_SUCCESS(rv, NS_ERROR_UC_UPDATE_BUILD_PREFIX_FAILURE);
1289 :
1290 : #if defined(DEBUG)
1291 6 : lookupCache->DumpCompletions();
1292 : #endif
1293 6 : rv = lookupCache->WriteFile();
1294 6 : NS_ENSURE_SUCCESS(rv, NS_ERROR_UC_UPDATE_FAIL_TO_WRITE_DISK);
1295 :
1296 6 : LOG(("Successfully updated %s", store.TableName().get()));
1297 :
1298 6 : return NS_OK;
1299 : }
1300 :
1301 : nsresult
1302 0 : Classifier::UpdateTableV4(nsTArray<TableUpdate*>* aUpdates,
1303 : const nsACString& aTable)
1304 : {
1305 0 : MOZ_ASSERT(!NS_IsMainThread(),
1306 : "UpdateTableV4 must be called on the classifier worker thread.");
1307 0 : if (nsUrlClassifierDBService::ShutdownHasStarted()) {
1308 0 : return NS_ERROR_UC_UPDATE_SHUTDOWNING;
1309 : }
1310 :
1311 0 : LOG(("Classifier::UpdateTableV4(%s)", PromiseFlatCString(aTable).get()));
1312 :
1313 0 : if (!CheckValidUpdate(aUpdates, aTable)) {
1314 0 : return NS_OK;
1315 : }
1316 :
1317 : LookupCacheV4* lookupCache =
1318 0 : LookupCache::Cast<LookupCacheV4>(GetLookupCacheForUpdate(aTable));
1319 0 : if (!lookupCache) {
1320 0 : return NS_ERROR_UC_UPDATE_TABLE_NOT_FOUND;
1321 : }
1322 :
1323 0 : nsresult rv = NS_OK;
1324 :
1325 : // If there are multiple updates for the same table, prefixes1 & prefixes2
1326 : // will act as input and output in turn to reduce memory copy overhead.
1327 0 : PrefixStringMap prefixes1, prefixes2;
1328 0 : PrefixStringMap* input = &prefixes1;
1329 0 : PrefixStringMap* output = &prefixes2;
1330 :
1331 0 : TableUpdateV4* lastAppliedUpdate = nullptr;
1332 0 : for (uint32_t i = 0; i < aUpdates->Length(); i++) {
1333 0 : TableUpdate *update = aUpdates->ElementAt(i);
1334 0 : if (!update || !update->TableName().Equals(aTable)) {
1335 0 : continue;
1336 : }
1337 :
1338 0 : auto updateV4 = TableUpdate::Cast<TableUpdateV4>(update);
1339 0 : NS_ENSURE_TRUE(updateV4, NS_ERROR_UC_UPDATE_TABLE_NOT_FOUND);
1340 :
1341 0 : if (updateV4->IsFullUpdate()) {
1342 0 : input->Clear();
1343 0 : output->Clear();
1344 0 : rv = lookupCache->ApplyUpdate(updateV4, *input, *output);
1345 0 : if (NS_FAILED(rv)) {
1346 0 : return rv;
1347 : }
1348 : } else {
1349 : // If both prefix sets are empty, this means we are doing a partial update
1350 : // without a prior full/partial update in the loop. In this case we should
1351 : // get prefixes from the lookup cache first.
1352 0 : if (prefixes1.IsEmpty() && prefixes2.IsEmpty()) {
1353 0 : lookupCache->GetPrefixes(prefixes1);
1354 : } else {
1355 0 : MOZ_ASSERT(prefixes1.IsEmpty() ^ prefixes2.IsEmpty());
1356 :
1357 : // When there are multiple partial updates, input should always point
1358 : // to the non-empty prefix set(filled by previous full/partial update).
1359 : // output should always point to the empty prefix set.
1360 0 : input = prefixes1.IsEmpty() ? &prefixes2 : &prefixes1;
1361 0 : output = prefixes1.IsEmpty() ? &prefixes1 : &prefixes2;
1362 : }
1363 :
1364 0 : rv = lookupCache->ApplyUpdate(updateV4, *input, *output);
1365 0 : if (NS_FAILED(rv)) {
1366 0 : return rv;
1367 : }
1368 :
1369 0 : input->Clear();
1370 : }
1371 :
1372 : // Keep track of the last applied update.
1373 0 : lastAppliedUpdate = updateV4;
1374 :
1375 0 : aUpdates->ElementAt(i) = nullptr;
1376 : }
1377 :
1378 0 : rv = lookupCache->Build(*output);
1379 0 : NS_ENSURE_SUCCESS(rv, NS_ERROR_UC_UPDATE_BUILD_PREFIX_FAILURE);
1380 :
1381 0 : rv = lookupCache->WriteFile();
1382 0 : NS_ENSURE_SUCCESS(rv, NS_ERROR_UC_UPDATE_FAIL_TO_WRITE_DISK);
1383 :
1384 0 : if (lastAppliedUpdate) {
1385 0 : LOG(("Write meta data of the last applied update."));
1386 0 : rv = lookupCache->WriteMetadata(lastAppliedUpdate);
1387 0 : NS_ENSURE_SUCCESS(rv, NS_ERROR_UC_UPDATE_FAIL_TO_WRITE_DISK);
1388 : }
1389 :
1390 0 : LOG(("Successfully updated %s\n", PromiseFlatCString(aTable).get()));
1391 :
1392 0 : return NS_OK;
1393 : }
1394 :
1395 : nsresult
1396 0 : Classifier::UpdateCache(TableUpdate* aUpdate)
1397 : {
1398 0 : if (!aUpdate) {
1399 0 : return NS_OK;
1400 : }
1401 :
1402 0 : nsAutoCString table(aUpdate->TableName());
1403 0 : LOG(("Classifier::UpdateCache(%s)", table.get()));
1404 :
1405 0 : LookupCache *lookupCache = GetLookupCache(table);
1406 0 : if (!lookupCache) {
1407 0 : return NS_ERROR_FAILURE;
1408 : }
1409 :
1410 0 : auto lookupV2 = LookupCache::Cast<LookupCacheV2>(lookupCache);
1411 0 : if (lookupV2) {
1412 0 : auto updateV2 = TableUpdate::Cast<TableUpdateV2>(aUpdate);
1413 0 : lookupV2->AddGethashResultToCache(updateV2->AddCompletes(),
1414 0 : updateV2->MissPrefixes());
1415 : } else {
1416 0 : auto lookupV4 = LookupCache::Cast<LookupCacheV4>(lookupCache);
1417 0 : if (!lookupV4) {
1418 0 : return NS_ERROR_FAILURE;
1419 : }
1420 :
1421 0 : auto updateV4 = TableUpdate::Cast<TableUpdateV4>(aUpdate);
1422 0 : lookupV4->AddFullHashResponseToCache(updateV4->FullHashResponse());
1423 : }
1424 :
1425 : #if defined(DEBUG)
1426 0 : lookupCache->DumpCache();
1427 : #endif
1428 :
1429 0 : return NS_OK;
1430 : }
1431 :
1432 : LookupCache *
1433 20 : Classifier::GetLookupCache(const nsACString& aTable, bool aForUpdate)
1434 : {
1435 20 : if (aForUpdate) {
1436 6 : MOZ_ASSERT(NS_GetCurrentThread() == mUpdateThread,
1437 : "GetLookupCache(aForUpdate==true) can only be called on update thread.");
1438 : }
1439 :
1440 : nsTArray<LookupCache*>& lookupCaches = aForUpdate ? mNewLookupCaches
1441 20 : : mLookupCaches;
1442 : auto& rootStoreDirectory = aForUpdate ? mUpdatingDirectory
1443 20 : : mRootStoreDirectory;
1444 :
1445 74 : for (auto c: lookupCaches) {
1446 61 : if (c->TableName().Equals(aTable)) {
1447 7 : return c;
1448 : }
1449 : }
1450 :
1451 : // TODO : Bug 1302600, It would be better if we have a more general non-main
1452 : // thread method to convert table name to protocol version. Currently
1453 : // we can only know this by checking if the table name ends with '-proto'.
1454 26 : UniquePtr<LookupCache> cache;
1455 26 : nsCString provider = GetProvider(aTable);
1456 13 : if (StringEndsWith(aTable, NS_LITERAL_CSTRING("-proto"))) {
1457 0 : cache = MakeUnique<LookupCacheV4>(aTable, provider, rootStoreDirectory);
1458 : } else {
1459 13 : cache = MakeUnique<LookupCacheV2>(aTable, provider, rootStoreDirectory);
1460 : }
1461 :
1462 13 : nsresult rv = cache->Init();
1463 13 : if (NS_FAILED(rv)) {
1464 0 : return nullptr;
1465 : }
1466 13 : rv = cache->Open();
1467 13 : if (NS_SUCCEEDED(rv)) {
1468 13 : lookupCaches.AppendElement(cache.get());
1469 13 : return cache.release();
1470 : }
1471 :
1472 : // At this point we failed to open LookupCache.
1473 : //
1474 : // GetLookupCache for update and for other usage will run on update thread
1475 : // and worker thread respectively (Bug 1339760). Removing stuff only in
1476 : // their own realms potentially increases the concurrency.
1477 :
1478 0 : if (aForUpdate) {
1479 : // Remove intermediaries no matter if it's due to file corruption or not.
1480 0 : RemoveUpdateIntermediaries();
1481 0 : return nullptr;
1482 : }
1483 :
1484 : // Non-update case.
1485 0 : if (rv == NS_ERROR_FILE_CORRUPTED) {
1486 0 : Reset(); // Not including the update intermediaries.
1487 : }
1488 0 : return nullptr;
1489 : }
1490 :
1491 : nsresult
1492 0 : Classifier::ReadNoiseEntries(const Prefix& aPrefix,
1493 : const nsACString& aTableName,
1494 : uint32_t aCount,
1495 : PrefixArray* aNoiseEntries)
1496 : {
1497 0 : FallibleTArray<uint32_t> prefixes;
1498 : nsresult rv;
1499 :
1500 0 : LookupCache *cache = GetLookupCache(aTableName);
1501 0 : if (!cache) {
1502 0 : return NS_ERROR_FAILURE;
1503 : }
1504 :
1505 0 : LookupCacheV2* cacheV2 = LookupCache::Cast<LookupCacheV2>(cache);
1506 0 : if (cacheV2) {
1507 0 : rv = cacheV2->GetPrefixes(prefixes);
1508 : } else {
1509 0 : rv = LookupCache::Cast<LookupCacheV4>(cache)->GetFixedLengthPrefixes(prefixes);
1510 : }
1511 :
1512 0 : NS_ENSURE_SUCCESS(rv, rv);
1513 :
1514 0 : if (prefixes.Length() == 0) {
1515 0 : NS_WARNING("Could not find prefix in PrefixSet during noise lookup");
1516 0 : return NS_ERROR_FAILURE;
1517 : }
1518 :
1519 : // We do not want to simply pick random prefixes, because this would allow
1520 : // averaging out the noise by analysing the traffic from Firefox users.
1521 : // Instead, we ensure the 'noise' is the same for the same prefix by seeding
1522 : // the random number generator with the prefix. We prefer not to use rand()
1523 : // which isn't thread safe, and the reseeding of which could trip up other
1524 : // parts othe code that expect actual random numbers.
1525 : // Here we use a simple LCG (Linear Congruential Generator) to generate
1526 : // random numbers. We seed the LCG with the prefix we are generating noise
1527 : // for.
1528 : // http://en.wikipedia.org/wiki/Linear_congruential_generator
1529 :
1530 0 : uint32_t m = prefixes.Length();
1531 0 : uint32_t a = aCount % m;
1532 0 : uint32_t idx = aPrefix.ToUint32() % m;
1533 :
1534 0 : for (size_t i = 0; i < aCount; i++) {
1535 0 : idx = (a * idx + a) % m;
1536 :
1537 : Prefix newPrefix;
1538 0 : uint32_t hash = prefixes[idx];
1539 : // In the case V4 little endian, we did swapping endian when converting from char* to
1540 : // int, should revert endian to make sure we will send hex string correctly
1541 : // See https://bugzilla.mozilla.org/show_bug.cgi?id=1283007#c23
1542 0 : if (!cacheV2 && !bool(MOZ_BIG_ENDIAN)) {
1543 0 : hash = NativeEndian::swapFromBigEndian(prefixes[idx]);
1544 : }
1545 :
1546 0 : newPrefix.FromUint32(hash);
1547 0 : if (newPrefix != aPrefix) {
1548 0 : aNoiseEntries->AppendElement(newPrefix);
1549 : }
1550 : }
1551 :
1552 0 : return NS_OK;
1553 : }
1554 :
1555 : nsresult
1556 2 : Classifier::LoadMetadata(nsIFile* aDirectory, nsACString& aResult)
1557 : {
1558 4 : nsCOMPtr<nsISimpleEnumerator> entries;
1559 2 : nsresult rv = aDirectory->GetDirectoryEntries(getter_AddRefs(entries));
1560 2 : NS_ENSURE_SUCCESS(rv, rv);
1561 2 : NS_ENSURE_ARG_POINTER(entries);
1562 :
1563 : bool hasMore;
1564 50 : while (NS_SUCCEEDED(rv = entries->HasMoreElements(&hasMore)) && hasMore) {
1565 24 : nsCOMPtr<nsISupports> supports;
1566 24 : rv = entries->GetNext(getter_AddRefs(supports));
1567 24 : NS_ENSURE_SUCCESS(rv, rv);
1568 :
1569 24 : nsCOMPtr<nsIFile> file = do_QueryInterface(supports);
1570 :
1571 : // If |file| is a directory, recurse to find its entries as well.
1572 : bool isDirectory;
1573 24 : if (NS_FAILED(file->IsDirectory(&isDirectory))) {
1574 0 : continue;
1575 : }
1576 24 : if (isDirectory) {
1577 0 : LoadMetadata(file, aResult);
1578 0 : continue;
1579 : }
1580 :
1581 : // Truncate file extension to get the table name.
1582 24 : nsCString tableName;
1583 24 : rv = file->GetNativeLeafName(tableName);
1584 24 : NS_ENSURE_SUCCESS(rv, rv);
1585 :
1586 24 : int32_t dot = tableName.RFind(METADATA_SUFFIX, 0);
1587 24 : if (dot == -1) {
1588 24 : continue;
1589 : }
1590 0 : tableName.Cut(dot, METADATA_SUFFIX.Length());
1591 :
1592 : LookupCacheV4* lookupCache =
1593 0 : LookupCache::Cast<LookupCacheV4>(GetLookupCache(tableName));
1594 0 : if (!lookupCache) {
1595 0 : continue;
1596 : }
1597 :
1598 0 : nsCString state;
1599 0 : nsCString checksum;
1600 0 : rv = lookupCache->LoadMetadata(state, checksum);
1601 0 : if (NS_FAILED(rv)) {
1602 0 : LOG(("Failed to get metadata for table %s", tableName.get()));
1603 0 : continue;
1604 : }
1605 :
1606 : // The state might include '\n' so that we have to encode.
1607 0 : nsAutoCString stateBase64;
1608 0 : rv = Base64Encode(state, stateBase64);
1609 0 : NS_ENSURE_SUCCESS(rv, rv);
1610 :
1611 0 : nsAutoCString checksumBase64;
1612 0 : rv = Base64Encode(checksum, checksumBase64);
1613 0 : NS_ENSURE_SUCCESS(rv, rv);
1614 :
1615 0 : LOG(("Appending state '%s' and checksum '%s' for table %s",
1616 : stateBase64.get(), checksumBase64.get(), tableName.get()));
1617 :
1618 0 : aResult.AppendPrintf("%s;%s:%s\n", tableName.get(),
1619 : stateBase64.get(),
1620 0 : checksumBase64.get());
1621 : }
1622 :
1623 2 : return rv;
1624 : }
1625 :
1626 : } // namespace safebrowsing
1627 : } // namespace mozilla
|