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