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