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