forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			483 lines
		
	
	
	
		
			14 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			483 lines
		
	
	
	
		
			14 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 | |
| /* vim: set sw=2 sts=2 ts=8 et tw=80 : */
 | |
| /* This Source Code Form is subject to the terms of the Mozilla Public
 | |
|  * License, v. 2.0. If a copy of the MPL was not distributed with this
 | |
|  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 | |
| 
 | |
| #include "nsChannelClassifier.h"
 | |
| 
 | |
| #include "nsCharSeparatedTokenizer.h"
 | |
| #include "nsICacheEntry.h"
 | |
| #include "nsICachingChannel.h"
 | |
| #include "nsIChannel.h"
 | |
| #include "nsIObserverService.h"
 | |
| #include "nsIProtocolHandler.h"
 | |
| #include "nsIScriptSecurityManager.h"
 | |
| #include "nsNetUtil.h"
 | |
| #include "nsXULAppAPI.h"
 | |
| #include "nsQueryObject.h"
 | |
| #include "nsPrintfCString.h"
 | |
| 
 | |
| #include "mozilla/Components.h"
 | |
| #include "mozilla/ErrorNames.h"
 | |
| #include "mozilla/Logging.h"
 | |
| #include "mozilla/Preferences.h"
 | |
| #include "mozilla/net/UrlClassifierCommon.h"
 | |
| #include "mozilla/net/UrlClassifierFeatureFactory.h"
 | |
| #include "mozilla/ClearOnShutdown.h"
 | |
| #include "mozilla/Services.h"
 | |
| 
 | |
| namespace mozilla {
 | |
| namespace net {
 | |
| 
 | |
| #define URLCLASSIFIER_EXCEPTION_HOSTNAMES "urlclassifier.skipHostnames"
 | |
| 
 | |
| // Put CachedPrefs in anonymous namespace to avoid any collision from outside of
 | |
| // this file.
 | |
| namespace {
 | |
| 
 | |
| /**
 | |
|  * It is not recommended to read from Preference everytime a channel is
 | |
|  * connected.
 | |
|  * That is not fast and we should cache preference values and reuse them
 | |
|  */
 | |
| class CachedPrefs final {
 | |
|  public:
 | |
|   static CachedPrefs* GetInstance();
 | |
| 
 | |
|   void Init();
 | |
| 
 | |
|   nsCString GetExceptionHostnames() const { return mExceptionHostnames; }
 | |
|   void SetExceptionHostnames(const nsACString& aHostnames) {
 | |
|     mExceptionHostnames = aHostnames;
 | |
|   }
 | |
| 
 | |
|  private:
 | |
|   friend class StaticAutoPtr<CachedPrefs>;
 | |
|   CachedPrefs();
 | |
|   ~CachedPrefs();
 | |
| 
 | |
|   static void OnPrefsChange(const char* aPrefName, void*);
 | |
| 
 | |
|   nsCString mExceptionHostnames;
 | |
| 
 | |
|   static StaticAutoPtr<CachedPrefs> sInstance;
 | |
| };
 | |
| 
 | |
| StaticAutoPtr<CachedPrefs> CachedPrefs::sInstance;
 | |
| 
 | |
| // static
 | |
