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 "nsIObserverService.h"
14 #include "nsIProtocolHandler.h"
15 #include "nsIScriptSecurityManager.h"
16 #include "nsNetUtil.h"
17 #include "nsXULAppAPI.h"
18 #include "nsQueryObject.h"
19 #include "nsPrintfCString.h"
21 #include "mozilla/Components.h"
22 #include "mozilla/ErrorNames.h"
23 #include "mozilla/Logging.h"
24 #include "mozilla/Preferences.h"
25 #include "mozilla/net/UrlClassifierCommon.h"
26 #include "mozilla/net/UrlClassifierFeatureFactory.h"
27 #include "mozilla/ClearOnShutdown.h"
28 #include "mozilla/Services.h"
33 #define URLCLASSIFIER_EXCEPTION_HOSTNAMES "urlclassifier.skipHostnames"
35 // Put CachedPrefs in anonymous namespace to avoid any collision from outside of
40 * It is not recommended to read from Preference everytime a channel is
42 * That is not fast and we should cache preference values and reuse them
44 class CachedPrefs final
{
46 static CachedPrefs
* GetInstance();
50 nsCString
GetExceptionHostnames() const { return mExceptionHostnames
; }
51 void SetExceptionHostnames(const nsACString
& aHostnames
) {
52 mExceptionHostnames
= aHostnames
;
56 friend class StaticAutoPtr
<CachedPrefs
>;
60 static void OnPrefsChange(const char* aPrefName
, void*);
62 nsCString mExceptionHostnames
;
64 static StaticAutoPtr
<CachedPrefs
> sInstance
;
67 StaticAutoPtr
<CachedPrefs
> CachedPrefs::sInstance
;
70 void CachedPrefs::OnPrefsChange(const char* aPref
, void* aPrefs
) {
71 auto* prefs
= static_cast<CachedPrefs
*>(aPrefs
);
73 if (!strcmp(aPref
, URLCLASSIFIER_EXCEPTION_HOSTNAMES
)) {
74 nsCString exceptionHostnames
;
75 Preferences::GetCString(URLCLASSIFIER_EXCEPTION_HOSTNAMES
,
77 ToLowerCase(exceptionHostnames
);
78 prefs
->SetExceptionHostnames(exceptionHostnames
);
82 void CachedPrefs::Init() {
83 Preferences::RegisterCallbackAndCall(CachedPrefs::OnPrefsChange
,
84 URLCLASSIFIER_EXCEPTION_HOSTNAMES
, this);
88 CachedPrefs
* CachedPrefs::GetInstance() {
90 sInstance
= new CachedPrefs();
92 ClearOnShutdown(&sInstance
);
94 MOZ_ASSERT(sInstance
);
98 CachedPrefs::CachedPrefs() { MOZ_COUNT_CTOR(CachedPrefs
); }
100 CachedPrefs::~CachedPrefs() {
101 MOZ_COUNT_DTOR(CachedPrefs
);
103 Preferences::UnregisterCallback(CachedPrefs::OnPrefsChange
,
104 URLCLASSIFIER_EXCEPTION_HOSTNAMES
, this);
107 } // anonymous namespace
109 NS_IMPL_ISUPPORTS(nsChannelClassifier
, nsIURIClassifierCallback
, nsIObserver
)
111 nsChannelClassifier::nsChannelClassifier(nsIChannel
* aChannel
)
112 : mIsAllowListed(false), mSuspendedChannel(false), mChannel(aChannel
) {
113 UC_LOG_LEAK(("nsChannelClassifier::nsChannelClassifier [this=%p]", this));
114 MOZ_ASSERT(mChannel
);
117 nsChannelClassifier::~nsChannelClassifier() {
118 UC_LOG_LEAK(("nsChannelClassifier::~nsChannelClassifier [this=%p]", this));
121 void nsChannelClassifier::Start() {
122 nsresult rv
= StartInternal();
124 // If we aren't getting a callback for any reason, assume a good verdict and
125 // make sure we resume the channel if necessary.
126 OnClassifyComplete(NS_OK
, ""_ns
, ""_ns
, ""_ns
);
130 nsresult
nsChannelClassifier::StartInternal() {
131 // Should only be called in the parent process.
132 MOZ_ASSERT(XRE_IsParentProcess());
134 // Don't bother to run the classifier on a load that has already failed.
135 // (this might happen after a redirect)
137 mChannel
->GetStatus(&status
);
138 if (NS_FAILED(status
)) return status
;
140 // Don't bother to run the classifier on a cached load that was
141 // previously classified as good.
142 if (HasBeenClassified(mChannel
)) {
143 return NS_ERROR_UNEXPECTED
;
146 nsCOMPtr
<nsIURI
> uri
;
147 nsresult rv
= mChannel
->GetURI(getter_AddRefs(uri
));
148 NS_ENSURE_SUCCESS(rv
, rv
);
150 // Don't bother checking certain types of URIs.
151 if (uri
->SchemeIs("about")) {
152 return NS_ERROR_UNEXPECTED
;
156 rv
= NS_URIChainHasFlags(uri
, nsIProtocolHandler::URI_DANGEROUS_TO_LOAD
,
158 NS_ENSURE_SUCCESS(rv
, rv
);
159 if (hasFlags
) return NS_ERROR_UNEXPECTED
;
161 rv
= NS_URIChainHasFlags(uri
, nsIProtocolHandler::URI_IS_LOCAL_FILE
,
163 NS_ENSURE_SUCCESS(rv
, rv
);
164 if (hasFlags
) return NS_ERROR_UNEXPECTED
;
166 rv
= NS_URIChainHasFlags(uri
, nsIProtocolHandler::URI_IS_UI_RESOURCE
,
168 NS_ENSURE_SUCCESS(rv
, rv
);
169 if (hasFlags
) return NS_ERROR_UNEXPECTED
;
171 rv
= NS_URIChainHasFlags(uri
, nsIProtocolHandler::URI_IS_LOCAL_RESOURCE
,
173 NS_ENSURE_SUCCESS(rv
, rv
);
174 if (hasFlags
) return NS_ERROR_UNEXPECTED
;
176 nsCString exceptionHostnames
=
177 CachedPrefs::GetInstance()->GetExceptionHostnames();
178 if (!exceptionHostnames
.IsEmpty()) {
180 ("nsChannelClassifier::StartInternal - entitylisted hostnames = %s "
182 exceptionHostnames
.get(), this));
183 if (IsHostnameEntitylisted(uri
, exceptionHostnames
)) {
184 return NS_ERROR_UNEXPECTED
;
188 nsCOMPtr
<nsIURIClassifier
> uriClassifier
=
189 do_GetService(NS_URICLASSIFIERSERVICE_CONTRACTID
, &rv
);
190 if (rv
== NS_ERROR_FACTORY_NOT_REGISTERED
|| rv
== NS_ERROR_NOT_AVAILABLE
) {
191 // no URI classifier, ignore this failure.
192 return NS_ERROR_NOT_AVAILABLE
;
194 NS_ENSURE_SUCCESS(rv
, rv
);
196 nsCOMPtr
<nsIScriptSecurityManager
> securityManager
=
197 do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID
, &rv
);
198 NS_ENSURE_SUCCESS(rv
, rv
);
200 nsCOMPtr
<nsIPrincipal
> principal
;
201 rv
= securityManager
->GetChannelURIPrincipal(mChannel
,
202 getter_AddRefs(principal
));
203 NS_ENSURE_SUCCESS(rv
, rv
);
206 if (UC_LOG_ENABLED()) {
207 nsCOMPtr
<nsIURI
> principalURI
;
209 principal
->GetAsciiSpec(spec
);
210 spec
.Truncate(std::min(spec
.Length(), UrlClassifierCommon::sMaxSpecLength
));
212 ("nsChannelClassifier::StartInternal - classifying principal %s on "
213 "channel %p [this=%p]",
214 spec
.get(), mChannel
.get(), this));
216 // The classify is running in parent process, no need to give a valid event
218 rv
= uriClassifier
->Classify(principal
, this, &expectCallback
);
223 if (expectCallback
) {
224 // Suspend the channel, it will be resumed when we get the classifier
226 rv
= mChannel
->Suspend();
228 // Some channels (including nsJSChannel) fail on Suspend. This
229 // shouldn't be fatal, but will prevent malware from being
230 // blocked on these channels.
232 ("nsChannelClassifier::StartInternal - couldn't suspend channel "
238 mSuspendedChannel
= true;
240 ("nsChannelClassifier::StartInternal - suspended channel %p [this=%p]",
241 mChannel
.get(), this));
244 "nsChannelClassifier::StartInternal - not expecting callback [this=%p]",
246 return NS_ERROR_FAILURE
;
249 // Add an observer for shutdown
250 AddShutdownObserver();
254 bool nsChannelClassifier::IsHostnameEntitylisted(
255 nsIURI
* aUri
, const nsACString
& aEntitylisted
) {
257 nsresult rv
= aUri
->GetHost(host
);
258 if (NS_FAILED(rv
) || host
.IsEmpty()) {
263 for (const nsACString
& token
:
264 nsCCharSeparatedTokenizer(aEntitylisted
, ',').ToRange()) {
265 if (token
.Equals(host
)) {
267 ("nsChannelClassifier::StartInternal - skipping %s (entitylisted) "
277 // Note in the cache entry that this URL was classified, so that future
278 // cached loads don't need to be checked.
279 void nsChannelClassifier::MarkEntryClassified(nsresult status
) {
280 // Should only be called in the parent process.
281 MOZ_ASSERT(XRE_IsParentProcess());
283 // Don't cache tracking classifications because we support allowlisting.
284 if (UrlClassifierFeatureFactory::IsClassifierBlockingErrorCode(status
) ||
289 if (UC_LOG_ENABLED()) {
290 nsAutoCString errorName
;
291 GetErrorName(status
, errorName
);
292 nsCOMPtr
<nsIURI
> uri
;
293 mChannel
->GetURI(getter_AddRefs(uri
));
295 uri
->GetAsciiSpec(spec
);
296 spec
.Truncate(std::min(spec
.Length(), UrlClassifierCommon::sMaxSpecLength
));
298 ("nsChannelClassifier::MarkEntryClassified - result is %s "
299 "for uri %s [this=%p, channel=%p]",
300 errorName
.get(), spec
.get(), this, mChannel
.get()));
303 nsCOMPtr
<nsICachingChannel
> cachingChannel
= do_QueryInterface(mChannel
);
304 if (!cachingChannel
) {
308 nsCOMPtr
<nsISupports
> cacheToken
;
309 cachingChannel
->GetCacheToken(getter_AddRefs(cacheToken
));
314 nsCOMPtr
<nsICacheEntry
> cacheEntry
= do_QueryInterface(cacheToken
);
319 cacheEntry
->SetMetaDataElement("necko:classified",
320 NS_SUCCEEDED(status
) ? "1" : nullptr);
323 bool nsChannelClassifier::HasBeenClassified(nsIChannel
* aChannel
) {
324 // Should only be called in the parent process.
325 MOZ_ASSERT(XRE_IsParentProcess());
327 nsCOMPtr
<nsICachingChannel
> cachingChannel
= do_QueryInterface(aChannel
);
328 if (!cachingChannel
) {
332 // Only check the tag if we are loading from the cache without
335 if (NS_FAILED(cachingChannel
->IsFromCache(&fromCache
)) || !fromCache
) {
339 nsCOMPtr
<nsISupports
> cacheToken
;
340 cachingChannel
->GetCacheToken(getter_AddRefs(cacheToken
));
345 nsCOMPtr
<nsICacheEntry
> cacheEntry
= do_QueryInterface(cacheToken
);
351 cacheEntry
->GetMetaDataElement("necko:classified", getter_Copies(tag
));
352 return tag
.EqualsLiteral("1");
356 nsresult
nsChannelClassifier::SendThreatHitReport(nsIChannel
* aChannel
,
357 const nsACString
& aProvider
,
358 const nsACString
& aList
,
359 const nsACString
& aFullHash
) {
360 NS_ENSURE_ARG_POINTER(aChannel
);
362 nsAutoCString
provider(aProvider
);
363 nsPrintfCString
reportEnablePref(
364 "browser.safebrowsing.provider.%s.dataSharing.enabled", provider
.get());
365 if (!Preferences::GetBool(reportEnablePref
.get(), false)) {
367 ("nsChannelClassifier::SendThreatHitReport - data sharing disabled for "
373 nsCOMPtr
<nsIURIClassifier
> uriClassifier
=
374 components::UrlClassifierDB::Service();
375 if (!uriClassifier
) {
376 return NS_ERROR_UNEXPECTED
;
380 uriClassifier
->SendThreatHitReport(aChannel
, aProvider
, aList
, aFullHash
);
381 NS_ENSURE_SUCCESS(rv
, rv
);
387 nsChannelClassifier::OnClassifyComplete(nsresult aErrorCode
,
388 const nsACString
& aList
,
389 const nsACString
& aProvider
,
390 const nsACString
& aFullHash
) {
391 // Should only be called in the parent process.
392 MOZ_ASSERT(XRE_IsParentProcess());
394 !UrlClassifierFeatureFactory::IsClassifierBlockingErrorCode(aErrorCode
));
396 if (mSuspendedChannel
) {
397 MarkEntryClassified(aErrorCode
);
399 if (NS_FAILED(aErrorCode
)) {
400 if (UC_LOG_ENABLED()) {
401 nsAutoCString errorName
;
402 GetErrorName(aErrorCode
, errorName
);
404 nsCOMPtr
<nsIURI
> uri
;
405 mChannel
->GetURI(getter_AddRefs(uri
));
406 nsCString spec
= uri
->GetSpecOrDefault();
408 std::min(spec
.Length(), UrlClassifierCommon::sMaxSpecLength
));
410 ("nsChannelClassifier::OnClassifyComplete - cancelling channel %p "
412 "with error code %s [this=%p]",
413 mChannel
.get(), spec
.get(), errorName
.get(), this));
416 // Channel will be cancelled (page element blocked) due to Safe Browsing.
417 // Do update the security state of the document and fire a security
419 UrlClassifierCommon::SetBlockedContent(mChannel
, aErrorCode
, aList
,
420 aProvider
, aFullHash
);
422 if (aErrorCode
== NS_ERROR_MALWARE_URI
||
423 aErrorCode
== NS_ERROR_PHISHING_URI
||
424 aErrorCode
== NS_ERROR_UNWANTED_URI
||
425 aErrorCode
== NS_ERROR_HARMFUL_URI
) {
426 SendThreatHitReport(mChannel
, aProvider
, aList
, aFullHash
);
429 mChannel
->Cancel(aErrorCode
);
432 ("nsChannelClassifier::OnClassifyComplete - resuming channel %p "
434 mChannel
.get(), this));
439 RemoveShutdownObserver();
444 void nsChannelClassifier::AddShutdownObserver() {
445 nsCOMPtr
<nsIObserverService
> observerService
=
446 mozilla::services::GetObserverService();
447 if (observerService
) {
448 observerService
->AddObserver(this, "profile-change-net-teardown", false);
452 void nsChannelClassifier::RemoveShutdownObserver() {
453 nsCOMPtr
<nsIObserverService
> observerService
=
454 mozilla::services::GetObserverService();
455 if (observerService
) {
456 observerService
->RemoveObserver(this, "profile-change-net-teardown");
460 ///////////////////////////////////////////////////////////////////////////////
461 // nsIObserver implementation
463 nsChannelClassifier::Observe(nsISupports
* aSubject
, const char* aTopic
,
464 const char16_t
* aData
) {
465 if (!strcmp(aTopic
, "profile-change-net-teardown")) {
466 // If we aren't getting a callback for any reason, make sure
467 // we resume the channel.
469 if (mChannel
&& mSuspendedChannel
) {
470 mSuspendedChannel
= false;
471 mChannel
->Cancel(NS_ERROR_ABORT
);
476 RemoveShutdownObserver();
483 } // namespace mozilla