tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

nsChannelClassifier.cpp (14882B)


      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/. */
      6 
      7 #include "nsChannelClassifier.h"
      8 
      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"
     20 
     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"
     29 
     30 namespace mozilla {
     31 namespace net {
     32 
     33 #define URLCLASSIFIER_EXCEPTION_HOSTNAMES "urlclassifier.skipHostnames"
     34 
     35 // Put CachedPrefs in anonymous namespace to avoid any collision from outside of
     36 // this file.
     37 namespace {
     38 
     39 /**
     40 * It is not recommended to read from Preference everytime a channel is
     41 * connected.
     42 * That is not fast and we should cache preference values and reuse them
     43 */
     44 class CachedPrefs final {
     45 public:
     46  static CachedPrefs* GetInstance();
     47 
     48  void Init();
     49 
     50  nsCString GetExceptionHostnames() const { return mExceptionHostnames; }
     51  void SetExceptionHostnames(const nsACString& aHostnames) {
     52    mExceptionHostnames = aHostnames;
     53  }
     54 
     55 private:
     56  friend class StaticAutoPtr<CachedPrefs>;
     57  CachedPrefs();
     58  ~CachedPrefs();
     59 
     60  static void OnPrefsChange(const char* aPrefName, void*);
     61 
     62  nsCString mExceptionHostnames;
     63 
     64  static StaticAutoPtr<CachedPrefs> sInstance;
     65 };
     66 
     67 StaticAutoPtr<CachedPrefs> CachedPrefs::sInstance;
     68 
     69 // static
     70 void CachedPrefs::OnPrefsChange(const char* aPref, void* aPrefs) {
     71  auto* prefs = static_cast<CachedPrefs*>(aPrefs);
     72 
     73  if (!strcmp(aPref, URLCLASSIFIER_EXCEPTION_HOSTNAMES)) {
     74    nsCString exceptionHostnames;
     75    Preferences::GetCString(URLCLASSIFIER_EXCEPTION_HOSTNAMES,
     76                            exceptionHostnames);
     77    ToLowerCase(exceptionHostnames);
     78    prefs->SetExceptionHostnames(exceptionHostnames);
     79  }
     80 }
     81 
     82 void CachedPrefs::Init() {
     83  Preferences::RegisterCallbackAndCall(CachedPrefs::OnPrefsChange,
     84                                       URLCLASSIFIER_EXCEPTION_HOSTNAMES, this);
     85 }
     86 
     87 // static
     88 CachedPrefs* CachedPrefs::GetInstance() {
     89  if (!sInstance) {
     90    sInstance = new CachedPrefs();
     91    sInstance->Init();
     92    ClearOnShutdown(&sInstance);
     93  }
     94  MOZ_ASSERT(sInstance);
     95  return sInstance;
     96 }
     97 
     98 CachedPrefs::CachedPrefs() { MOZ_COUNT_CTOR(CachedPrefs); }
     99 
    100 CachedPrefs::~CachedPrefs() {
    101  MOZ_COUNT_DTOR(CachedPrefs);
    102 
    103  Preferences::UnregisterCallback(CachedPrefs::OnPrefsChange,
    104                                  URLCLASSIFIER_EXCEPTION_HOSTNAMES, this);
    105 }
    106 
    107 }  // anonymous namespace
    108 
    109 NS_IMPL_ISUPPORTS(nsChannelClassifier, nsIURIClassifierCallback, nsIObserver)
    110 
    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);
    115 }
    116 
    117 nsChannelClassifier::~nsChannelClassifier() {
    118  UC_LOG_LEAK(("nsChannelClassifier::~nsChannelClassifier [this=%p]", this));
    119 }
    120 
    121 void nsChannelClassifier::Start() {
    122  nsresult rv = StartInternal();
    123  if (NS_FAILED(rv)) {
    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);
    127  }
    128 }
    129 
    130 nsresult nsChannelClassifier::StartInternal() {
    131  // Should only be called in the parent process.
    132  MOZ_ASSERT(XRE_IsParentProcess());
    133 
    134  // Don't bother to run the classifier on a load that has already failed.
    135  // (this might happen after a redirect)
    136  nsresult status;
    137  mChannel->GetStatus(&status);
    138  if (NS_FAILED(status)) return status;
    139 
    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;
    144  }
    145 
    146  nsCOMPtr<nsIURI> uri;
    147  nsresult rv = mChannel->GetURI(getter_AddRefs(uri));
    148  NS_ENSURE_SUCCESS(rv, rv);
    149 
    150  // Don't bother checking certain types of URIs.
    151  if (uri->SchemeIs("about")) {
    152    return NS_ERROR_UNEXPECTED;
    153  }
    154 
    155  bool hasFlags;
    156  rv = NS_URIChainHasFlags(uri, nsIProtocolHandler::URI_DANGEROUS_TO_LOAD,
    157                           &hasFlags);
    158  NS_ENSURE_SUCCESS(rv, rv);
    159  if (hasFlags) return NS_ERROR_UNEXPECTED;
    160 
    161  rv = NS_URIChainHasFlags(uri, nsIProtocolHandler::URI_IS_LOCAL_FILE,
    162                           &hasFlags);
    163  NS_ENSURE_SUCCESS(rv, rv);
    164  if (hasFlags) return NS_ERROR_UNEXPECTED;
    165 
    166  rv = NS_URIChainHasFlags(uri, nsIProtocolHandler::URI_IS_UI_RESOURCE,
    167                           &hasFlags);
    168  NS_ENSURE_SUCCESS(rv, rv);
    169  if (hasFlags) return NS_ERROR_UNEXPECTED;
    170 
    171  rv = NS_URIChainHasFlags(uri, nsIProtocolHandler::URI_IS_LOCAL_RESOURCE,
    172                           &hasFlags);
    173  NS_ENSURE_SUCCESS(rv, rv);
    174  if (hasFlags) return NS_ERROR_UNEXPECTED;
    175 
    176  nsCString exceptionHostnames =
    177      CachedPrefs::GetInstance()->GetExceptionHostnames();
    178  if (!exceptionHostnames.IsEmpty()) {
    179    UC_LOG(
    180        ("nsChannelClassifier::StartInternal - entitylisted hostnames = %s "
    181         "[this=%p]",
    182         exceptionHostnames.get(), this));
    183    if (IsHostnameEntitylisted(uri, exceptionHostnames)) {
    184      return NS_ERROR_UNEXPECTED;
    185    }
    186  }
    187 
    188  nsCOMPtr<nsIURIClassifier> uriClassifier =
    189      mozilla::components::UrlClassifierDB::Service(&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;
    193  }
    194  NS_ENSURE_SUCCESS(rv, rv);
    195 
    196  nsCOMPtr<nsIScriptSecurityManager> securityManager =
    197      mozilla::components::ScriptSecurityManager::Service(&rv);
    198  NS_ENSURE_SUCCESS(rv, rv);
    199 
    200  nsCOMPtr<nsIPrincipal> principal;
    201  rv = securityManager->GetChannelURIPrincipal(mChannel,
    202                                               getter_AddRefs(principal));
    203  NS_ENSURE_SUCCESS(rv, rv);
    204 
    205  bool expectCallback;
    206  if (UC_LOG_ENABLED()) {
    207    nsCOMPtr<nsIURI> principalURI;
    208    nsCString spec;
    209    principal->GetAsciiSpec(spec);
    210    spec.Truncate(std::min(spec.Length(), UrlClassifierCommon::sMaxSpecLength));
    211    UC_LOG(
    212        ("nsChannelClassifier::StartInternal - classifying principal %s on "
    213         "channel %p [this=%p]",
    214         spec.get(), mChannel.get(), this));
    215  }
    216  // The classify is running in parent process, no need to give a valid event
    217  // target
    218  rv = uriClassifier->Classify(principal, this, &expectCallback);
    219  if (NS_FAILED(rv)) {
    220    return rv;
    221  }
    222 
    223  if (expectCallback) {
    224    // Suspend the channel, it will be resumed when we get the classifier
    225    // callback.
    226    rv = mChannel->Suspend();
    227    if (NS_FAILED(rv)) {
    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.
    231      UC_LOG_WARN(
    232          ("nsChannelClassifier::StartInternal - couldn't suspend channel "
    233           "[this=%p]",
    234           this));
    235      return rv;
    236    }
    237 
    238    mSuspendedChannel = true;
    239    UC_LOG(
    240        ("nsChannelClassifier::StartInternal - suspended channel %p [this=%p]",
    241         mChannel.get(), this));
    242  } else {
    243    UC_LOG_WARN((
    244        "nsChannelClassifier::StartInternal - not expecting callback [this=%p]",
    245        this));
    246    return NS_ERROR_FAILURE;
    247  }
    248 
    249  // Add an observer for shutdown
    250  AddShutdownObserver();
    251  return NS_OK;
    252 }
    253 
    254 bool nsChannelClassifier::IsHostnameEntitylisted(
    255    nsIURI* aUri, const nsACString& aEntitylisted) {
    256  nsAutoCString host;
    257  nsresult rv = aUri->GetHost(host);
    258  if (NS_FAILED(rv) || host.IsEmpty()) {
    259    return false;
    260  }
    261  ToLowerCase(host);
    262 
    263  for (const nsACString& token :
    264       nsCCharSeparatedTokenizer(aEntitylisted, ',').ToRange()) {
    265    if (token.Equals(host)) {
    266      UC_LOG(
    267          ("nsChannelClassifier::StartInternal - skipping %s (entitylisted) "
    268           "[this=%p]",
    269           host.get(), this));
    270      return true;
    271    }
    272  }
    273 
    274  return false;
    275 }
    276 
    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());
    282 
    283  // Don't cache tracking classifications because we support allowlisting.
    284  if (UrlClassifierFeatureFactory::IsClassifierBlockingErrorCode(status) ||
    285      mIsAllowListed) {
    286    return;
    287  }
    288 
    289  if (UC_LOG_ENABLED()) {
    290    nsAutoCString errorName;
    291    GetErrorName(status, errorName);
    292    nsCOMPtr<nsIURI> uri;
    293    mChannel->GetURI(getter_AddRefs(uri));
    294    nsAutoCString spec;
    295    uri->GetAsciiSpec(spec);
    296    spec.Truncate(std::min(spec.Length(), UrlClassifierCommon::sMaxSpecLength));
    297    UC_LOG(
    298        ("nsChannelClassifier::MarkEntryClassified - result is %s "
    299         "for uri %s [this=%p, channel=%p]",
    300         errorName.get(), spec.get(), this, mChannel.get()));
    301  }
    302 
    303  nsCOMPtr<nsICachingChannel> cachingChannel = do_QueryInterface(mChannel);
    304  if (!cachingChannel) {
    305    return;
    306  }
    307 
    308  nsCOMPtr<nsISupports> cacheToken;
    309  cachingChannel->GetCacheToken(getter_AddRefs(cacheToken));
    310  if (!cacheToken) {
    311    return;
    312  }
    313 
    314  nsCOMPtr<nsICacheEntry> cacheEntry = do_QueryInterface(cacheToken);
    315  if (!cacheEntry) {
    316    return;
    317  }
    318 
    319  cacheEntry->SetMetaDataElement("necko:classified",
    320                                 NS_SUCCEEDED(status) ? "1" : nullptr);
    321 }
    322 
    323 bool nsChannelClassifier::HasBeenClassified(nsIChannel* aChannel) {
    324  // Should only be called in the parent process.
    325  MOZ_ASSERT(XRE_IsParentProcess());
    326 
    327  nsCOMPtr<nsICachingChannel> cachingChannel = do_QueryInterface(aChannel);
    328  if (!cachingChannel) {
    329    return false;
    330  }
    331 
    332  // Only check the tag if we are loading from the cache without
    333  // validation.
    334  bool fromCache;
    335  if (NS_FAILED(cachingChannel->IsFromCache(&fromCache)) || !fromCache) {
    336    return false;
    337  }
    338 
    339  nsCOMPtr<nsISupports> cacheToken;
    340  cachingChannel->GetCacheToken(getter_AddRefs(cacheToken));
    341  if (!cacheToken) {
    342    return false;
    343  }
    344 
    345  nsCOMPtr<nsICacheEntry> cacheEntry = do_QueryInterface(cacheToken);
    346  if (!cacheEntry) {
    347    return false;
    348  }
    349 
    350  nsCString tag;
    351  cacheEntry->GetMetaDataElement("necko:classified", getter_Copies(tag));
    352  return tag.EqualsLiteral("1");
    353 }
    354 
    355 /* static */
    356 nsresult nsChannelClassifier::SendThreatHitReport(nsIChannel* aChannel,
    357                                                  const nsACString& aProvider,
    358                                                  const nsACString& aList,
    359                                                  const nsACString& aFullHash) {
    360  NS_ENSURE_ARG_POINTER(aChannel);
    361 
    362  nsAutoCString provider(aProvider);
    363  nsPrintfCString reportEnablePref(
    364      "browser.safebrowsing.provider.%s.dataSharing.enabled", provider.get());
    365  if (!Preferences::GetBool(reportEnablePref.get(), false)) {
    366    UC_LOG(
    367        ("nsChannelClassifier::SendThreatHitReport - data sharing disabled for "
    368         "%s",
    369         provider.get()));
    370    return NS_OK;
    371  }
    372 
    373  nsCOMPtr<nsIURIClassifier> uriClassifier =
    374      components::UrlClassifierDB::Service();
    375  if (!uriClassifier) {
    376    return NS_ERROR_UNEXPECTED;
    377  }
    378 
    379  nsresult rv =
    380      uriClassifier->SendThreatHitReport(aChannel, aProvider, aList, aFullHash);
    381  NS_ENSURE_SUCCESS(rv, rv);
    382 
    383  return NS_OK;
    384 }
    385 
    386 NS_IMETHODIMP
    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());
    393  MOZ_ASSERT(
    394      !UrlClassifierFeatureFactory::IsClassifierBlockingErrorCode(aErrorCode));
    395 
    396  if (mSuspendedChannel) {
    397    MarkEntryClassified(aErrorCode);
    398 
    399    if (NS_FAILED(aErrorCode)) {
    400      if (UC_LOG_ENABLED()) {
    401        nsAutoCString errorName;
    402        GetErrorName(aErrorCode, errorName);
    403 
    404        nsCOMPtr<nsIURI> uri;
    405        mChannel->GetURI(getter_AddRefs(uri));
    406        nsCString spec = uri->GetSpecOrDefault();
    407        spec.Truncate(
    408            std::min(spec.Length(), UrlClassifierCommon::sMaxSpecLength));
    409        UC_LOG(
    410            ("nsChannelClassifier::OnClassifyComplete - cancelling channel %p "
    411             "for %s "
    412             "with error code %s [this=%p]",
    413             mChannel.get(), spec.get(), errorName.get(), this));
    414      }
    415 
    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
    418      // change event.
    419      UrlClassifierCommon::SetBlockedContent(mChannel, aErrorCode, aList,
    420                                             aProvider, aFullHash);
    421 
    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);
    427      }
    428 
    429      mChannel->Cancel(aErrorCode);
    430    }
    431    UC_LOG(
    432        ("nsChannelClassifier::OnClassifyComplete - resuming channel %p "
    433         "[this=%p]",
    434         mChannel.get(), this));
    435    mChannel->Resume();
    436  }
    437 
    438  mChannel = nullptr;
    439  RemoveShutdownObserver();
    440 
    441  return NS_OK;
    442 }
    443 
    444 void nsChannelClassifier::AddShutdownObserver() {
    445  nsCOMPtr<nsIObserverService> observerService =
    446      mozilla::services::GetObserverService();
    447  if (observerService) {
    448    observerService->AddObserver(this, "profile-change-net-teardown", false);
    449  }
    450 }
    451 
    452 void nsChannelClassifier::RemoveShutdownObserver() {
    453  nsCOMPtr<nsIObserverService> observerService =
    454      mozilla::services::GetObserverService();
    455  if (observerService) {
    456    observerService->RemoveObserver(this, "profile-change-net-teardown");
    457  }
    458 }
    459 
    460 ///////////////////////////////////////////////////////////////////////////////
    461 // nsIObserver implementation
    462 NS_IMETHODIMP
    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.
    468 
    469    if (mChannel && mSuspendedChannel) {
    470      mSuspendedChannel = false;
    471      mChannel->Cancel(NS_ERROR_ABORT);
    472      mChannel->Resume();
    473      mChannel = nullptr;
    474    }
    475 
    476    RemoveShutdownObserver();
    477  }
    478 
    479  return NS_OK;
    480 }
    481 
    482 }  // namespace net
    483 }  // namespace mozilla