Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/toolkit/components/reputationservice/ApplicationReputation.cpp
Line
Count
Source (jump to first uncovered line)
1
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2
/* vim: set ts=2 et sw=2 tw=80: */
3
/* This Source Code Form is subject to the terms of the Mozilla Public
4
 * License, v. 2.0. If a copy of the MPL was not distributed with this
5
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
// See
7
// https://wiki.mozilla.org/Security/Features/Application_Reputation_Design_Doc
8
// for a description of Chrome's implementation of this feature.
9
#include "ApplicationReputation.h"
10
#include "chrome/common/safe_browsing/csd.pb.h"
11
12
#include "nsIArray.h"
13
#include "nsIApplicationReputation.h"
14
#include "nsIChannel.h"
15
#include "nsIHttpChannel.h"
16
#include "nsIIOService.h"
17
#include "nsIPrefService.h"
18
#include "nsISimpleEnumerator.h"
19
#include "nsIStreamListener.h"
20
#include "nsIStringStream.h"
21
#include "nsITimer.h"
22
#include "nsIUploadChannel2.h"
23
#include "nsIURI.h"
24
#include "nsIURL.h"
25
#include "nsIUrlClassifierDBService.h"
26
#include "nsIX509Cert.h"
27
#include "nsIX509CertDB.h"
28
#include "nsIX509CertList.h"
29
30
#include "mozilla/ArrayUtils.h"
31
#include "mozilla/BasePrincipal.h"
32
#include "mozilla/ErrorNames.h"
33
#include "mozilla/LoadContext.h"
34
#include "mozilla/Preferences.h"
35
#include "mozilla/Services.h"
36
#include "mozilla/Telemetry.h"
37
#include "mozilla/TimeStamp.h"
38
#include "mozilla/intl/LocaleService.h"
39
40
#include "nsAutoPtr.h"
41
#include "nsCOMPtr.h"
42
#include "nsDebug.h"
43
#include "nsDependentSubstring.h"
44
#include "nsError.h"
45
#include "nsNetCID.h"
46
#include "nsReadableUtils.h"
47
#include "nsServiceManagerUtils.h"
48
#include "nsString.h"
49
#include "nsTArray.h"
50
#include "nsThreadUtils.h"
51
52
#include "nsIContentPolicy.h"
53
#include "nsICryptoHash.h"
54
#include "nsILoadInfo.h"
55
#include "nsContentUtils.h"
56
#include "nsWeakReference.h"
57
#include "nsIRedirectHistoryEntry.h"
58
59
#include "ApplicationReputationTelemetryUtils.h"
60
61
using mozilla::ArrayLength;
62
using mozilla::BasePrincipal;
63
using mozilla::OriginAttributes;
64
using mozilla::Preferences;
65
using mozilla::TimeStamp;
66
using mozilla::Telemetry::Accumulate;
67
using mozilla::Telemetry::AccumulateCategorical;
68
using mozilla::intl::LocaleService;
69
using safe_browsing::ClientDownloadRequest;
70
using safe_browsing::ClientDownloadRequest_CertificateChain;
71
using safe_browsing::ClientDownloadRequest_Resource;
72
using safe_browsing::ClientDownloadRequest_SignatureInfo;
73
74
// Preferences that we need to initialize the query.
75
#define PREF_SB_APP_REP_URL "browser.safebrowsing.downloads.remote.url"
76
0
#define PREF_SB_MALWARE_ENABLED "browser.safebrowsing.malware.enabled"
77
0
#define PREF_SB_DOWNLOADS_ENABLED "browser.safebrowsing.downloads.enabled"
78
0
#define PREF_SB_DOWNLOADS_REMOTE_ENABLED "browser.safebrowsing.downloads.remote.enabled"
79
0
#define PREF_SB_DOWNLOADS_REMOTE_TIMEOUT "browser.safebrowsing.downloads.remote.timeout_ms"
80
0
#define PREF_DOWNLOAD_BLOCK_TABLE "urlclassifier.downloadBlockTable"
81
0
#define PREF_DOWNLOAD_ALLOW_TABLE "urlclassifier.downloadAllowTable"
82
83
// Preferences that are needed to action the verdict.
84
0
#define PREF_BLOCK_DANGEROUS            "browser.safebrowsing.downloads.remote.block_dangerous"
85
0
#define PREF_BLOCK_DANGEROUS_HOST       "browser.safebrowsing.downloads.remote.block_dangerous_host"
86
0
#define PREF_BLOCK_POTENTIALLY_UNWANTED "browser.safebrowsing.downloads.remote.block_potentially_unwanted"
87
0
#define PREF_BLOCK_UNCOMMON             "browser.safebrowsing.downloads.remote.block_uncommon"
88
89
// MOZ_LOG=ApplicationReputation:5
90
mozilla::LazyLogModule ApplicationReputationService::prlog("ApplicationReputation");
91
0
#define LOG(args) MOZ_LOG(ApplicationReputationService::prlog, mozilla::LogLevel::Debug, args)
92
#define LOG_ENABLED() MOZ_LOG_TEST(ApplicationReputationService::prlog, mozilla::LogLevel::Debug)
93
94
enum class LookupType { AllowlistOnly, BlocklistOnly, BothLists };
95
96
class PendingDBLookup;
97
98
// A single use class private to ApplicationReputationService encapsulating an
99
// nsIApplicationReputationQuery and an nsIApplicationReputationCallback. Once
100
// created by ApplicationReputationService, it is guaranteed to call mCallback.
101
// This class is private to ApplicationReputationService.
102
class PendingLookup final : public nsIStreamListener,
103
                            public nsITimerCallback,
104
                            public nsIObserver,
105
                            public nsSupportsWeakReference
106
{
107
public:
108
  NS_DECL_ISUPPORTS
109
  NS_DECL_NSIREQUESTOBSERVER
110
  NS_DECL_NSISTREAMLISTENER
111
  NS_DECL_NSITIMERCALLBACK
112
  NS_DECL_NSIOBSERVER
113
114
  // Constructor and destructor.
115
  PendingLookup(nsIApplicationReputationQuery* aQuery,
116
                nsIApplicationReputationCallback* aCallback);
117
118
  // Start the lookup. The lookup may have 2 parts: local and remote. In the
119
  // local lookup, PendingDBLookups are created to query the local allow and
120
  // blocklists for various URIs associated with this downloaded file. In the
121
  // event that no results are found, a remote lookup is sent to the Application
122
  // Reputation server.
123
  nsresult StartLookup();
124
125
private:
126
  ~PendingLookup();
127
128
  friend class PendingDBLookup;
129
130
  // Telemetry states.
131
  // Status of the remote response (valid or not).
132
  enum SERVER_RESPONSE_TYPES {
133
    SERVER_RESPONSE_VALID = 0,
134
    SERVER_RESPONSE_FAILED = 1,
135
    SERVER_RESPONSE_INVALID = 2,
136
  };
137
138
  // Number of blocklist and allowlist hits we have seen.
139
  uint32_t mBlocklistCount;
140
  uint32_t mAllowlistCount;
141
142
  // The query containing metadata about the downloaded file.
143
  nsCOMPtr<nsIApplicationReputationQuery> mQuery;
144
145
  // The callback with which to report the verdict.
146
  nsCOMPtr<nsIApplicationReputationCallback> mCallback;
147
148
  // An array of strings created from certificate information used to whitelist
149
  // the downloaded file.
150
  nsTArray<nsCString> mAllowlistSpecs;
151
  // The source URI of the download (i.e. final URI after any redirects).
152
  nsTArray<nsCString> mAnylistSpecs;
153
  // The referrer and possibly any redirects.
154
  nsTArray<nsCString> mBlocklistSpecs;
155
156
  // When we started this query
157
  TimeStamp mStartTime;
158
159
  // The channel used to talk to the remote lookup server
160
  nsCOMPtr<nsIChannel> mChannel;
161
162
  // Timer to abort this lookup if it takes too long
163
  nsCOMPtr<nsITimer> mTimeoutTimer;
164
165
  // A protocol buffer for storing things we need in the remote request. We
166
  // store the resource chain (redirect information) as well as signature
167
  // information extracted using the Windows Authenticode API, if the binary is
168
  // signed.
169
  ClientDownloadRequest mRequest;
170
171
  // The response from the application reputation query. This is read in chunks
172
  // as part of our nsIStreamListener implementation and may contain embedded
173
  // NULLs.
174
  nsCString mResponse;
175
176
  // The clock records the start time of a remote lookup request, used by telemetry.
177
  PRIntervalTime mTelemetryRemoteRequestStartMs;
178
179
  // Returns the type of download binary for the file.
180
  ClientDownloadRequest::DownloadType GetDownloadType(const nsACString& aFilename);
181
182
  // Clean up and call the callback. PendingLookup must not be used after this
183
  // function is called.
184
  nsresult OnComplete(bool shouldBlock, nsresult rv,
185
    uint32_t verdict = nsIApplicationReputationService::VERDICT_SAFE);
186
187
  // Wrapper function for nsIStreamListener.onStopRequest to make it easy to
188
  // guarantee calling the callback
189
  nsresult OnStopRequestInternal(nsIRequest *aRequest,
190
                                 nsISupports *aContext,
191
                                 nsresult aResult,
192
                                 bool* aShouldBlock,
193
                                 uint32_t* aVerdict);
194
195
  // Return the hex-encoded hash of the whole URI.
196
  nsresult GetSpecHash(nsACString& aSpec, nsACString& hexEncodedHash);
197
198
  // Strip url parameters, fragments, and user@pass fields from the URI spec
199
  // using nsIURL. Hash data URIs and return blob URIs unfiltered.
200
  nsresult GetStrippedSpec(nsIURI* aUri, nsACString& spec);
201
202
  // Escape '/' and '%' in certificate attribute values.
203
  nsCString EscapeCertificateAttribute(const nsACString& aAttribute);
204
205
  // Escape ':' in fingerprint values.
206
  nsCString EscapeFingerprint(const nsACString& aAttribute);
207
208
  // Generate whitelist strings for the given certificate pair from the same
209
  // certificate chain.
210
  nsresult GenerateWhitelistStringsForPair(
211
    nsIX509Cert* certificate, nsIX509Cert* issuer);
212
213
  // Generate whitelist strings for the given certificate chain, which starts
214
  // with the signer and may go all the way to the root cert.
215
  nsresult GenerateWhitelistStringsForChain(
216
    const ClientDownloadRequest_CertificateChain& aChain);
217
218
  // For signed binaries, generate strings of the form:
219
  // http://sb-ssl.google.com/safebrowsing/csd/certificate/
220
  //   <issuer_cert_sha1_fingerprint>[/CN=<cn>][/O=<org>][/OU=<unit>]
221
  // for each (cert, issuer) pair in each chain of certificates that is
222
  // associated with the binary.
223
  nsresult GenerateWhitelistStrings();
224
225
  // Parse the XPCOM certificate lists and stick them into the protocol buffer
226
  // version.
227
  nsresult ParseCertificates(nsIArray* aSigArray);
228
229
  // Adds the redirects to mBlocklistSpecs to be looked up.
230
  nsresult AddRedirects(nsIArray* aRedirects);
231
232
  // Helper function to ensure that we call PendingLookup::LookupNext or
233
  // PendingLookup::OnComplete.
234
  nsresult DoLookupInternal();
235
236
  // Looks up all the URIs that may be responsible for allowlisting or
237
  // blocklisting the downloaded file. These URIs may include whitelist strings
238
  // generated by certificates verifying the binary as well as the target URI
239
  // from which the file was downloaded.
240
  nsresult LookupNext();
241
242
  // Sends a query to the remote application reputation service. Returns NS_OK
243
  // on success.
244
  nsresult SendRemoteQuery();
245
246
  // Helper function to ensure that we always call the callback.
247
  nsresult SendRemoteQueryInternal();
248
};
249
250
// A single-use class for looking up a single URI in the safebrowsing DB. This
251
// class is private to PendingLookup.
252
class PendingDBLookup final : public nsIUrlClassifierCallback
253
{
254
public:
255
  NS_DECL_ISUPPORTS
256
  NS_DECL_NSIURLCLASSIFIERCALLBACK
257
258
  // Constructor and destructor
259
  explicit PendingDBLookup(PendingLookup* aPendingLookup);
260
261
  // Look up the given URI in the safebrowsing DBs, optionally on both the allow
262
  // list and the blocklist. If there is a match, call
263
  // PendingLookup::OnComplete. Otherwise, call PendingLookup::LookupNext.
264
  nsresult LookupSpec(const nsACString& aSpec, const LookupType& aLookupType);
265
266
private:
267
  ~PendingDBLookup();
268
269
  // The download appeared on the allowlist, blocklist, or no list (and thus
270
  // could trigger a remote query.
271
  enum LIST_TYPES {
272
    ALLOW_LIST = 0,
273
    BLOCK_LIST = 1,
274
    NO_LIST = 2,
275
  };
276
277
  nsCString mSpec;
278
  LookupType mLookupType;
279
  RefPtr<PendingLookup> mPendingLookup;
280
  nsresult LookupSpecInternal(const nsACString& aSpec);
281
};
282
283
NS_IMPL_ISUPPORTS(PendingDBLookup,
284
                  nsIUrlClassifierCallback)
285
286
PendingDBLookup::PendingDBLookup(PendingLookup* aPendingLookup) :
287
  mLookupType(LookupType::BothLists),
288
  mPendingLookup(aPendingLookup)
289
0
{
290
0
  LOG(("Created pending DB lookup [this = %p]", this));
291
0
}
292
293
PendingDBLookup::~PendingDBLookup()
294
0
{
295
0
  LOG(("Destroying pending DB lookup [this = %p]", this));
296
0
  mPendingLookup = nullptr;
297
0
}
298
299
nsresult
300
PendingDBLookup::LookupSpec(const nsACString& aSpec,
301
                            const LookupType& aLookupType)
302
0
{
303
0
  LOG(("Checking principal %s [this=%p]", aSpec.Data(), this));
304
0
  mSpec = aSpec;
305
0
  mLookupType = aLookupType;
306
0
  nsresult rv = LookupSpecInternal(aSpec);
307
0
  if (NS_FAILED(rv)) {
308
0
    nsAutoCString errorName;
309
0
    mozilla::GetErrorName(rv, errorName);
310
0
    LOG(("Error in LookupSpecInternal() [rv = %s, this = %p]",
311
0
         errorName.get(), this));
312
0
    return mPendingLookup->LookupNext(); // ignore this lookup and move to next
313
0
  }
314
0
  // LookupSpecInternal has called nsIUrlClassifierCallback.lookup, which is
315
0
  // guaranteed to call HandleEvent.
316
0
  return rv;
317
0
}
318
319
nsresult
320
PendingDBLookup::LookupSpecInternal(const nsACString& aSpec)
321
0
{
322
0
  nsresult rv;
323
0
324
0
  nsCOMPtr<nsIURI> uri;
325
0
  nsCOMPtr<nsIIOService> ios = do_GetService(NS_IOSERVICE_CONTRACTID, &rv);
326
0
  rv = ios->NewURI(aSpec, nullptr, nullptr, getter_AddRefs(uri));
327
0
  NS_ENSURE_SUCCESS(rv, rv);
328
0
329
0
  OriginAttributes attrs;
330
0
  nsCOMPtr<nsIPrincipal> principal =
331
0
    BasePrincipal::CreateCodebasePrincipal(uri, attrs);
332
0
  if (!principal) {
333
0
    return NS_ERROR_FAILURE;
334
0
  }
335
0
336
0
  // Check local lists to see if the URI has already been whitelisted or
337
0
  // blacklisted.
338
0
  LOG(("Checking DB service for principal %s [this = %p]", mSpec.get(), this));
339
0
  nsCOMPtr<nsIUrlClassifierDBService> dbService =
340
0
    do_GetService(NS_URLCLASSIFIERDBSERVICE_CONTRACTID, &rv);
341
0
  NS_ENSURE_SUCCESS(rv, rv);
342
0
343
0
  nsAutoCString tables;
344
0
  nsAutoCString allowlist;
345
0
  Preferences::GetCString(PREF_DOWNLOAD_ALLOW_TABLE, allowlist);
346
0
  if ((mLookupType != LookupType::BlocklistOnly) && !allowlist.IsEmpty()) {
347
0
    tables.Append(allowlist);
348
0
  }
349
0
  nsAutoCString blocklist;
350
0
  Preferences::GetCString(PREF_DOWNLOAD_BLOCK_TABLE, blocklist);
351
0
  if ((mLookupType != LookupType::AllowlistOnly) && !blocklist.IsEmpty()) {
352
0
    if (!tables.IsEmpty()) {
353
0
      tables.Append(',');
354
0
    }
355
0
    tables.Append(blocklist);
356
0
  }
357
0
  return dbService->Lookup(principal, tables, this);
358
0
}
359
360
NS_IMETHODIMP
361
PendingDBLookup::HandleEvent(const nsACString& tables)
362
0
{
363
0
  // HandleEvent is guaranteed to call either:
364
0
  // 1) PendingLookup::OnComplete if the URL matches the blocklist, or
365
0
  // 2) PendingLookup::LookupNext if the URL does not match the blocklist.
366
0
  // Blocklisting trumps allowlisting.
367
0
  nsAutoCString blockList;
368
0
  Preferences::GetCString(PREF_DOWNLOAD_BLOCK_TABLE, blockList);
369
0
  if ((mLookupType != LookupType::AllowlistOnly) && FindInReadable(blockList, tables)) {
370
0
    mPendingLookup->mBlocklistCount++;
371
0
    Accumulate(mozilla::Telemetry::APPLICATION_REPUTATION_LOCAL, BLOCK_LIST);
372
0
    LOG(("Found principal %s on blocklist [this = %p]", mSpec.get(), this));
373
0
    return mPendingLookup->OnComplete(true, NS_OK,
374
0
      nsIApplicationReputationService::VERDICT_DANGEROUS);
375
0
  }
376
0
377
0
  nsAutoCString allowList;
378
0
  Preferences::GetCString(PREF_DOWNLOAD_ALLOW_TABLE, allowList);
379
0
  if ((mLookupType != LookupType::BlocklistOnly) && FindInReadable(allowList, tables)) {
380
0
    mPendingLookup->mAllowlistCount++;
381
0
    Accumulate(mozilla::Telemetry::APPLICATION_REPUTATION_LOCAL, ALLOW_LIST);
382
0
    LOG(("Found principal %s on allowlist [this = %p]", mSpec.get(), this));
383
0
    // Don't call onComplete, since blocklisting trumps allowlisting
384
0
    return mPendingLookup->LookupNext();
385
0
  }
386
0
387
0
  LOG(("Didn't find principal %s on any list [this = %p]", mSpec.get(), this));
388
0
  Accumulate(mozilla::Telemetry::APPLICATION_REPUTATION_LOCAL, NO_LIST);
389
0
  return mPendingLookup->LookupNext();
390
0
}
391
392
NS_IMPL_ISUPPORTS(PendingLookup,
393
                  nsIStreamListener,
394
                  nsIRequestObserver,
395
                  nsIObserver,
396
                  nsISupportsWeakReference)
397
398
PendingLookup::PendingLookup(nsIApplicationReputationQuery* aQuery,
399
                             nsIApplicationReputationCallback* aCallback) :
400
  mBlocklistCount(0),
401
  mAllowlistCount(0),
402
  mQuery(aQuery),
403
  mCallback(aCallback)
404
0
{
405
0
  LOG(("Created pending lookup [this = %p]", this));
406
0
}
407
408
PendingLookup::~PendingLookup()
409
0
{
410
0
  LOG(("Destroying pending lookup [this = %p]", this));
411
0
}
412
413
static const char* const kBinaryFileExtensions[] = {
414
    // Extracted from the "File Type Policies" Chrome extension
415
    //".001",
416
    //".7z",
417
    //".ace",
418
    //".action", // Mac script
419
    //".ad", // Windows
420
    ".ade", // MS Access
421
    ".adp", // MS Access
422
    ".apk", // Android package
423
    ".app", // Executable application
424
    ".applescript",
425
    ".application", // MS ClickOnce
426
    ".appref-ms", // MS ClickOnce
427
    //".arc",
428
    //".arj",
429
    ".as", // Mac archive
430
    ".asp", // Windows Server script
431
    ".asx", // Windows Media Player
432
    //".b64",
433
    //".balz",
434
    ".bas", // Basic script
435
    ".bash", // Linux shell
436
    ".bat", // Windows shell
437
    //".bhx",
438
    //".bin",
439
    ".btapp", // uTorrent and Transmission
440
    ".btinstall", // uTorrent and Transmission
441
    ".btkey", // uTorrent and Transmission
442
    ".btsearch", // uTorrent and Transmission
443
    ".btskin", // uTorrent and Transmission
444
    ".bz", // Linux archive (bzip)
445
    ".bz2", // Linux archive (bzip2)
446
    ".bzip2", // Linux archive (bzip2)
447
    ".cab", // Windows archive
448
    ".cdr", // Mac disk image
449
    ".cfg", // Windows
450
    ".chi", // Windows Help
451
    ".chm", // Windows Help
452
    ".class", // Java
453
    ".cmd", // Windows executable
454
    ".com", // Windows executable
455
    ".command", // Mac script
456
    ".cpgz", // Mac archive
457
    //".cpio",
458
    ".cpl", // Windows executable
459
    ".crt", // Windows signed certificate
460
    ".crx", // Chrome extensions
461
    ".csh", // Linux shell
462
    ".dart", // Mac disk image
463
    ".dc42", // Apple DiskCopy Image
464
    ".deb", // Linux package
465
    ".dex", // Android
466
    ".dhtml", // HTML
467
    ".dhtm", // HTML
468
    ".dht", // HTML
469
    ".diskcopy42", // Apple DiskCopy Image
470
    ".dll", // Windows executable
471
    ".dmg", // Mac disk image
472
    ".dmgpart", // Mac disk image
473
    //".docb", // MS Office
474
    //".docm", // MS Word
475
    //".docx", // MS Word
476
    //".dotm", // MS Word
477
    //".dott", // MS Office
478
    ".drv", // Windows driver
479
    ".dvdr", // Mac Disk image
480
    ".efi", // Firmware
481
    ".eml", // MS Outlook
482
    ".exe", // Windows executable
483
    //".fat",
484
    ".fon", // Windows font
485
    ".fxp", // MS FoxPro
486
    ".gadget", // Windows
487
    ".grp", // Windows
488
    ".gz", // Linux archive (gzip)
489
    ".gzip", // Linux archive (gzip)
490
    ".hfs", // Mac disk image
491
    ".hlp", // Windows Help
492
    ".hqx", // Mac archive
493
    ".hta", // HTML trusted application
494
    ".htm",
495
    ".html",
496
    ".htt", // MS HTML template
497
    ".img", // Mac disk image
498
    ".imgpart", // Mac disk image
499
    ".inf", // Windows installer
500
    ".ini", // Generic config file
501
    ".ins", // IIS config
502
    //".inx", // InstallShield
503
    ".iso", // CD image
504
    ".isp", // IIS config
505
    //".isu", // InstallShield
506
    ".jar", // Java
507
    ".jnlp", // Java
508
    //".job", // Windows
509
    ".js", // JavaScript script
510
    ".jse", // JScript
511
    ".ksh", // Linux shell
512
    //".lha",
513
    ".lnk", // Windows
514
    ".local", // Windows
515
    //".lpaq1",
516
    //".lpaq5",
517
    //".lpaq8",
518
    //".lzh",
519
    //".lzma",
520
    ".mad", // MS Access
521
    ".maf", // MS Access
522
    ".mag", // MS Access
523
    ".mam", // MS Access
524
    ".manifest", // Windows
525
    ".maq", // MS Access
526
    ".mar", // MS Access
527
    ".mas", // MS Access
528
    ".mat", // MS Access
529
    ".mau", // Media attachment
530
    ".mav", // MS Access
531
    ".maw", // MS Access
532
    ".mda", // MS Access
533
    ".mdb", // MS Access
534
    ".mde", // MS Access
535
    ".mdt", // MS Access
536
    ".mdw", // MS Access
537
    ".mdz", // MS Access
538
    ".mht", // MS HTML
539
    ".mhtml", // MS HTML
540
    ".mim", // MS Mail
541
    ".mmc", // MS Office
542
    ".mof", // Windows
543
    ".mpkg", // Mac installer
544
    ".msc", // Windows executable
545
    ".msg", // MS Outlook
546
    ".msh", // Windows shell
547
    ".msh1", // Windows shell
548
    ".msh1xml", // Windows shell
549
    ".msh2", // Windows shell
550
    ".msh2xml", // Windows shell
551
    ".mshxml", // Windows
552
    ".msi", // Windows installer
553
    ".msp", // Windows installer
554
    ".mst", // Windows installer
555
    ".ndif", // Mac disk image
556
    //".ntfs", // 7z
557
    ".ocx", // ActiveX
558
    ".ops", // MS Office
559
    ".osas", // AppleScript
560
    ".osax", // AppleScript
561
    //".out", // Linux binary
562
    ".oxt", // OpenOffice extension, can execute arbitrary code
563
    //".paf", // PortableApps package
564
    //".paq8f",
565
    //".paq8jd",
566
    //".paq8l",
567
    //".paq8o",
568
    ".partial", // Downloads
569
    ".pax", // Mac archive
570
    ".pcd", // Microsoft Visual Test
571
    ".pdf", // Adobe Acrobat
572
    //".pea",
573
    ".pet", // Linux package
574
    ".pif", // Windows
575
    ".pkg", // Mac installer
576
    ".pl", // Perl script
577
    ".plg", // MS Visual Studio
578
    //".potx", // MS PowerPoint
579
    //".ppam", // MS PowerPoint
580
    //".ppsx", // MS PowerPoint
581
    //".pptm", // MS PowerPoint
582
    //".pptx", // MS PowerPoint
583
    ".prf", // MS Outlook
584
    ".prg", // Windows
585
    ".ps1", // Windows shell
586
    ".ps1xml", // Windows shell
587
    ".ps2", // Windows shell
588
    ".ps2xml", // Windows shell
589
    ".psc1", // Windows shell
590
    ".psc2", // Windows shell
591
    ".pst", // MS Outlook
592
    ".pup", // Linux package
593
    ".py", // Python script
594
    ".pyc", // Python binary
595
    ".pyw", // Python GUI
596
    //".quad",
597
    //".r00",
598
    //".r01",
599
    //".r02",
600
    //".r03",
601
    //".r04",
602
    //".r05",
603
    //".r06",
604
    //".r07",
605
    //".r08",
606
    //".r09",
607
    //".r10",
608
    //".r11",
609
    //".r12",
610
    //".r13",
611
    //".r14",
612
    //".r15",
613
    //".r16",
614
    //".r17",
615
    //".r18",
616
    //".r19",
617
    //".r20",
618
    //".r21",
619
    //".r22",
620
    //".r23",
621
    //".r24",
622
    //".r25",
623
    //".r26",
624
    //".r27",
625
    //".r28",
626
    //".r29",
627
    //".rar",
628
    ".rb", // Ruby script
629
    ".reg", // Windows Registry
630
    ".rels", // MS Office
631
    //".rgs", // Windows Registry
632
    ".rpm", // Linux package
633
    //".rtf", // MS Office
634
    //".run", // Linux shell
635
    ".scf", // Windows shell
636
    ".scpt", // AppleScript
637
    ".scptd", // AppleScript
638
    ".scr", // Windows
639
    ".sct", // Windows shell
640
    ".search-ms", // Windows
641
    ".seplugin", // AppleScript
642
    ".settingcontent-ms", // Windows settings
643
    ".sh", // Linux shell
644
    ".shar", // Linux shell
645
    ".shb", // Windows
646
    ".shs", // Windows shell
647
    ".shtml", // HTML
648
    ".shtm", // HTML
649
    ".sht", // HTML
650
    //".sldm", // MS PowerPoint
651
    //".sldx", // MS PowerPoint
652
    ".slk", // MS Excel
653
    ".slp", // Linux package
654
    ".smi", // Mac disk image
655
    ".sparsebundle", // Mac disk image
656
    ".sparseimage", // Mac disk image
657
    ".spl", // Adobe Flash
658
    //".squashfs",
659
    ".svg",
660
    ".swf", // Adobe Flash
661
    ".swm", // Windows Imaging
662
    ".sys", // Windows
663
    ".tar", // Linux archive
664
    ".taz", // Linux archive (bzip2)
665
    ".tbz", // Linux archive (bzip2)
666
    ".tbz2", // Linux archive (bzip2)
667
    ".tcsh", // Linux shell
668
    ".tgz", // Linux archive (gzip)
669
    //".toast", // Roxio disk image
670
    ".torrent", // Bittorrent
671
    ".tpz", // Linux archive (gzip)
672
    ".txz", // Linux archive (xz)
673
    ".tz", // Linux archive (gzip)
674
    //".u3p", // U3 Smart Apps
675
    ".udf", // MS Excel
676
    ".udif", // Mac disk image
677
    ".url", // Windows
678
    //".uu",
679
    //".uue",
680
    ".vb", // Visual Basic script
681
    ".vbe", // Visual Basic script
682
    ".vbs", // Visual Basic script
683
    //".vbscript", // Visual Basic script
684
    ".vdx", // MS Visio
685
    ".vhd", // Windows virtual hard drive
686
    ".vhdx", // Windows virtual hard drive
687
    ".vmdk", // VMware virtual disk
688
    ".vsd", // MS Visio
689
    ".vsdm", // MS Visio
690
    ".vsdx", // MS Visio
691
    ".vsmacros", // MS Visual Studio
692
    ".vss", // MS Visio
693
    ".vssm", // MS Visio
694
    ".vssx", // MS Visio
695
    ".vst", // MS Visio
696
    ".vstm", // MS Visio
697
    ".vstx", // MS Visio
698
    ".vsw", // MS Visio
699
    ".vsx", // MS Visio
700
    ".vtx", // MS Visio
701
    ".website",  // Windows
702
    ".wim", // Windows Imaging
703
    //".workflow", // Mac Automator
704
    //".wrc", // FreeArc archive
705
    ".ws", // Windows script
706
    ".wsc", // Windows script
707
    ".wsf", // Windows script
708
    ".wsh", // Windows script
709
    ".xar", // MS Excel
710
    ".xbap", // XAML Browser Application
711
    ".xhtml",
712
    ".xhtm",
713
    ".xht",
714
    ".xip", // Mac archive
715
    //".xlsm", // MS Excel
716
    //".xlsx", // MS Excel
717
    //".xltm", // MS Excel
718
    //".xltx", // MS Excel
719
    ".xml",
720
    ".xnk", // MS Exchange
721
    ".xrm-ms", // Windows
722
    ".xsl", // XML Stylesheet
723
    //".xxe",
724
    ".xz", // Linux archive (xz)
725
    ".z", // InstallShield
726
#ifdef XP_WIN // disable on Mac/Linux, see 1167493
727
    ".zip", // Generic archive
728
#endif
729
    ".zipx", // WinZip
730
    //".zpaq",
731
};
732
733
static const char* const kDmgFileExtensions[] = {
734
    ".cdr",
735
    ".dart",
736
    ".dc42",
737
    ".diskcopy42",
738
    ".dmg",
739
    ".dmgpart",
740
    ".dvdr",
741
    ".img",
742
    ".imgpart",
743
    ".iso",
744
    ".ndif",
745
    ".smi",
746
    ".sparsebundle",
747
    ".sparseimage",
748
    ".toast",
749
    ".udif",
750
};
751
752
static const char* const kRarFileExtensions[] = {
753
    ".r00",
754
    ".r01",
755
    ".r02",
756
    ".r03",
757
    ".r04",
758
    ".r05",
759
    ".r06",
760
    ".r07",
761
    ".r08",
762
    ".r09",
763
    ".r10",
764
    ".r11",
765
    ".r12",
766
    ".r13",
767
    ".r14",
768
    ".r15",
769
    ".r16",
770
    ".r17",
771
    ".r18",
772
    ".r19",
773
    ".r20",
774
    ".r21",
775
    ".r22",
776
    ".r23",
777
    ".r24",
778
    ".r25",
779
    ".r26",
780
    ".r27",
781
    ".r28",
782
    ".r29",
783
    ".rar",
784
};
785
786
static const char* const kZipFileExtensions[] = {
787
    ".zip", // Generic archive
788
    ".zipx", // WinZip
789
};
790
791
// Returns true if the file extension matches one in the given array.
792
static bool
793
IsFileType(const nsACString& aFilename, const char* const aFileExtensions[],
794
           const size_t aLength)
795
0
{
796
0
  for (size_t i = 0; i < aLength; ++i) {
797
0
    if (StringEndsWith(aFilename, nsDependentCString(aFileExtensions[i]))) {
798
0
      return true;
799
0
    }
800
0
  }
801
0
  return false;
802
0
}
803
804
ClientDownloadRequest::DownloadType
805
0
PendingLookup::GetDownloadType(const nsACString& aFilename) {
806
0
  MOZ_ASSERT(IsFileType(aFilename, kBinaryFileExtensions,
807
0
                        ArrayLength(kBinaryFileExtensions)));
808
0
809
0
  // From https://cs.chromium.org/chromium/src/chrome/common/safe_browsing/download_protection_util.cc?l=17
810
0
  if (StringEndsWith(aFilename, NS_LITERAL_CSTRING(".zip"))) {
811
0
    return ClientDownloadRequest::ZIPPED_EXECUTABLE;
812
0
  } else if (StringEndsWith(aFilename, NS_LITERAL_CSTRING(".apk"))) {
813
0
    return ClientDownloadRequest::ANDROID_APK;
814
0
  } else if (StringEndsWith(aFilename, NS_LITERAL_CSTRING(".app")) ||
815
0
             StringEndsWith(aFilename, NS_LITERAL_CSTRING(".applescript")) ||
816
0
             StringEndsWith(aFilename, NS_LITERAL_CSTRING(".cdr")) ||
817
0
             StringEndsWith(aFilename, NS_LITERAL_CSTRING(".dart")) ||
818
0
             StringEndsWith(aFilename, NS_LITERAL_CSTRING(".dc42")) ||
819
0
             StringEndsWith(aFilename, NS_LITERAL_CSTRING(".diskcopy42")) ||
820
0
             StringEndsWith(aFilename, NS_LITERAL_CSTRING(".dmg")) ||
821
0
             StringEndsWith(aFilename, NS_LITERAL_CSTRING(".dmgpart")) ||
822
0
             StringEndsWith(aFilename, NS_LITERAL_CSTRING(".dvdr")) ||
823
0
             StringEndsWith(aFilename, NS_LITERAL_CSTRING(".img")) ||
824
0
             StringEndsWith(aFilename, NS_LITERAL_CSTRING(".imgpart")) ||
825
0
             StringEndsWith(aFilename, NS_LITERAL_CSTRING(".iso")) ||
826
0
             StringEndsWith(aFilename, NS_LITERAL_CSTRING(".mpkg")) ||
827
0
             StringEndsWith(aFilename, NS_LITERAL_CSTRING(".ndif")) ||
828
0
             StringEndsWith(aFilename, NS_LITERAL_CSTRING(".osas")) ||
829
0
             StringEndsWith(aFilename, NS_LITERAL_CSTRING(".osax")) ||
830
0
             StringEndsWith(aFilename, NS_LITERAL_CSTRING(".pkg")) ||
831
0
             StringEndsWith(aFilename, NS_LITERAL_CSTRING(".scpt")) ||
832
0
             StringEndsWith(aFilename, NS_LITERAL_CSTRING(".scptd")) ||
833
0
             StringEndsWith(aFilename, NS_LITERAL_CSTRING(".seplugin")) ||
834
0
             StringEndsWith(aFilename, NS_LITERAL_CSTRING(".smi")) ||
835
0
             StringEndsWith(aFilename, NS_LITERAL_CSTRING(".sparsebundle")) ||
836
0
             StringEndsWith(aFilename, NS_LITERAL_CSTRING(".sparseimage")) ||
837
0
             StringEndsWith(aFilename, NS_LITERAL_CSTRING(".toast")) ||
838
0
             StringEndsWith(aFilename, NS_LITERAL_CSTRING(".udif"))) {
839
0
    return ClientDownloadRequest::MAC_EXECUTABLE;
840
0
  }
841
0
842
0
  return ClientDownloadRequest::WIN_EXECUTABLE; // default to Windows binaries
843
0
}
844
845
nsresult
846
PendingLookup::LookupNext()
847
0
{
848
0
  // We must call LookupNext or SendRemoteQuery upon return.
849
0
  // Look up all of the URLs that could allow or block this download.
850
0
  // Blocklist first.
851
0
852
0
  // If any of mAnylistSpecs or mBlocklistSpecs matched the blocklist,
853
0
  // go ahead and block.
854
0
  if (mBlocklistCount > 0) {
855
0
    return OnComplete(true, NS_OK,
856
0
                      nsIApplicationReputationService::VERDICT_DANGEROUS);
857
0
  }
858
0
859
0
  int index = mAnylistSpecs.Length() - 1;
860
0
  nsCString spec;
861
0
  if (index >= 0) {
862
0
    // Check the source URI only.
863
0
    spec = mAnylistSpecs[index];
864
0
    mAnylistSpecs.RemoveElementAt(index);
865
0
    RefPtr<PendingDBLookup> lookup(new PendingDBLookup(this));
866
0
    return lookup->LookupSpec(spec, LookupType::BothLists);
867
0
  }
868
0
869
0
  index = mBlocklistSpecs.Length() - 1;
870
0
  if (index >= 0) {
871
0
    // Check the referrer and redirect chain.
872
0
    spec = mBlocklistSpecs[index];
873
0
    mBlocklistSpecs.RemoveElementAt(index);
874
0
    RefPtr<PendingDBLookup> lookup(new PendingDBLookup(this));
875
0
    return lookup->LookupSpec(spec, LookupType::BlocklistOnly);
876
0
  }
877
0
878
0
  // Now that we've looked up all of the URIs against the blocklist,
879
0
  // if any of mAnylistSpecs or mAllowlistSpecs matched the allowlist,
880
0
  // go ahead and pass.
881
0
  if (mAllowlistCount > 0) {
882
0
    return OnComplete(false, NS_OK);
883
0
  }
884
0
885
0
  // Only binary signatures remain.
886
0
  index = mAllowlistSpecs.Length() - 1;
887
0
  if (index >= 0) {
888
0
    spec = mAllowlistSpecs[index];
889
0
    LOG(("PendingLookup::LookupNext: checking %s on allowlist", spec.get()));
890
0
    mAllowlistSpecs.RemoveElementAt(index);
891
0
    RefPtr<PendingDBLookup> lookup(new PendingDBLookup(this));
892
0
    return lookup->LookupSpec(spec, LookupType::AllowlistOnly);
893
0
  }
894
0
895
0
  // Check whether or not the file is eligible for remote lookups.
896
0
  bool isBinaryFile = false;
897
0
  nsAutoCString fileName;
898
0
  nsresult rv = mQuery->GetSuggestedFileName(fileName);
899
0
  if (NS_SUCCEEDED(rv) && !fileName.IsEmpty()) {
900
0
    LOG(("Suggested filename: %s [this = %p]", fileName.get(), this));
901
0
    isBinaryFile = IsFileType(fileName, kBinaryFileExtensions,
902
0
                              ArrayLength(kBinaryFileExtensions));
903
0
    AccumulateCategorical(isBinaryFile ?
904
0
                          mozilla::Telemetry::LABELS_APPLICATION_REPUTATION_BINARY::BinaryFile :
905
0
                          mozilla::Telemetry::LABELS_APPLICATION_REPUTATION_BINARY::NonBinaryFile);
906
0
  } else {
907
0
    LOG(("No suggested filename [this = %p]", this));
908
0
    AccumulateCategorical(mozilla::Telemetry::LABELS_APPLICATION_REPUTATION_BINARY::MissingFilename);
909
0
  }
910
0
911
0
  if (IsFileType(fileName, kDmgFileExtensions,
912
0
                 ArrayLength(kDmgFileExtensions))) {
913
0
    AccumulateCategorical(mozilla::Telemetry::LABELS_APPLICATION_REPUTATION_BINARY_ARCHIVE::DmgFile);
914
0
  } else if (IsFileType(fileName, kRarFileExtensions,
915
0
                        ArrayLength(kRarFileExtensions))) {
916
0
    AccumulateCategorical(mozilla::Telemetry::LABELS_APPLICATION_REPUTATION_BINARY_ARCHIVE::RarFile);
917
0
  } else if (IsFileType(fileName, kZipFileExtensions,
918
0
                        ArrayLength(kZipFileExtensions))) {
919
0
    AccumulateCategorical(mozilla::Telemetry::LABELS_APPLICATION_REPUTATION_BINARY_ARCHIVE::ZipFile);
920
0
  } else if (isBinaryFile) {
921
0
    AccumulateCategorical(mozilla::Telemetry::LABELS_APPLICATION_REPUTATION_BINARY_ARCHIVE::OtherBinaryFile);
922
0
  }
923
0
924
0
  // There are no more URIs to check against local list. If the file is
925
0
  // not eligible for remote lookup, bail.
926
0
  if (!isBinaryFile) {
927
0
    LOG(("Not eligible for remote lookups [this=%p]", this));
928
0
    return OnComplete(false, NS_OK);
929
0
  }
930
0
931
0
  rv = SendRemoteQuery();
932
0
  if (NS_FAILED(rv)) {
933
0
    return OnComplete(false, rv);
934
0
  }
935
0
  return NS_OK;
936
0
}
937
938
nsCString
939
PendingLookup::EscapeCertificateAttribute(const nsACString& aAttribute)
940
0
{
941
0
  // Escape '/' because it's a field separator, and '%' because Chrome does
942
0
  nsCString escaped;
943
0
  escaped.SetCapacity(aAttribute.Length());
944
0
  for (unsigned int i = 0; i < aAttribute.Length(); ++i) {
945
0
    if (aAttribute.Data()[i] == '%') {
946
0
      escaped.AppendLiteral("%25");
947
0
    } else if (aAttribute.Data()[i] == '/') {
948
0
      escaped.AppendLiteral("%2F");
949
0
    } else if (aAttribute.Data()[i] == ' ') {
950
0
      escaped.AppendLiteral("%20");
951
0
    } else {
952
0
      escaped.Append(aAttribute.Data()[i]);
953
0
    }
954
0
  }
955
0
  return escaped;
956
0
}
957
958
nsCString
959
PendingLookup::EscapeFingerprint(const nsACString& aFingerprint)
960
0
{
961
0
  // Google's fingerprint doesn't have colons
962
0
  nsCString escaped;
963
0
  escaped.SetCapacity(aFingerprint.Length());
964
0
  for (unsigned int i = 0; i < aFingerprint.Length(); ++i) {
965
0
    if (aFingerprint.Data()[i] != ':') {
966
0
      escaped.Append(aFingerprint.Data()[i]);
967
0
    }
968
0
  }
969
0
  return escaped;
970
0
}
971
972
nsresult
973
PendingLookup::GenerateWhitelistStringsForPair(
974
  nsIX509Cert* certificate,
975
  nsIX509Cert* issuer)
976
0
{
977
0
  // The whitelist paths have format:
978
0
  // http://sb-ssl.google.com/safebrowsing/csd/certificate/<issuer_cert_fingerprint>[/CN=<cn>][/O=<org>][/OU=<unit>]
979
0
  // Any of CN, O, or OU may be omitted from the whitelist entry. Unfortunately
980
0
  // this is not publicly documented, but the Chrome implementation can be found
981
0
  // here:
982
0
  // https://code.google.com/p/chromium/codesearch#search/&q=GetCertificateWhitelistStrings
983
0
  nsCString whitelistString(
984
0
    "http://sb-ssl.google.com/safebrowsing/csd/certificate/");
985
0
986
0
  nsString fingerprint;
987
0
  nsresult rv = issuer->GetSha1Fingerprint(fingerprint);
988
0
  NS_ENSURE_SUCCESS(rv, rv);
989
0
  whitelistString.Append(
990
0
    EscapeFingerprint(NS_ConvertUTF16toUTF8(fingerprint)));
991
0
992
0
  nsString commonName;
993
0
  rv = certificate->GetCommonName(commonName);
994
0
  NS_ENSURE_SUCCESS(rv, rv);
995
0
  if (!commonName.IsEmpty()) {
996
0
    whitelistString.AppendLiteral("/CN=");
997
0
    whitelistString.Append(
998
0
      EscapeCertificateAttribute(NS_ConvertUTF16toUTF8(commonName)));
999
0
  }
1000
0
1001
0
  nsString organization;
1002
0
  rv = certificate->GetOrganization(organization);
1003
0
  NS_ENSURE_SUCCESS(rv, rv);
1004
0
  if (!organization.IsEmpty()) {
1005
0
    whitelistString.AppendLiteral("/O=");
1006
0
    whitelistString.Append(
1007
0
      EscapeCertificateAttribute(NS_ConvertUTF16toUTF8(organization)));
1008
0
  }
1009
0
1010
0
  nsString organizationalUnit;
1011
0
  rv = certificate->GetOrganizationalUnit(organizationalUnit);
1012
0
  NS_ENSURE_SUCCESS(rv, rv);
1013
0
  if (!organizationalUnit.IsEmpty()) {
1014
0
    whitelistString.AppendLiteral("/OU=");
1015
0
    whitelistString.Append(
1016
0
      EscapeCertificateAttribute(NS_ConvertUTF16toUTF8(organizationalUnit)));
1017
0
  }
1018
0
  LOG(("Whitelisting %s", whitelistString.get()));
1019
0
1020
0
  mAllowlistSpecs.AppendElement(whitelistString);
1021
0
  return NS_OK;
1022
0
}
1023
1024
nsresult
1025
PendingLookup::GenerateWhitelistStringsForChain(
1026
  const safe_browsing::ClientDownloadRequest_CertificateChain& aChain)
1027
0
{
1028
0
  // We need a signing certificate and an issuer to construct a whitelist
1029
0
  // entry.
1030
0
  if (aChain.element_size() < 2) {
1031
0
    return NS_OK;
1032
0
  }
1033
0
1034
0
  // Get the signer.
1035
0
  nsresult rv;
1036
0
  nsCOMPtr<nsIX509CertDB> certDB = do_GetService(NS_X509CERTDB_CONTRACTID, &rv);
1037
0
  NS_ENSURE_SUCCESS(rv, rv);
1038
0
1039
0
  nsCOMPtr<nsIX509Cert> signer;
1040
0
  nsDependentCSubstring signerDER(
1041
0
    const_cast<char *>(aChain.element(0).certificate().data()),
1042
0
    aChain.element(0).certificate().size());
1043
0
  rv = certDB->ConstructX509(signerDER, getter_AddRefs(signer));
1044
0
  NS_ENSURE_SUCCESS(rv, rv);
1045
0
1046
0
  for (int i = 1; i < aChain.element_size(); ++i) {
1047
0
    // Get the issuer.
1048
0
    nsCOMPtr<nsIX509Cert> issuer;
1049
0
    nsDependentCSubstring issuerDER(
1050
0
      const_cast<char *>(aChain.element(i).certificate().data()),
1051
0
      aChain.element(i).certificate().size());
1052
0
    rv = certDB->ConstructX509(issuerDER, getter_AddRefs(issuer));
1053
0
    NS_ENSURE_SUCCESS(rv, rv);
1054
0
1055
0
    rv = GenerateWhitelistStringsForPair(signer, issuer);
1056
0
    NS_ENSURE_SUCCESS(rv, rv);
1057
0
  }
1058
0
  return NS_OK;
1059
0
}
1060
1061
nsresult
1062
PendingLookup::GenerateWhitelistStrings()
1063
0
{
1064
0
  for (int i = 0; i < mRequest.signature().certificate_chain_size(); ++i) {
1065
0
    nsresult rv = GenerateWhitelistStringsForChain(
1066
0
      mRequest.signature().certificate_chain(i));
1067
0
    NS_ENSURE_SUCCESS(rv, rv);
1068
0
  }
1069
0
  return NS_OK;
1070
0
}
1071
1072
nsresult
1073
PendingLookup::AddRedirects(nsIArray* aRedirects)
1074
0
{
1075
0
  uint32_t length = 0;
1076
0
  aRedirects->GetLength(&length);
1077
0
  LOG(("ApplicationReputation: Got %u redirects", length));
1078
0
  nsCOMPtr<nsISimpleEnumerator> iter;
1079
0
  nsresult rv = aRedirects->Enumerate(getter_AddRefs(iter));
1080
0
  NS_ENSURE_SUCCESS(rv, rv);
1081
0
1082
0
  bool hasMoreRedirects = false;
1083
0
  rv = iter->HasMoreElements(&hasMoreRedirects);
1084
0
  NS_ENSURE_SUCCESS(rv, rv);
1085
0
1086
0
  while (hasMoreRedirects) {
1087
0
    nsCOMPtr<nsISupports> supports;
1088
0
    rv = iter->GetNext(getter_AddRefs(supports));
1089
0
    NS_ENSURE_SUCCESS(rv, rv);
1090
0
1091
0
    nsCOMPtr<nsIRedirectHistoryEntry> redirectEntry = do_QueryInterface(supports, &rv);
1092
0
    NS_ENSURE_SUCCESS(rv, rv);
1093
0
1094
0
    nsCOMPtr<nsIPrincipal> principal;
1095
0
    rv = redirectEntry->GetPrincipal(getter_AddRefs(principal));
1096
0
    NS_ENSURE_SUCCESS(rv, rv);
1097
0
1098
0
    nsCOMPtr<nsIURI> uri;
1099
0
    rv = principal->GetURI(getter_AddRefs(uri));
1100
0
    NS_ENSURE_SUCCESS(rv, rv);
1101
0
1102
0
    // Add the spec to our list of local lookups. The most recent redirect is
1103
0
    // the last element.
1104
0
    nsCString spec;
1105
0
    rv = GetStrippedSpec(uri, spec);
1106
0
    NS_ENSURE_SUCCESS(rv, rv);
1107
0
    mBlocklistSpecs.AppendElement(spec);
1108
0
    LOG(("ApplicationReputation: Appending redirect %s\n", spec.get()));
1109
0
1110
0
    // Store the redirect information in the remote request.
1111
0
    ClientDownloadRequest_Resource* resource = mRequest.add_resources();
1112
0
    resource->set_url(spec.get());
1113
0
    resource->set_type(ClientDownloadRequest::DOWNLOAD_REDIRECT);
1114
0
1115
0
    rv = iter->HasMoreElements(&hasMoreRedirects);
1116
0
    NS_ENSURE_SUCCESS(rv, rv);
1117
0
  }
1118
0
  return NS_OK;
1119
0
}
1120
1121
nsresult
1122
PendingLookup::StartLookup()
1123
0
{
1124
0
  mStartTime = TimeStamp::Now();
1125
0
  nsresult rv = DoLookupInternal();
1126
0
  if (NS_FAILED(rv)) {
1127
0
    return OnComplete(false, NS_OK);
1128
0
  }
1129
0
  return rv;
1130
0
}
1131
1132
nsresult
1133
PendingLookup::GetSpecHash(nsACString& aSpec, nsACString& hexEncodedHash)
1134
0
{
1135
0
  nsresult rv;
1136
0
1137
0
  nsCOMPtr<nsICryptoHash> cryptoHash =
1138
0
    do_CreateInstance("@mozilla.org/security/hash;1", &rv);
1139
0
  NS_ENSURE_SUCCESS(rv, rv);
1140
0
  rv = cryptoHash->Init(nsICryptoHash::SHA256);
1141
0
  NS_ENSURE_SUCCESS(rv, rv);
1142
0
1143
0
  rv = cryptoHash->Update(reinterpret_cast<const uint8_t*>(aSpec.BeginReading()),
1144
0
                          aSpec.Length());
1145
0
  NS_ENSURE_SUCCESS(rv, rv);
1146
0
1147
0
  nsAutoCString binaryHash;
1148
0
  rv = cryptoHash->Finish(false, binaryHash);
1149
0
  NS_ENSURE_SUCCESS(rv, rv);
1150
0
1151
0
  // This needs to match HexEncode() in Chrome's
1152
0
  // src/base/strings/string_number_conversions.cc
1153
0
  static const char* const hex = "0123456789ABCDEF";
1154
0
  hexEncodedHash.SetCapacity(2 * binaryHash.Length());
1155
0
  for (size_t i = 0; i < binaryHash.Length(); ++i) {
1156
0
    auto c = static_cast<unsigned char>(binaryHash[i]);
1157
0
    hexEncodedHash.Append(hex[(c >> 4) & 0x0F]);
1158
0
    hexEncodedHash.Append(hex[c & 0x0F]);
1159
0
  }
1160
0
1161
0
  return NS_OK;
1162
0
}
1163
1164
nsresult
1165
PendingLookup::GetStrippedSpec(nsIURI* aUri, nsACString& escaped)
1166
0
{
1167
0
  if (NS_WARN_IF(!aUri)) {
1168
0
    return NS_ERROR_INVALID_ARG;
1169
0
  }
1170
0
1171
0
  nsresult rv;
1172
0
  rv = aUri->GetScheme(escaped);
1173
0
  NS_ENSURE_SUCCESS(rv, rv);
1174
0
1175
0
  if (escaped.EqualsLiteral("blob")) {
1176
0
    aUri->GetSpec(escaped);
1177
0
    LOG(("PendingLookup::GetStrippedSpec(): blob URL left unstripped as '%s' [this = %p]",
1178
0
         PromiseFlatCString(escaped).get(), this));
1179
0
    return NS_OK;
1180
0
1181
0
  } else if (escaped.EqualsLiteral("data")) {
1182
0
    // Replace URI with "data:<everything before comma>,SHA256(<whole URI>)"
1183
0
    aUri->GetSpec(escaped);
1184
0
    int32_t comma = escaped.FindChar(',');
1185
0
    if (comma > -1 &&
1186
0
        static_cast<nsCString::size_type>(comma) < escaped.Length() - 1) {
1187
0
      MOZ_ASSERT(comma > 4, "Data URIs start with 'data:'");
1188
0
      nsAutoCString hexEncodedHash;
1189
0
      rv = GetSpecHash(escaped, hexEncodedHash);
1190
0
      if (NS_SUCCEEDED(rv)) {
1191
0
        escaped.Truncate(comma + 1);
1192
0
        escaped.Append(hexEncodedHash);
1193
0
      }
1194
0
    }
1195
0
1196
0
    LOG(("PendingLookup::GetStrippedSpec(): data URL stripped to '%s' [this = %p]",
1197
0
         PromiseFlatCString(escaped).get(), this));
1198
0
    return NS_OK;
1199
0
  }
1200
0
1201
0
  // If aURI is not an nsIURL, we do not want to check the lists or send a
1202
0
  // remote query.
1203
0
  nsCOMPtr<nsIURL> url = do_QueryInterface(aUri, &rv);
1204
0
  if (NS_FAILED(rv)) {
1205
0
    LOG(("PendingLookup::GetStrippedSpec(): scheme '%s' is not supported [this = %p]",
1206
0
         PromiseFlatCString(escaped).get(), this));
1207
0
    return rv;
1208
0
  }
1209
0
1210
0
  nsCString temp;
1211
0
  rv = url->GetHostPort(temp);
1212
0
  NS_ENSURE_SUCCESS(rv, rv);
1213
0
1214
0
  escaped.AppendLiteral("://");
1215
0
  escaped.Append(temp);
1216
0
1217
0
  rv = url->GetFilePath(temp);
1218
0
  NS_ENSURE_SUCCESS(rv, rv);
1219
0
1220
0
  // nsIUrl.filePath starts with '/'
1221
0
  escaped.Append(temp);
1222
0
1223
0
  LOG(("PendingLookup::GetStrippedSpec(): URL stripped to '%s' [this = %p]",
1224
0
       PromiseFlatCString(escaped).get(), this));
1225
0
  return NS_OK;
1226
0
}
1227
1228
nsresult
1229
PendingLookup::DoLookupInternal()
1230
0
{
1231
0
  // We want to check the target URI, its referrer, and associated redirects
1232
0
  // against the local lists.
1233
0
  nsCOMPtr<nsIURI> uri;
1234
0
  nsresult rv = mQuery->GetSourceURI(getter_AddRefs(uri));
1235
0
  NS_ENSURE_SUCCESS(rv, rv);
1236
0
1237
0
  nsCString sourceSpec;
1238
0
  rv = GetStrippedSpec(uri, sourceSpec);
1239
0
  NS_ENSURE_SUCCESS(rv, rv);
1240
0
1241
0
  mAnylistSpecs.AppendElement(sourceSpec);
1242
0
1243
0
  ClientDownloadRequest_Resource* resource = mRequest.add_resources();
1244
0
  resource->set_url(sourceSpec.get());
1245
0
  resource->set_type(ClientDownloadRequest::DOWNLOAD_URL);
1246
0
1247
0
  nsCOMPtr<nsIURI> referrer = nullptr;
1248
0
  rv = mQuery->GetReferrerURI(getter_AddRefs(referrer));
1249
0
  if (referrer) {
1250
0
    nsCString referrerSpec;
1251
0
    rv = GetStrippedSpec(referrer, referrerSpec);
1252
0
    NS_ENSURE_SUCCESS(rv, rv);
1253
0
    mBlocklistSpecs.AppendElement(referrerSpec);
1254
0
    resource->set_referrer(referrerSpec.get());
1255
0
  }
1256
0
  nsCOMPtr<nsIArray> redirects;
1257
0
  rv = mQuery->GetRedirects(getter_AddRefs(redirects));
1258
0
  if (redirects) {
1259
0
    AddRedirects(redirects);
1260
0
  } else {
1261
0
    LOG(("ApplicationReputation: Got no redirects [this=%p]", this));
1262
0
  }
1263
0
1264
0
  // Extract the signature and parse certificates so we can use it to check
1265
0
  // whitelists.
1266
0
  nsCOMPtr<nsIArray> sigArray;
1267
0
  rv = mQuery->GetSignatureInfo(getter_AddRefs(sigArray));
1268
0
  NS_ENSURE_SUCCESS(rv, rv);
1269
0
1270
0
  if (sigArray) {
1271
0
    rv = ParseCertificates(sigArray);
1272
0
    NS_ENSURE_SUCCESS(rv, rv);
1273
0
  }
1274
0
1275
0
  rv = GenerateWhitelistStrings();
1276
0
  NS_ENSURE_SUCCESS(rv, rv);
1277
0
1278
0
  // Start the call chain.
1279
0
  return LookupNext();
1280
0
}
1281
1282
nsresult
1283
PendingLookup::OnComplete(bool shouldBlock, nsresult rv, uint32_t verdict)
1284
0
{
1285
0
  MOZ_ASSERT(!shouldBlock ||
1286
0
             verdict != nsIApplicationReputationService::VERDICT_SAFE);
1287
0
1288
0
  if (NS_FAILED(rv)) {
1289
0
    nsAutoCString errorName;
1290
0
    mozilla::GetErrorName(rv, errorName);
1291
0
    LOG(("Failed sending remote query for application reputation "
1292
0
         "[rv = %s, this = %p]", errorName.get(), this));
1293
0
  }
1294
0
1295
0
  if (mTimeoutTimer) {
1296
0
    mTimeoutTimer->Cancel();
1297
0
    mTimeoutTimer = nullptr;
1298
0
  }
1299
0
1300
0
  Accumulate(mozilla::Telemetry::APPLICATION_REPUTATION_SHOULD_BLOCK,
1301
0
    shouldBlock);
1302
0
  double t = (TimeStamp::Now() - mStartTime).ToMilliseconds();
1303
0
  LOG(("Application Reputation verdict is %u, obtained in %f ms [this = %p]",
1304
0
       verdict, t, this));
1305
0
  if (shouldBlock) {
1306
0
    LOG(("Application Reputation check failed, blocking bad binary [this = %p]",
1307
0
        this));
1308
0
  } else {
1309
0
    LOG(("Application Reputation check passed [this = %p]", this));
1310
0
  }
1311
0
  nsresult res = mCallback->OnComplete(shouldBlock, rv, verdict);
1312
0
  return res;
1313
0
}
1314
1315
nsresult
1316
PendingLookup::ParseCertificates(nsIArray* aSigArray)
1317
0
{
1318
0
  // If we haven't been set for any reason, bail.
1319
0
  NS_ENSURE_ARG_POINTER(aSigArray);
1320
0
1321
0
  // Binaries may be signed by multiple chains of certificates. If there are no
1322
0
  // chains, the binary is unsigned (or we were unable to extract signature
1323
0
  // information on a non-Windows platform)
1324
0
  nsCOMPtr<nsISimpleEnumerator> chains;
1325
0
  nsresult rv = aSigArray->Enumerate(getter_AddRefs(chains));
1326
0
  NS_ENSURE_SUCCESS(rv, rv);
1327
0
1328
0
  bool hasMoreChains = false;
1329
0
  rv = chains->HasMoreElements(&hasMoreChains);
1330
0
  NS_ENSURE_SUCCESS(rv, rv);
1331
0
1332
0
  while (hasMoreChains) {
1333
0
    nsCOMPtr<nsISupports> chainSupports;
1334
0
    rv = chains->GetNext(getter_AddRefs(chainSupports));
1335
0
    NS_ENSURE_SUCCESS(rv, rv);
1336
0
1337
0
    nsCOMPtr<nsIX509CertList> certList = do_QueryInterface(chainSupports, &rv);
1338
0
    NS_ENSURE_SUCCESS(rv, rv);
1339
0
1340
0
    safe_browsing::ClientDownloadRequest_CertificateChain* certChain =
1341
0
      mRequest.mutable_signature()->add_certificate_chain();
1342
0
    nsCOMPtr<nsISimpleEnumerator> chainElt;
1343
0
    rv = certList->GetEnumerator(getter_AddRefs(chainElt));
1344
0
    NS_ENSURE_SUCCESS(rv, rv);
1345
0
1346
0
    // Each chain may have multiple certificates.
1347
0
    bool hasMoreCerts = false;
1348
0
    rv = chainElt->HasMoreElements(&hasMoreCerts);
1349
0
    while (hasMoreCerts) {
1350
0
      nsCOMPtr<nsISupports> certSupports;
1351
0
      rv = chainElt->GetNext(getter_AddRefs(certSupports));
1352
0
      NS_ENSURE_SUCCESS(rv, rv);
1353
0
1354
0
      nsCOMPtr<nsIX509Cert> cert = do_QueryInterface(certSupports, &rv);
1355
0
      NS_ENSURE_SUCCESS(rv, rv);
1356
0
1357
0
      uint8_t* data = nullptr;
1358
0
      uint32_t len = 0;
1359
0
      rv = cert->GetRawDER(&len, &data);
1360
0
      NS_ENSURE_SUCCESS(rv, rv);
1361
0
1362
0
      // Add this certificate to the protobuf to send remotely.
1363
0
      certChain->add_element()->set_certificate(data, len);
1364
0
      free(data);
1365
0
1366
0
      rv = chainElt->HasMoreElements(&hasMoreCerts);
1367
0
      NS_ENSURE_SUCCESS(rv, rv);
1368
0
    }
1369
0
    rv = chains->HasMoreElements(&hasMoreChains);
1370
0
    NS_ENSURE_SUCCESS(rv, rv);
1371
0
  }
1372
0
  if (mRequest.signature().certificate_chain_size() > 0) {
1373
0
    mRequest.mutable_signature()->set_trusted(true);
1374
0
  }
1375
0
  return NS_OK;
1376
0
}
1377
1378
nsresult
1379
PendingLookup::SendRemoteQuery()
1380
0
{
1381
0
  nsresult rv = SendRemoteQueryInternal();
1382
0
  if (NS_FAILED(rv)) {
1383
0
    return OnComplete(false, rv);
1384
0
  }
1385
0
  // SendRemoteQueryInternal has fired off the query and we call OnComplete in
1386
0
  // the nsIStreamListener.onStopRequest.
1387
0
  return rv;
1388
0
}
1389
1390
nsresult
1391
PendingLookup::SendRemoteQueryInternal()
1392
0
{
1393
0
  // If we aren't supposed to do remote lookups, bail.
1394
0
  if (!Preferences::GetBool(PREF_SB_DOWNLOADS_REMOTE_ENABLED, false)) {
1395
0
    LOG(("Remote lookups are disabled [this = %p]", this));
1396
0
    return NS_ERROR_NOT_AVAILABLE;
1397
0
  }
1398
0
  // If the remote lookup URL is empty or absent, bail.
1399
0
  nsAutoCString serviceUrl;
1400
0
  NS_ENSURE_SUCCESS(Preferences::GetCString(PREF_SB_APP_REP_URL, serviceUrl),
1401
0
                    NS_ERROR_NOT_AVAILABLE);
1402
0
  if (serviceUrl.IsEmpty()) {
1403
0
    LOG(("Remote lookup URL is empty [this = %p]", this));
1404
0
    return NS_ERROR_NOT_AVAILABLE;
1405
0
  }
1406
0
1407
0
  // If the blocklist or allowlist is empty (so we couldn't do local lookups),
1408
0
  // bail
1409
0
  {
1410
0
    nsAutoCString table;
1411
0
    NS_ENSURE_SUCCESS(Preferences::GetCString(PREF_DOWNLOAD_BLOCK_TABLE,
1412
0
                                              table),
1413
0
                      NS_ERROR_NOT_AVAILABLE);
1414
0
    if (table.IsEmpty()) {
1415
0
      LOG(("Blocklist is empty [this = %p]", this));
1416
0
      return NS_ERROR_NOT_AVAILABLE;
1417
0
    }
1418
0
  }
1419
0
  {
1420
0
    nsAutoCString table;
1421
0
    NS_ENSURE_SUCCESS(Preferences::GetCString(PREF_DOWNLOAD_ALLOW_TABLE,
1422
0
                                              table),
1423
0
                      NS_ERROR_NOT_AVAILABLE);
1424
0
    if (table.IsEmpty()) {
1425
0
      LOG(("Allowlist is empty [this = %p]", this));
1426
0
      return NS_ERROR_NOT_AVAILABLE;
1427
0
    }
1428
0
  }
1429
0
1430
0
  LOG(("Sending remote query for application reputation [this = %p]",
1431
0
       this));
1432
0
  // We did not find a local result, so fire off the query to the
1433
0
  // application reputation service.
1434
0
  nsCOMPtr<nsIURI> uri;
1435
0
  nsresult rv;
1436
0
  rv = mQuery->GetSourceURI(getter_AddRefs(uri));
1437
0
  NS_ENSURE_SUCCESS(rv, rv);
1438
0
  nsCString spec;
1439
0
  rv = GetStrippedSpec(uri, spec);
1440
0
  NS_ENSURE_SUCCESS(rv, rv);
1441
0
  mRequest.set_url(spec.get());
1442
0
1443
0
  uint32_t fileSize;
1444
0
  rv = mQuery->GetFileSize(&fileSize);
1445
0
  NS_ENSURE_SUCCESS(rv, rv);
1446
0
  mRequest.set_length(fileSize);
1447
0
  // We have no way of knowing whether or not a user initiated the
1448
0
  // download. Set it to true to lessen the chance of false positives.
1449
0
  mRequest.set_user_initiated(true);
1450
0
1451
0
  nsCString locale;
1452
0
  rv = LocaleService::GetInstance()->GetAppLocaleAsLangTag(locale);
1453
0
  NS_ENSURE_SUCCESS(rv, rv);
1454
0
  mRequest.set_locale(locale.get());
1455
0
  nsCString sha256Hash;
1456
0
  rv = mQuery->GetSha256Hash(sha256Hash);
1457
0
  NS_ENSURE_SUCCESS(rv, rv);
1458
0
  mRequest.mutable_digests()->set_sha256(sha256Hash.Data());
1459
0
  nsCString fileName;
1460
0
  rv = mQuery->GetSuggestedFileName(fileName);
1461
0
  NS_ENSURE_SUCCESS(rv, rv);
1462
0
  mRequest.set_file_basename(fileName.get());
1463
0
  mRequest.set_download_type(GetDownloadType(fileName));
1464
0
1465
0
  if (mRequest.signature().trusted()) {
1466
0
    LOG(("Got signed binary for remote application reputation check "
1467
0
         "[this = %p]", this));
1468
0
  } else {
1469
0
    LOG(("Got unsigned binary for remote application reputation check "
1470
0
         "[this = %p]", this));
1471
0
  }
1472
0
1473
0
  // Look for truncated hashes (see bug 1190020)
1474
0
  const auto originalHashLength = sha256Hash.Length();
1475
0
  if (originalHashLength == 0) {
1476
0
    AccumulateCategorical(mozilla::Telemetry::LABELS_APPLICATION_REPUTATION_HASH_LENGTH::OriginalHashEmpty);
1477
0
  } else if (originalHashLength < 32) {
1478
0
    AccumulateCategorical(mozilla::Telemetry::LABELS_APPLICATION_REPUTATION_HASH_LENGTH::OriginalHashTooShort);
1479
0
  } else if (originalHashLength > 32) {
1480
0
    AccumulateCategorical(mozilla::Telemetry::LABELS_APPLICATION_REPUTATION_HASH_LENGTH::OriginalHashTooLong);
1481
0
  } else if (!mRequest.has_digests()) {
1482
0
    AccumulateCategorical(mozilla::Telemetry::LABELS_APPLICATION_REPUTATION_HASH_LENGTH::MissingDigest);
1483
0
  } else if (!mRequest.digests().has_sha256()) {
1484
0
    AccumulateCategorical(mozilla::Telemetry::LABELS_APPLICATION_REPUTATION_HASH_LENGTH::MissingSha256);
1485
0
  } else if (mRequest.digests().sha256().size() != originalHashLength) {
1486
0
    AccumulateCategorical(mozilla::Telemetry::LABELS_APPLICATION_REPUTATION_HASH_LENGTH::InvalidSha256);
1487
0
  } else {
1488
0
    AccumulateCategorical(mozilla::Telemetry::LABELS_APPLICATION_REPUTATION_HASH_LENGTH::ValidHash);
1489
0
  }
1490
0
1491
0
  // Serialize the protocol buffer to a string. This can only fail if we are
1492
0
  // out of memory, or if the protocol buffer req is missing required fields
1493
0
  // (only the URL for now).
1494
0
  std::string serialized;
1495
0
  if (!mRequest.SerializeToString(&serialized)) {
1496
0
    return NS_ERROR_UNEXPECTED;
1497
0
  }
1498
0
  LOG(("Serialized protocol buffer [this = %p]: (length=%zu) %s", this,
1499
0
       serialized.length(), serialized.c_str()));
1500
0
1501
0
  // Set the input stream to the serialized protocol buffer
1502
0
  nsCOMPtr<nsIStringInputStream> sstream =
1503
0
    do_CreateInstance("@mozilla.org/io/string-input-stream;1", &rv);
1504
0
  NS_ENSURE_SUCCESS(rv, rv);
1505
0
1506
0
  rv = sstream->SetData(serialized.c_str(), serialized.length());
1507
0
  NS_ENSURE_SUCCESS(rv, rv);
1508
0
1509
0
  // Set up the channel to transmit the request to the service.
1510
0
  nsCOMPtr<nsIIOService> ios = do_GetService(NS_IOSERVICE_CONTRACTID, &rv);
1511
0
  rv = ios->NewChannel2(serviceUrl,
1512
0
                        nullptr,
1513
0
                        nullptr,
1514
0
                        nullptr, // aLoadingNode
1515
0
                        nsContentUtils::GetSystemPrincipal(),
1516
0
                        nullptr, // aTriggeringPrincipal
1517
0
                        nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
1518
0
                        nsIContentPolicy::TYPE_OTHER,
1519
0
                        getter_AddRefs(mChannel));
1520
0
  NS_ENSURE_SUCCESS(rv, rv);
1521
0
1522
0
  nsCOMPtr<nsILoadInfo> loadInfo = mChannel->GetLoadInfo();
1523
0
  if (loadInfo) {
1524
0
    mozilla::OriginAttributes attrs;
1525
0
    attrs.mFirstPartyDomain.AssignLiteral(NECKO_SAFEBROWSING_FIRST_PARTY_DOMAIN);
1526
0
    loadInfo->SetOriginAttributes(attrs);
1527
0
  }
1528
0
1529
0
  nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(mChannel, &rv));
1530
0
  NS_ENSURE_SUCCESS(rv, rv);
1531
0
  mozilla::Unused << httpChannel;
1532
0
1533
0
  // Upload the protobuf to the application reputation service.
1534
0
  nsCOMPtr<nsIUploadChannel2> uploadChannel = do_QueryInterface(mChannel, &rv);
1535
0
  NS_ENSURE_SUCCESS(rv, rv);
1536
0
1537
0
  rv = uploadChannel->ExplicitSetUploadStream(sstream,
1538
0
    NS_LITERAL_CSTRING("application/octet-stream"), serialized.size(),
1539
0
    NS_LITERAL_CSTRING("POST"), false);
1540
0
  NS_ENSURE_SUCCESS(rv, rv);
1541
0
1542
0
  uint32_t timeoutMs = Preferences::GetUint(PREF_SB_DOWNLOADS_REMOTE_TIMEOUT, 10000);
1543
0
  NS_NewTimerWithCallback(getter_AddRefs(mTimeoutTimer),
1544
0
                          this, timeoutMs, nsITimer::TYPE_ONE_SHOT);
1545
0
1546
0
  mTelemetryRemoteRequestStartMs = PR_IntervalNow();
1547
0
1548
0
  rv = mChannel->AsyncOpen2(this);
1549
0
  NS_ENSURE_SUCCESS(rv, rv);
1550
0
1551
0
  return NS_OK;
1552
0
}
1553
1554
NS_IMETHODIMP
1555
PendingLookup::Notify(nsITimer* aTimer)
1556
0
{
1557
0
  LOG(("Remote lookup timed out [this = %p]", this));
1558
0
  MOZ_ASSERT(aTimer == mTimeoutTimer);
1559
0
  Accumulate(mozilla::Telemetry::APPLICATION_REPUTATION_REMOTE_LOOKUP_TIMEOUT,
1560
0
    true);
1561
0
  mChannel->Cancel(NS_ERROR_NET_TIMEOUT);
1562
0
  mTimeoutTimer->Cancel();
1563
0
  return NS_OK;
1564
0
}
1565
1566
///////////////////////////////////////////////////////////////////////////////
1567
// nsIObserver implementation
1568
NS_IMETHODIMP
1569
PendingLookup::Observe(nsISupports *aSubject, const char *aTopic,
1570
                       const char16_t *aData)
1571
0
{
1572
0
  if (!strcmp(aTopic, "quit-application")) {
1573
0
    if (mTimeoutTimer) {
1574
0
      mTimeoutTimer->Cancel();
1575
0
      mTimeoutTimer = nullptr;
1576
0
    }
1577
0
    if (mChannel) {
1578
0
      mChannel->Cancel(NS_ERROR_ABORT);
1579
0
    }
1580
0
  }
1581
0
  return NS_OK;
1582
0
}
1583
1584
////////////////////////////////////////////////////////////////////////////////
1585
//// nsIStreamListener
1586
static nsresult
1587
AppendSegmentToString(nsIInputStream* inputStream,
1588
                      void *closure,
1589
                      const char *rawSegment,
1590
                      uint32_t toOffset,
1591
                      uint32_t count,
1592
0
                      uint32_t *writeCount) {
1593
0
  nsAutoCString* decodedData = static_cast<nsAutoCString*>(closure);
1594
0
  decodedData->Append(rawSegment, count);
1595
0
  *writeCount = count;
1596
0
  return NS_OK;
1597
0
}
1598
1599
NS_IMETHODIMP
1600
PendingLookup::OnDataAvailable(nsIRequest *aRequest,
1601
                               nsISupports *aContext,
1602
                               nsIInputStream *aStream,
1603
                               uint64_t offset,
1604
0
                               uint32_t count) {
1605
0
  uint32_t read;
1606
0
  return aStream->ReadSegments(AppendSegmentToString, &mResponse, count, &read);
1607
0
}
1608
1609
NS_IMETHODIMP
1610
PendingLookup::OnStartRequest(nsIRequest *aRequest,
1611
0
                              nsISupports *aContext) {
1612
0
  return NS_OK;
1613
0
}
1614
1615
NS_IMETHODIMP
1616
PendingLookup::OnStopRequest(nsIRequest *aRequest,
1617
                             nsISupports *aContext,
1618
0
                             nsresult aResult) {
1619
0
  NS_ENSURE_STATE(mCallback);
1620
0
1621
0
  bool shouldBlock = false;
1622
0
  uint32_t verdict = nsIApplicationReputationService::VERDICT_SAFE;
1623
0
1624
0
  if (aResult != NS_ERROR_NET_TIMEOUT) {
1625
0
    Accumulate(mozilla::Telemetry::APPLICATION_REPUTATION_REMOTE_LOOKUP_TIMEOUT,
1626
0
      false);
1627
0
1628
0
    MOZ_ASSERT(mTelemetryRemoteRequestStartMs > 0);
1629
0
    int32_t msecs =
1630
0
      PR_IntervalToMilliseconds(PR_IntervalNow() - mTelemetryRemoteRequestStartMs);
1631
0
1632
0
    MOZ_ASSERT(msecs >= 0);
1633
0
    mozilla::Telemetry::Accumulate(
1634
0
      mozilla::Telemetry::APPLICATION_REPUTATION_REMOTE_LOOKUP_RESPONSE_TIME, msecs);
1635
0
  }
1636
0
1637
0
  nsresult rv = OnStopRequestInternal(aRequest, aContext, aResult,
1638
0
                                      &shouldBlock, &verdict);
1639
0
  OnComplete(shouldBlock, rv, verdict);
1640
0
  return rv;
1641
0
}
1642
1643
nsresult
1644
PendingLookup::OnStopRequestInternal(nsIRequest *aRequest,
1645
                                     nsISupports *aContext,
1646
                                     nsresult aResult,
1647
                                     bool* aShouldBlock,
1648
0
                                     uint32_t* aVerdict) {
1649
0
  if (NS_FAILED(aResult)) {
1650
0
    Accumulate(mozilla::Telemetry::APPLICATION_REPUTATION_SERVER,
1651
0
      SERVER_RESPONSE_FAILED);
1652
0
    AccumulateCategorical(NSErrorToLabel(aResult));
1653
0
    return aResult;
1654
0
  }
1655
0
1656
0
  *aShouldBlock = false;
1657
0
  *aVerdict = nsIApplicationReputationService::VERDICT_SAFE;
1658
0
  nsresult rv;
1659
0
  nsCOMPtr<nsIHttpChannel> channel = do_QueryInterface(aRequest, &rv);
1660
0
  if (NS_FAILED(rv)) {
1661
0
    Accumulate(mozilla::Telemetry::APPLICATION_REPUTATION_SERVER,
1662
0
      SERVER_RESPONSE_FAILED);
1663
0
    AccumulateCategorical(
1664
0
      mozilla::Telemetry::LABELS_APPLICATION_REPUTATION_SERVER_2::FailGetChannel);
1665
0
    return rv;
1666
0
  }
1667
0
1668
0
  uint32_t status = 0;
1669
0
  rv = channel->GetResponseStatus(&status);
1670
0
  if (NS_FAILED(rv)) {
1671
0
    Accumulate(mozilla::Telemetry::APPLICATION_REPUTATION_SERVER,
1672
0
      SERVER_RESPONSE_FAILED);
1673
0
    AccumulateCategorical(
1674
0
      mozilla::Telemetry::LABELS_APPLICATION_REPUTATION_SERVER_2::FailGetResponse);
1675
0
    return rv;
1676
0
  }
1677
0
1678
0
  if (status != 200) {
1679
0
    Accumulate(mozilla::Telemetry::APPLICATION_REPUTATION_SERVER,
1680
0
      SERVER_RESPONSE_FAILED);
1681
0
    AccumulateCategorical(HTTPStatusToLabel(status));
1682
0
    return NS_ERROR_NOT_AVAILABLE;
1683
0
  }
1684
0
1685
0
  std::string buf(mResponse.Data(), mResponse.Length());
1686
0
  safe_browsing::ClientDownloadResponse response;
1687
0
  if (!response.ParseFromString(buf)) {
1688
0
    LOG(("Invalid protocol buffer response [this = %p]: %s", this, buf.c_str()));
1689
0
    Accumulate(mozilla::Telemetry::APPLICATION_REPUTATION_SERVER,
1690
0
                                   SERVER_RESPONSE_INVALID);
1691
0
    return NS_ERROR_CANNOT_CONVERT_DATA;
1692
0
  }
1693
0
1694
0
  Accumulate(mozilla::Telemetry::APPLICATION_REPUTATION_SERVER,
1695
0
    SERVER_RESPONSE_VALID);
1696
0
  AccumulateCategorical(
1697
0
    mozilla::Telemetry::LABELS_APPLICATION_REPUTATION_SERVER_2::ResponseValid);
1698
0
1699
0
  // Clamp responses 0-7, we only know about 0-4 for now.
1700
0
  Accumulate(mozilla::Telemetry::APPLICATION_REPUTATION_SERVER_VERDICT,
1701
0
    std::min<uint32_t>(response.verdict(), 7));
1702
0
  switch(response.verdict()) {
1703
0
    case safe_browsing::ClientDownloadResponse::DANGEROUS:
1704
0
      *aShouldBlock = Preferences::GetBool(PREF_BLOCK_DANGEROUS, true);
1705
0
      *aVerdict = nsIApplicationReputationService::VERDICT_DANGEROUS;
1706
0
      break;
1707
0
    case safe_browsing::ClientDownloadResponse::DANGEROUS_HOST:
1708
0
      *aShouldBlock = Preferences::GetBool(PREF_BLOCK_DANGEROUS_HOST, true);
1709
0
      *aVerdict = nsIApplicationReputationService::VERDICT_DANGEROUS_HOST;
1710
0
      break;
1711
0
    case safe_browsing::ClientDownloadResponse::POTENTIALLY_UNWANTED:
1712
0
      *aShouldBlock = Preferences::GetBool(PREF_BLOCK_POTENTIALLY_UNWANTED, false);
1713
0
      *aVerdict = nsIApplicationReputationService::VERDICT_POTENTIALLY_UNWANTED;
1714
0
      break;
1715
0
    case safe_browsing::ClientDownloadResponse::UNCOMMON:
1716
0
      *aShouldBlock = Preferences::GetBool(PREF_BLOCK_UNCOMMON, false);
1717
0
      *aVerdict = nsIApplicationReputationService::VERDICT_UNCOMMON;
1718
0
      break;
1719
0
    default:
1720
0
      // Treat everything else as safe
1721
0
      break;
1722
0
  }
1723
0
1724
0
  return NS_OK;
1725
0
}
1726
1727
NS_IMPL_ISUPPORTS(ApplicationReputationService,
1728
                  nsIApplicationReputationService)
1729
1730
ApplicationReputationService*
1731
  ApplicationReputationService::gApplicationReputationService = nullptr;
1732
1733
already_AddRefed<ApplicationReputationService>
1734
ApplicationReputationService::GetSingleton()
1735
0
{
1736
0
  if (!gApplicationReputationService) {
1737
0
    // Note: This is cleared in the new ApplicationReputationService destructor.
1738
0
    gApplicationReputationService = new ApplicationReputationService();
1739
0
  }
1740
0
  return do_AddRef(gApplicationReputationService);
1741
0
}
1742
1743
ApplicationReputationService::ApplicationReputationService()
1744
0
{
1745
0
  LOG(("Application reputation service started up"));
1746
0
}
1747
1748
0
ApplicationReputationService::~ApplicationReputationService() {
1749
0
  LOG(("Application reputation service shutting down"));
1750
0
  MOZ_ASSERT(gApplicationReputationService == this);
1751
0
  gApplicationReputationService = nullptr;
1752
0
}
1753
1754
NS_IMETHODIMP
1755
ApplicationReputationService::QueryReputation(
1756
    nsIApplicationReputationQuery* aQuery,
1757
0
    nsIApplicationReputationCallback* aCallback) {
1758
0
  LOG(("Starting application reputation check [query=%p]", aQuery));
1759
0
  NS_ENSURE_ARG_POINTER(aQuery);
1760
0
  NS_ENSURE_ARG_POINTER(aCallback);
1761
0
1762
0
  nsresult rv = QueryReputationInternal(aQuery, aCallback);
1763
0
  if (NS_FAILED(rv)) {
1764
0
    Accumulate(mozilla::Telemetry::APPLICATION_REPUTATION_SHOULD_BLOCK,
1765
0
      false);
1766
0
    aCallback->OnComplete(false, rv,
1767
0
                          nsIApplicationReputationService::VERDICT_SAFE);
1768
0
  }
1769
0
  return NS_OK;
1770
0
}
1771
1772
nsresult ApplicationReputationService::QueryReputationInternal(
1773
  nsIApplicationReputationQuery* aQuery,
1774
0
  nsIApplicationReputationCallback* aCallback) {
1775
0
  nsresult rv;
1776
0
  // If malware checks aren't enabled, don't query application reputation.
1777
0
  if (!Preferences::GetBool(PREF_SB_MALWARE_ENABLED, false)) {
1778
0
    return NS_ERROR_NOT_AVAILABLE;
1779
0
  }
1780
0
1781
0
  if (!Preferences::GetBool(PREF_SB_DOWNLOADS_ENABLED, false)) {
1782
0
    return NS_ERROR_NOT_AVAILABLE;
1783
0
  }
1784
0
1785
0
  nsCOMPtr<nsIURI> uri;
1786
0
  rv = aQuery->GetSourceURI(getter_AddRefs(uri));
1787
0
  NS_ENSURE_SUCCESS(rv, rv);
1788
0
  // Bail if the URI hasn't been set.
1789
0
  NS_ENSURE_STATE(uri);
1790
0
1791
0
  // Create a new pending lookup and start the call chain.
1792
0
  RefPtr<PendingLookup> lookup(new PendingLookup(aQuery, aCallback));
1793
0
  NS_ENSURE_STATE(lookup);
1794
0
1795
0
  // Add an observer for shutdown
1796
0
  nsCOMPtr<nsIObserverService> observerService =
1797
0
    mozilla::services::GetObserverService();
1798
0
  if (!observerService) {
1799
0
    return NS_ERROR_FAILURE;
1800
0
  }
1801
0
1802
0
  observerService->AddObserver(lookup, "quit-application", true);
1803
0
  return lookup->StartLookup();
1804
0
}