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