1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set sw=2 sts=2 ts=8 et 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/. */
7 #include "nsChannelClassifier.h"
9 #include "nsCharSeparatedTokenizer.h"
10 #include "nsICacheEntry.h"
11 #include "nsICachingChannel.h"
12 #include "nsIChannel.h"
13 #include "nsIIOService.h"
14 #include "nsIObserverService.h"
15 #include "nsIPermissionManager.h"
16 #include "nsIProtocolHandler.h"
17 #include "nsIScriptSecurityManager.h"
18 #include "nsISecureBrowserUI.h"
19 #include "nsISupportsPriority.h"
20 #include "nsNetUtil.h"
21 #include "nsXULAppAPI.h"
22 #include "nsQueryObject.h"
23 #include "nsIUrlClassifierDBService.h"
24 #include "nsIUrlClassifierFeature.h"
25 #include "nsPrintfCString.h"
27 #include "mozilla/Components.h"
28 #include "mozilla/ErrorNames.h"
29 #include "mozilla/Logging.h"
30 #include "mozilla/Preferences.h"
31 #include "mozilla/net/UrlClassifierCommon.h"
32 #include "mozilla/net/UrlClassifierFeatureFactory.h"
33 #include "mozilla/ClearOnShutdown.h"
34 #include "mozilla/Services.h"
40 // MOZ_LOG=nsChannelClassifier:5
42 static LazyLogModule
gChannelClassifierLog("nsChannelClassifier");
45 #define LOG(args) MOZ_LOG(gChannelClassifierLog, LogLevel::Info, args)
46 #define LOG_DEBUG(args) MOZ_LOG(gChannelClassifierLog, LogLevel::Debug, args)
47 #define LOG_WARN(args) MOZ_LOG(gChannelClassifierLog, LogLevel::Warning, args)
48 #define LOG_ENABLED() MOZ_LOG_TEST(gChannelClassifierLog, LogLevel::Info)
50 #define URLCLASSIFIER_SKIP_HOSTNAMES "urlclassifier.skipHostnames"
52 // Put CachedPrefs in anonymous namespace to avoid any collision from outside of
57 * It is not recommended to read from Preference everytime a channel is
59 * That is not fast and we should cache preference values and reuse them
61 class CachedPrefs final
{
63 static CachedPrefs
* GetInstance();
67 nsCString
GetSkipHostnames() const { return mSkipHostnames
; }
68 void SetSkipHostnames(const nsACString
& aHostnames
) {
69 mSkipHostnames
= aHostnames
;
73 friend class StaticAutoPtr
<CachedPrefs
>;
77 static void OnPrefsChange(const char* aPrefName
, CachedPrefs
*);
79 nsCString mSkipHostnames
;
81 static StaticAutoPtr
<CachedPrefs
> sInstance
;
84 StaticAutoPtr
<CachedPrefs
> CachedPrefs::sInstance
;
87 void CachedPrefs::OnPrefsChange(const char* aPref
, CachedPrefs
* aPrefs
) {
88 if (!strcmp(aPref
, URLCLASSIFIER_SKIP_HOSTNAMES
)) {
89 nsCString skipHostnames
;
90 Preferences::GetCString(URLCLASSIFIER_SKIP_HOSTNAMES
, skipHostnames
);
91 ToLowerCase(skipHostnames
);
92 aPrefs
->SetSkipHostnames(skipHostnames
);
96 void CachedPrefs::Init() {
97 Preferences::RegisterCallbackAndCall(CachedPrefs::OnPrefsChange
,
98 URLCLASSIFIER_SKIP_HOSTNAMES
, this);
102 CachedPrefs
* CachedPrefs::GetInstance() {
104 sInstance
= new CachedPrefs();
106 ClearOnShutdown(&sInstance
);
108 MOZ_ASSERT(sInstance
);
112 CachedPrefs::CachedPrefs() { MOZ_COUNT_CTOR(CachedPrefs
); }
114 CachedPrefs::~CachedPrefs() {
115 MOZ_COUNT_DTOR(CachedPrefs
);
117 Preferences::UnregisterCallback(CachedPrefs::OnPrefsChange
,
118 URLCLASSIFIER_SKIP_HOSTNAMES
, this);
121 } // anonymous namespace
123 NS_IMPL_ISUPPORTS(nsChannelClassifier
, nsIURIClassifierCallback
, nsIObserver
)
125 nsChannelClassifier::nsChannelClassifier(nsIChannel
* aChannel
)
126 : mIsAllowListed(false), mSuspendedChannel(false), mChannel(aChannel
) {
127 LOG_DEBUG(("nsChannelClassifier::nsChannelClassifier %p", this));
128 MOZ_ASSERT(mChannel
);
131 nsChannelClassifier::~nsChannelClassifier() {
132 LOG_DEBUG(("nsChannelClassifier::~nsChannelClassifier %p", this));
135 void nsChannelClassifier::Start() {
136 nsresult rv
= StartInternal();
138 // If we aren't getting a callback for any reason, assume a good verdict and
139 // make sure we resume the channel if necessary.
140 OnClassifyComplete(NS_OK
, NS_LITERAL_CSTRING(""), NS_LITERAL_CSTRING(""),
141 NS_LITERAL_CSTRING(""));
145 nsresult
nsChannelClassifier::StartInternal() {
146 // Should only be called in the parent process.
147 MOZ_ASSERT(XRE_IsParentProcess());
149 // Don't bother to run the classifier on a load that has already failed.
150 // (this might happen after a redirect)
152 mChannel
->GetStatus(&status
);
153 if (NS_FAILED(status
)) return status
;
155 // Don't bother to run the classifier on a cached load that was
156 // previously classified as good.
157 if (HasBeenClassified(mChannel
)) {
158 return NS_ERROR_UNEXPECTED
;
161 nsCOMPtr
<nsIURI
> uri
;
162 nsresult rv
= mChannel
->GetURI(getter_AddRefs(uri
));
163 NS_ENSURE_SUCCESS(rv
, rv
);
165 // Don't bother checking certain types of URIs.
166 bool isAbout
= false;
167 rv
= uri
->SchemeIs("about", &isAbout
);
168 NS_ENSURE_SUCCESS(rv
, rv
);
169 if (isAbout
) return NS_ERROR_UNEXPECTED
;
172 rv
= NS_URIChainHasFlags(uri
, nsIProtocolHandler::URI_DANGEROUS_TO_LOAD
,
174 NS_ENSURE_SUCCESS(rv
, rv
);
175 if (hasFlags
) return NS_ERROR_UNEXPECTED
;
177 rv
= NS_URIChainHasFlags(uri
, nsIProtocolHandler::URI_IS_LOCAL_FILE
,
179 NS_ENSURE_SUCCESS(rv
, rv
);
180 if (hasFlags
) return NS_ERROR_UNEXPECTED
;
182 rv
= NS_URIChainHasFlags(uri
, nsIProtocolHandler::URI_IS_UI_RESOURCE
,
184 NS_ENSURE_SUCCESS(rv
, rv
);
185 if (hasFlags
) return NS_ERROR_UNEXPECTED
;
187 rv
= NS_URIChainHasFlags(uri
, nsIProtocolHandler::URI_IS_LOCAL_RESOURCE
,
189 NS_ENSURE_SUCCESS(rv
, rv
);
190 if (hasFlags
) return NS_ERROR_UNEXPECTED
;
192 nsCString skipHostnames
= CachedPrefs::GetInstance()->GetSkipHostnames();
193 if (!skipHostnames
.IsEmpty()) {
194 LOG(("nsChannelClassifier[%p]:StartInternal whitelisted hostnames = %s",
195 this, skipHostnames
.get()));
196 if (IsHostnameWhitelisted(uri
, skipHostnames
)) {
197 return NS_ERROR_UNEXPECTED
;
201 nsCOMPtr
<nsIURIClassifier
> uriClassifier
=
202 do_GetService(NS_URICLASSIFIERSERVICE_CONTRACTID
, &rv
);
203 if (rv
== NS_ERROR_FACTORY_NOT_REGISTERED
|| rv
== NS_ERROR_NOT_AVAILABLE
) {
204 // no URI classifier, ignore this failure.
205 return NS_ERROR_NOT_AVAILABLE
;
207 NS_ENSURE_SUCCESS(rv
, rv
);
209 nsCOMPtr
<nsIScriptSecurityManager
> securityManager
=
210 do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID
, &rv
);
211 NS_ENSURE_SUCCESS(rv
, rv
);
213 nsCOMPtr
<nsIPrincipal
> principal
;
214 rv
= securityManager
->GetChannelURIPrincipal(mChannel
,
215 getter_AddRefs(principal
));
216 NS_ENSURE_SUCCESS(rv
, rv
);
220 nsCOMPtr
<nsIURI
> principalURI
;
221 principal
->GetURI(getter_AddRefs(principalURI
));
222 nsCString spec
= principalURI
->GetSpecOrDefault();
223 spec
.Truncate(std::min(spec
.Length(), UrlClassifierCommon::sMaxSpecLength
));
224 LOG(("nsChannelClassifier[%p]: Classifying principal %s on channel[%p]",
225 this, spec
.get(), mChannel
.get()));
227 // The classify is running in parent process, no need to give a valid event
229 rv
= uriClassifier
->Classify(principal
, nullptr, this, &expectCallback
);
234 if (expectCallback
) {
235 // Suspend the channel, it will be resumed when we get the classifier
237 rv
= mChannel
->Suspend();
239 // Some channels (including nsJSChannel) fail on Suspend. This
240 // shouldn't be fatal, but will prevent malware from being
241 // blocked on these channels.
242 LOG_WARN(("nsChannelClassifier[%p]: Couldn't suspend channel", this));
246 mSuspendedChannel
= true;
247 LOG_DEBUG(("nsChannelClassifier[%p]: suspended channel %p", this,
250 LOG(("nsChannelClassifier[%p]: not expecting callback", this));
251 return NS_ERROR_FAILURE
;
254 // Add an observer for shutdown
255 AddShutdownObserver();
259 bool nsChannelClassifier::IsHostnameWhitelisted(
260 nsIURI
* aUri
, const nsACString
& aWhitelisted
) {
262 nsresult rv
= aUri
->GetHost(host
);
263 if (NS_FAILED(rv
) || host
.IsEmpty()) {
268 nsCCharSeparatedTokenizer
tokenizer(aWhitelisted
, ',');
269 while (tokenizer
.hasMoreTokens()) {
270 const nsACString
& token
= tokenizer
.nextToken();
271 if (token
.Equals(host
)) {
272 LOG(("nsChannelClassifier[%p]:StartInternal skipping %s (whitelisted)",
281 // Note in the cache entry that this URL was classified, so that future
282 // cached loads don't need to be checked.
283 void nsChannelClassifier::MarkEntryClassified(nsresult status
) {
284 // Should only be called in the parent process.
285 MOZ_ASSERT(XRE_IsParentProcess());
287 // Don't cache tracking classifications because we support allowlisting.
288 if (UrlClassifierFeatureFactory::IsClassifierBlockingErrorCode(status
) ||
294 nsAutoCString errorName
;
295 GetErrorName(status
, errorName
);
296 nsCOMPtr
<nsIURI
> uri
;
297 mChannel
->GetURI(getter_AddRefs(uri
));
299 uri
->GetAsciiSpec(spec
);
300 spec
.Truncate(std::min(spec
.Length(), UrlClassifierCommon::sMaxSpecLength
));
301 LOG(("nsChannelClassifier::MarkEntryClassified[%s] %s", errorName
.get(),
305 nsCOMPtr
<nsICachingChannel
> cachingChannel
= do_QueryInterface(mChannel
);
306 if (!cachingChannel
) {
310 nsCOMPtr
<nsISupports
> cacheToken
;
311 cachingChannel
->GetCacheToken(getter_AddRefs(cacheToken
));
316 nsCOMPtr
<nsICacheEntry
> cacheEntry
= do_QueryInterface(cacheToken
);
321 cacheEntry
->SetMetaDataElement("necko:classified",
322 NS_SUCCEEDED(status
) ? "1" : nullptr);
325 bool nsChannelClassifier::HasBeenClassified(nsIChannel
* aChannel
) {
326 // Should only be called in the parent process.
327 MOZ_ASSERT(XRE_IsParentProcess());
329 nsCOMPtr
<nsICachingChannel
> cachingChannel
= do_QueryInterface(aChannel
);
330 if (!cachingChannel
) {
334 // Only check the tag if we are loading from the cache without
337 if (NS_FAILED(cachingChannel
->IsFromCache(&fromCache
)) || !fromCache
) {
341 nsCOMPtr
<nsISupports
> cacheToken
;
342 cachingChannel
->GetCacheToken(getter_AddRefs(cacheToken
));
347 nsCOMPtr
<nsICacheEntry
> cacheEntry
= do_QueryInterface(cacheToken
);
353 cacheEntry
->GetMetaDataElement("necko:classified", getter_Copies(tag
));
354 return tag
.EqualsLiteral("1");
358 nsresult
nsChannelClassifier::SendThreatHitReport(nsIChannel
* aChannel
,
359 const nsACString
& aProvider
,
360 const nsACString
& aList
,
361 const nsACString
& aFullHash
) {
362 NS_ENSURE_ARG_POINTER(aChannel
);
364 nsAutoCString
provider(aProvider
);
365 nsPrintfCString
reportEnablePref(
366 "browser.safebrowsing.provider.%s.dataSharing.enabled", provider
.get());
367 if (!Preferences::GetBool(reportEnablePref
.get(), false)) {
369 "nsChannelClassifier::SendThreatHitReport data sharing disabled for %s",
374 nsCOMPtr
<nsIURIClassifier
> uriClassifier
=
375 components::UrlClassifierDB::Service();
376 if (!uriClassifier
) {
377 return NS_ERROR_UNEXPECTED
;
381 uriClassifier
->SendThreatHitReport(aChannel
, aProvider
, aList
, aFullHash
);
382 NS_ENSURE_SUCCESS(rv
, rv
);
388 nsChannelClassifier::OnClassifyComplete(nsresult aErrorCode
,
389 const nsACString
& aList
,
390 const nsACString
& aProvider
,
391 const nsACString
& aFullHash
) {
392 // Should only be called in the parent process.
393 MOZ_ASSERT(XRE_IsParentProcess());
395 !UrlClassifierFeatureFactory::IsClassifierBlockingErrorCode(aErrorCode
));
397 if (mSuspendedChannel
) {
398 nsAutoCString errorName
;
399 if (LOG_ENABLED() && NS_FAILED(aErrorCode
)) {
400 GetErrorName(aErrorCode
, errorName
);
401 LOG(("nsChannelClassifier[%p]:OnClassifyComplete %s (suspended channel)",
402 this, errorName
.get()));
404 MarkEntryClassified(aErrorCode
);
406 if (NS_FAILED(aErrorCode
)) {
408 nsCOMPtr
<nsIURI
> uri
;
409 mChannel
->GetURI(getter_AddRefs(uri
));
410 nsCString spec
= uri
->GetSpecOrDefault();
412 std::min(spec
.Length(), UrlClassifierCommon::sMaxSpecLength
));
414 ("nsChannelClassifier[%p]: cancelling channel %p for %s "
415 "with error code %s",
416 this, mChannel
.get(), spec
.get(), errorName
.get()));
419 // Channel will be cancelled (page element blocked) due to Safe Browsing.
420 // Do update the security state of the document and fire a security
422 UrlClassifierCommon::SetBlockedContent(mChannel
, aErrorCode
, aList
,
423 aProvider
, aFullHash
);
425 if (aErrorCode
== NS_ERROR_MALWARE_URI
||
426 aErrorCode
== NS_ERROR_PHISHING_URI
||
427 aErrorCode
== NS_ERROR_UNWANTED_URI
||
428 aErrorCode
== NS_ERROR_HARMFUL_URI
) {
429 SendThreatHitReport(mChannel
, aProvider
, aList
, aFullHash
);
432 mChannel
->Cancel(aErrorCode
);
435 ("nsChannelClassifier[%p]: resuming channel[%p] from "
436 "OnClassifyComplete",
437 this, mChannel
.get()));
442 RemoveShutdownObserver();
447 void nsChannelClassifier::AddShutdownObserver() {
448 nsCOMPtr
<nsIObserverService
> observerService
=
449 mozilla::services::GetObserverService();
450 if (observerService
) {
451 observerService
->AddObserver(this, "profile-change-net-teardown", false);
455 void nsChannelClassifier::RemoveShutdownObserver() {
456 nsCOMPtr
<nsIObserverService
> observerService
=
457 mozilla::services::GetObserverService();
458 if (observerService
) {
459 observerService
->RemoveObserver(this, "profile-change-net-teardown");
463 ///////////////////////////////////////////////////////////////////////////////
464 // nsIObserver implementation
466 nsChannelClassifier::Observe(nsISupports
* aSubject
, const char* aTopic
,
467 const char16_t
* aData
) {
468 if (!strcmp(aTopic
, "profile-change-net-teardown")) {
469 // If we aren't getting a callback for any reason, make sure
470 // we resume the channel.
472 if (mChannel
&& mSuspendedChannel
) {
473 mSuspendedChannel
= false;
474 mChannel
->Cancel(NS_ERROR_ABORT
);
479 RemoveShutdownObserver();
488 } // namespace mozilla