| void CachedPrefs::OnPrefsChange(const char* aPref, void* aPrefs) {
 | |
|   auto* prefs = static_cast<CachedPrefs*>(aPrefs);
 | |
| 
 | |
|   if (!strcmp(aPref, URLCLASSIFIER_EXCEPTION_HOSTNAMES)) {
 | |
|     nsCString exceptionHostnames;
 | |
|     Preferences::GetCString(URLCLASSIFIER_EXCEPTION_HOSTNAMES,
 | |
|                             exceptionHostnames);
 | |
|     ToLowerCase(exceptionHostnames);
 | |
|     prefs->SetExceptionHostnames(exceptionHostnames);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void CachedPrefs::Init() {
 | |
|   Preferences::RegisterCallbackAndCall(CachedPrefs::OnPrefsChange,
 | |
|                                        URLCLASSIFIER_EXCEPTION_HOSTNAMES, this);
 | |
| }
 | |
| 
 | |
| // static
 | |
| CachedPrefs* CachedPrefs::GetInstance() {
 | |
|   if (!sInstance) {
 | |
|     sInstance = new CachedPrefs();
 | |
|     sInstance->Init();
 | |
|     ClearOnShutdown(&sInstance);
 | |
|   }
 | |
|   MOZ_ASSERT(sInstance);
 | |
|   return sInstance;
 | |
| }
 | |
| 
 | |
| CachedPrefs::CachedPrefs() { MOZ_COUNT_CTOR(CachedPrefs); }
 | |
| 
 | |
| CachedPrefs::~CachedPrefs() {
 | |
|   MOZ_COUNT_DTOR(CachedPrefs);
 | |
| 
 | |
|   Preferences::UnregisterCallback(CachedPrefs::OnPrefsChange,
 | |
|                                   URLCLASSIFIER_EXCEPTION_HOSTNAMES, this);
 | |
| }
 | |
| 
 | |
| }  // anonymous namespace
 | |
| 
 | |
| NS_IMPL_ISUPPORTS(nsChannelClassifier, nsIURIClassifierCallback, nsIObserver)
 | |
| 
 | |
| nsChannelClassifier::nsChannelClassifier(nsIChannel* aChannel)
 | |
|     : mIsAllowListed(false), mSuspendedChannel(false), mChannel(aChannel) {
 | |
|   UC_LOG_LEAK(("nsChannelClassifier::nsChannelClassifier [this=%p]", this));
 | |
|   MOZ_ASSERT(mChannel);
 | |
| }
 | |
| 
 | |
| nsChannelClassifier::~nsChannelClassifier() {
 | |
|   UC_LOG_LEAK(("nsChannelClassifier::~nsChannelClassifier [this=%p]", this));
 | |
| }
 | |
| 
 | |
| void nsChannelClassifier::Start() {
 | |
|   nsresult rv = StartInternal();
 | |
|   if (NS_FAILED(rv)) {
 | |
|     // If we aren't getting a callback for any reason, assume a good verdict and
 | |
|     // make sure we resume the channel if necessary.
 | |
|     OnClassifyComplete(NS_OK, ""_ns, ""_ns, ""_ns);
 | |
|   }
 | |
| }
 | |
| 
 | |
| nsresult nsChannelClassifier::StartInternal() {
 | |
|   // Should only be called in the parent process.
 | |
|   MOZ_ASSERT(XRE_IsParentProcess());
 | |
| 
 | |
|   // Don't bother to run the classifier on a load that has already failed.
 | |
|   // (this might happen after a redirect)
 | |
|   nsresult status;
 | |
|   mChannel->GetStatus(&status);
 | |
|   if (NS_FAILED(status)) return status;
 | |
| 
 | |
|   // Don't bother to run the classifier on a cached load that was
 | |
|   // previously classified as good.
 | |
|   if (HasBeenClassified(mChannel)) {
 | |
|     return NS_ERROR_UNEXPECTED;
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<nsIURI> uri;
 | |
|   nsresult rv = mChannel->GetURI(getter_AddRefs(uri));
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|   // Don't bother checking certain types of URIs.
 | |
|   if (uri->SchemeIs("about")) {
 | |
|     return NS_ERROR_UNEXPECTED;
 | |
|   }
 | |
| 
 | |
|   bool hasFlags;
 | |
|   rv = NS_URIChainHasFlags(uri, nsIProtocolHandler::URI_DANGEROUS_TO_LOAD,
 | |
|                            &hasFlags);
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
|   if (hasFlags) return NS_ERROR_UNEXPECTED;
 | |
| 
 | |
|   rv = NS_URIChainHasFlags(uri, nsIProtocolHandler::URI_IS_LOCAL_FILE,
 | |
|                            &hasFlags);
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
|   if (hasFlags) return NS_ERROR_UNEXPECTED;
 | |
| 
 | |
|   rv = NS_URIChainHasFlags(uri, nsIProtocolHandler::URI_IS_UI_RESOURCE,
 | |
|                            &hasFlags);
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
|   if (hasFlags) return NS_ERROR_UNEXPECTED;
 | |
| 
 | |
|   rv = NS_URIChainHasFlags(uri, nsIProtocolHandler::URI_IS_LOCAL_RESOURCE,
 | |
|                            &hasFlags);
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
|   if (hasFlags) return NS_ERROR_UNEXPECTED;
 | |
| 
 | |
|   nsCString exceptionHostnames =
 | |
|       CachedPrefs::GetInstance()->GetExceptionHostnames();
 | |
|   if (!exceptionHostnames.IsEmpty()) {
 | |
|     UC_LOG(
 | |
|         ("nsChannelClassifier::StartInternal - entitylisted hostnames = %s "
 | |
|          "[this=%p]",
 | |
|          exceptionHostnames.get(), this));
 | |
|     if (IsHostnameEntitylisted(uri, exceptionHostnames)) {
 | |
|       return NS_ERROR_UNEXPECTED;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<nsIURIClassifier> uriClassifier =
 | |
|       do_GetService(NS_URICLASSIFIERSERVICE_CONTRACTID, &rv);
 | |
|   if (rv == NS_ERROR_FACTORY_NOT_REGISTERED || rv == NS_ERROR_NOT_AVAILABLE) {
 | |
|     // no URI classifier, ignore this failure.
 | |
|     return NS_ERROR_NOT_AVAILABLE;
 | |
|   }
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|   nsCOMPtr<nsIScriptSecurityManager> securityManager =
 | |
|       do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv);
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|   nsCOMPtr<nsIPrincipal> principal;
 | |
|   rv = securityManager->GetChannelURIPrincipal(mChannel,
 | |
|                                                getter_AddRefs(principal));
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|   bool expectCallback;
 | |
|   if (UC_LOG_ENABLED()) {
 | |
|     nsCOMPtr<nsIURI> principalURI;
 | |
|     nsCString spec;
 | |
|     principal->GetAsciiSpec(spec);
 | |
|     spec.Truncate(std::min(spec.Length(), UrlClassifierCommon::sMaxSpecLength));
 | |
|     UC_LOG(
 | |
|         ("nsChannelClassifier::StartInternal - classifying principal %s on "
 | |
|          "channel %p [this=%p]",
 | |
|          spec.get(), mChannel.get(), this));
 | |
|   }
 | |
|   // The classify is running in parent process, no need to give a valid event
 | |
|   // target
 | |
|   rv = uriClassifier->Classify(principal, nullptr, this, &expectCallback);
 | |
|   if (NS_FAILED(rv)) {
 | |
|     return rv;
 | |
|   }
 | |
| 
 | |
|   if (expectCallback) {
 | |
|     // Suspend the channel, it will be resumed when we get the classifier
 | |
|     // callback.
 | |
|     rv = mChannel->Suspend();
 | |
|     if (NS_FAILED(rv)) {
 | |
|       // Some channels (including nsJSChannel) fail on Suspend.  This
 | |
|       // shouldn't be fatal, but will prevent malware from being
 | |
|       // blocked on these channels.
 | |
|       UC_LOG_WARN(
 | |
|           ("nsChannelClassifier::StartInternal - couldn't suspend channel "
 | |
|            "[this=%p]",
 | |
|            this));
 | |
|       return rv;
 | |
|     }
 | |
| 
 | |
|     mSuspendedChannel = true;
 | |
|     UC_LOG(
 | |
|         ("nsChannelClassifier::StartInternal - suspended channel %p [this=%p]",
 | |
|          mChannel.get(), this));
 | |
|   } else {
 | |
|     UC_LOG_WARN((
 | |
|         "nsChannelClassifier::StartInternal - not expecting callback [this=%p]",
 | |
|         this));
 | |
|     return NS_ERROR_FAILURE;
 | |
|   }
 | |
| 
 | |
|   // Add an observer for shutdown
 | |
|   AddShutdownObserver();
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| bool nsChannelClassifier::IsHostnameEntitylisted(
 | |
|     nsIURI* aUri, const nsACString& aEntitylisted) {
 | |
|   nsAutoCString host;
 | |
|   nsresult rv = aUri->GetHost(host);
 | |
|   if (NS_FAILED(rv) || host.IsEmpty()) {
 | |
|     return false;
 | |
|   }
 | |
|   ToLowerCase(host);
 | |
| 
 | |
|   for (const nsACString& token :
 | |
|        nsCCharSeparatedTokenizer(aEntitylisted, ',').ToRange()) {
 | |
|     if (token.Equals(host)) {
 | |
|       UC_LOG(
 | |
|           ("nsChannelClassifier::StartInternal - skipping %s (entitylisted) "
 | |
|            "[this=%p]",
 | |
|            host.get(), this));
 | |
|       return true;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| // Note in the cache entry that this URL was classified, so that future
 | |
| // cached loads don't need to be checked.
 | |
| void nsChannelClassifier::MarkEntryClassified(nsresult status) {
 | |
|   // Should only be called in the parent process.
 | |
|   MOZ_ASSERT(XRE_IsParentProcess());
 | |
| 
 | |
|   // Don't cache tracking classifications because we support allowlisting.
 | |
|   if (UrlClassifierFeatureFactory::IsClassifierBlockingErrorCode(status) ||
 | |
|       mIsAllowListed) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (UC_LOG_ENABLED()) {
 | |
|     nsAutoCString errorName;
 | |
|     GetErrorName(status, errorName);
 | |
|     nsCOMPtr<nsIURI> uri;
 | |
|     mChannel->GetURI(getter_AddRefs(uri));
 | |
|     nsAutoCString spec;
 | |
|     uri->GetAsciiSpec(spec);
 | |
|     spec.Truncate(std::min(spec.Length(), UrlClassifierCommon::sMaxSpecLength));
 | |
|     UC_LOG(
 | |
|         ("nsChannelClassifier::MarkEntryClassified - result is %s "
 | |
|          "for uri %s [this=%p, channel=%p]",
 | |
|          errorName.get(), spec.get(), this, mChannel.get()));
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<nsICachingChannel> cachingChannel = do_QueryInterface(mChannel);
 | |
|   if (!cachingChannel) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<nsISupports> cacheToken;
 | |
|   cachingChannel->GetCacheToken(getter_AddRefs(cacheToken));
 | |
|   if (!cacheToken) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<nsICacheEntry> cacheEntry = do_QueryInterface(cacheToken);
 | |
|   if (!cacheEntry) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   cacheEntry->SetMetaDataElement("necko:classified",
 | |
|                                  NS_SUCCEEDED(status) ? "1" : nullptr);
 | |
| }
 | |
| 
 | |
| bool nsChannelClassifier::HasBeenClassified(nsIChannel* aChannel) {
 | |
|   // Should only be called in the parent process.
 | |
|   MOZ_ASSERT(XRE_IsParentProcess());
 | |
| 
 | |
|   nsCOMPtr<nsICachingChannel> cachingChannel = do_QueryInterface(aChannel);
 | |
|   if (!cachingChannel) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   // Only check the tag if we are loading from the cache without
 | |
|   // validation.
 | |
|   bool fromCache;
 | |
|   if (NS_FAILED(cachingChannel->IsFromCache(&fromCache)) || !fromCache) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<nsISupports> cacheToken;
 | |
|   cachingChannel->GetCacheToken(getter_AddRefs(cacheToken));
 | |
|   if (!cacheToken) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<nsICacheEntry> cacheEntry = do_QueryInterface(cacheToken);
 | |
|   if (!cacheEntry) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   nsCString tag;
 | |
|   cacheEntry->GetMetaDataElement("necko:classified", getter_Copies(tag));
 | |
|   return tag.EqualsLiteral("1");
 | |
| }
 | |
| 
 | |
| /* static */
 | |
| nsresult nsChannelClassifier::SendThreatHitReport(nsIChannel* aChannel,
 | |
|                                                   const nsACString& aProvider,
 | |
|                                                   const nsACString& aList,
 | |
|                                                   const nsACString& aFullHash) {
 | |
|   NS_ENSURE_ARG_POINTER(aChannel);
 | |
| 
 | |
|   nsAutoCString provider(aProvider);
 | |
|   nsPrintfCString reportEnablePref(
 | |
|       "browser.safebrowsing.provider.%s.dataSharing.enabled", provider.get());
 | |
|   if (!Preferences::GetBool(reportEnablePref.get(), false)) {
 | |
|     UC_LOG(
 | |
|         ("nsChannelClassifier::SendThreatHitReport - data sharing disabled for "
 | |
|          "%s",
 | |
|          provider.get()));
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<nsIURIClassifier> uriClassifier =
 | |
|       components::UrlClassifierDB::Service();
 | |
|   if (!uriClassifier) {
 | |
|     return NS_ERROR_UNEXPECTED;
 | |
|   }
 | |
| 
 | |
|   nsresult rv =
 | |
|       uriClassifier->SendThreatHitReport(aChannel, aProvider, aList, aFullHash);
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsChannelClassifier::OnClassifyComplete(nsresult aErrorCode,
 | |
|                                         const nsACString& aList,
 | |
|                                         const nsACString& aProvider,
 | |
|                                         const nsACString& aFullHash) {
 | |
|   // Should only be called in the parent process.
 | |
|   MOZ_ASSERT(XRE_IsParentProcess());
 | |
|   MOZ_ASSERT(
 | |
|       !UrlClassifierFeatureFactory::IsClassifierBlockingErrorCode(aErrorCode));
 | |
| 
 | |
|   if (mSuspendedChannel) {
 | |
|     MarkEntryClassified(aErrorCode);
 | |
| 
 | |
|     if (NS_FAILED(aErrorCode)) {
 | |
|       if (UC_LOG_ENABLED()) {
 | |
|         nsAutoCString errorName;
 | |
|         GetErrorName(aErrorCode, errorName);
 | |
| 
 | |
|         nsCOMPtr<nsIURI> uri;
 | |
|         mChannel->GetURI(getter_AddRefs(uri));
 | |
|         nsCString spec = uri->GetSpecOrDefault();
 | |
|         spec.Truncate(
 | |
|             std::min(spec.Length(), UrlClassifierCommon::sMaxSpecLength));
 | |
|         UC_LOG(
 | |
|             ("nsChannelClassifier::OnClassifyComplete - cancelling channel %p "
 | |
|              "for %s "
 | |
|              "with error code %s [this=%p]",
 | |
|              mChannel.get(), spec.get(), errorName.get(), this));
 | |
|       }
 | |
| 
 | |
|       // Channel will be cancelled (page element blocked) due to Safe Browsing.
 | |
|       // Do update the security state of the document and fire a security
 | |
|       // change event.
 | |
|       UrlClassifierCommon::SetBlockedContent(mChannel, aErrorCode, aList,
 | |
|                                              aProvider, aFullHash);
 | |
| 
 | |
|       if (aErrorCode == NS_ERROR_MALWARE_URI ||
 | |
|           aErrorCode == NS_ERROR_PHISHING_URI ||
 | |
|           aErrorCode == NS_ERROR_UNWANTED_URI ||
 | |
|           aErrorCode == NS_ERROR_HARMFUL_URI) {
 | |
|         SendThreatHitReport(mChannel, aProvider, aList, aFullHash);
 | |
|       }
 | |
| 
 | |
|       mChannel->Cancel(aErrorCode);
 | |
|     }
 | |
|     UC_LOG(
 | |
|         ("nsChannelClassifier::OnClassifyComplete - resuming channel %p "
 | |
|          "[this=%p]",
 | |
|          mChannel.get(), this));
 | |
|     mChannel->Resume();
 | |
|   }
 | |
| 
 | |
|   mChannel = nullptr;
 | |
|   RemoveShutdownObserver();
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| void nsChannelClassifier::AddShutdownObserver() {
 | |
|   nsCOMPtr<nsIObserverService> observerService =
 | |
|       mozilla::services::GetObserverService();
 | |
|   if (observerService) {
 | |
|     observerService->AddObserver(this, "profile-change-net-teardown", false);
 | |
|   }
 | |
| }
 | |
| 
 | |
| void nsChannelClassifier::RemoveShutdownObserver() {
 | |
|   nsCOMPtr<nsIObserverService> observerService =
 | |
|       mozilla::services::GetObserverService();
 | |
|   if (observerService) {
 | |
|     observerService->RemoveObserver(this, "profile-change-net-teardown");
 | |
|   }
 | |
| }
 | |
| 
 | |
| ///////////////////////////////////////////////////////////////////////////////
 | |
| // nsIObserver implementation
 | |
| NS_IMETHODIMP
 | |
| nsChannelClassifier::Observe(nsISupports* aSubject, const char* aTopic,
 | |
|                              const char16_t* aData) {
 | |
|   if (!strcmp(aTopic, "profile-change-net-teardown")) {
 | |
|     // If we aren't getting a callback for any reason, make sure
 | |
|     // we resume the channel.
 | |
| 
 | |
|     if (mChannel && mSuspendedChannel) {
 | |
|       mSuspendedChannel = false;
 | |
|       mChannel->Cancel(NS_ERROR_ABORT);
 | |
|       mChannel->Resume();
 | |
|       mChannel = nullptr;
 | |
|     }
 | |
| 
 | |
|     RemoveShutdownObserver();
 | |
|   }
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| }  // namespace net
 | |
| }  // namespace mozilla
 | 
