UrlClassifierFeatureHarmfulAddonProtection.cpp (9716B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 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 "UrlClassifierFeatureHarmfulAddonProtection.h" 8 9 #include "mozilla/AntiTrackingUtils.h" 10 #include "mozilla/extensions/WebExtensionPolicy.h" 11 #include "mozilla/glean/NetwerkMetrics.h" 12 #include "mozilla/net/UrlClassifierCommon.h" 13 #include "ChannelClassifierService.h" 14 #include "mozilla/StaticPrefs_privacy.h" 15 #include "nsNetUtil.h" 16 #include "mozilla/StaticPtr.h" 17 #include "nsIChannel.h" 18 #include "nsIEffectiveTLDService.h" 19 #include "nsIHttpChannelInternal.h" 20 #include "nsIWebProgressListener.h" 21 #include "nsIWritablePropertyBag2.h" 22 23 namespace mozilla { 24 namespace net { 25 26 namespace { 27 28 #define HARMFULADDON_FEATURE_NAME "harmfuladdon-protection" 29 30 #define URLCLASSIFIER_HARMFULADDON_BLOCKLIST \ 31 "urlclassifier.features.harmfuladdon.blocklistTables" 32 #define URLCLASSIFIER_HARMFULADDON_BLOCKLIST_TEST_ENTRIES \ 33 "urlclassifier.features.harmfuladdon.blocklistHosts" 34 #define URLCLASSIFIER_HARMFULADDON_ENTITYLIST \ 35 "urlclassifier.features.harmfuladdon.entitylistTables" 36 #define URLCLASSIFIER_HARMFULADDON_ENTITYLIST_TEST_ENTRIES \ 37 "urlclassifier.features.harmfuladdon.entitylistHosts" 38 #define URLCLASSIFIER_HARMFULADDON_EXCEPTION_URLS \ 39 "urlclassifier.features.harmfuladdon.skipURLs" 40 #define TABLE_HARMFULADDON_BLOCKLIST_PREF "harmfuladdon-blocklist-pref" 41 #define TABLE_HARMFULADDON_ENTITYLIST_PREF "harmfuladdon-entitylist-pref" 42 43 StaticRefPtr<UrlClassifierFeatureHarmfulAddonProtection> 44 gFeatureHarmfulAddonProtection; 45 46 extensions::WebExtensionPolicy* GetAddonPolicy(nsIChannel* aChannel) { 47 nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo(); 48 49 nsCOMPtr<nsIPrincipal> triggeringPrincipal; 50 if (NS_FAILED(loadInfo->GetTriggeringPrincipal( 51 getter_AddRefs(triggeringPrincipal)))) { 52 return nullptr; 53 } 54 55 nsCOMPtr<nsIPrincipal> loadingPrincipal; 56 if (NS_FAILED( 57 loadInfo->GetLoadingPrincipal(getter_AddRefs(loadingPrincipal)))) { 58 return nullptr; 59 } 60 61 extensions::WebExtensionPolicy* policy = nullptr; 62 63 if (triggeringPrincipal) { 64 policy = BasePrincipal::Cast(triggeringPrincipal)->AddonPolicy(); 65 66 if (!policy) { 67 policy = 68 BasePrincipal::Cast(triggeringPrincipal)->ContentScriptAddonPolicy(); 69 } 70 } 71 72 if (!policy && loadingPrincipal) { 73 policy = BasePrincipal::Cast(loadingPrincipal)->AddonPolicy(); 74 75 if (!policy) { 76 policy = 77 BasePrincipal::Cast(loadingPrincipal)->ContentScriptAddonPolicy(); 78 } 79 } 80 81 return policy; 82 } 83 84 bool GetAddonId(nsIChannel* aChannel, nsACString& aAddonID) { 85 extensions::WebExtensionPolicy* policy = GetAddonPolicy(aChannel); 86 if (!policy) { 87 return false; 88 } 89 90 CopyUTF16toUTF8(nsDependentAtomString(policy->Id()), aAddonID); 91 return true; 92 } 93 94 bool GetAddonName(nsIChannel* aChannel, nsACString& aAddonName) { 95 extensions::WebExtensionPolicy* policy = GetAddonPolicy(aChannel); 96 if (!policy) { 97 return false; 98 } 99 100 CopyUTF16toUTF8(policy->Name(), aAddonName); 101 return true; 102 } 103 104 bool GetETLD(nsIChannel* aChannel, nsACString& aETLD) { 105 nsCOMPtr<nsIURI> uri; 106 nsresult rv = aChannel->GetURI(getter_AddRefs(uri)); 107 if (NS_WARN_IF(NS_FAILED(rv)) || !uri) { 108 return false; 109 } 110 111 nsCOMPtr<nsIEffectiveTLDService> etld( 112 do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID)); 113 if (NS_WARN_IF(!etld)) { 114 return false; 115 } 116 117 rv = etld->GetBaseDomain(uri, 0, aETLD); 118 if (NS_WARN_IF(NS_FAILED(rv))) { 119 return false; 120 } 121 122 return !aETLD.IsEmpty(); 123 } 124 125 void RecordGleanAddonBlocked(nsIChannel* aChannel, 126 const nsACString& aTableStr) { 127 nsAutoCString etld; 128 if (!GetETLD(aChannel, etld)) { 129 return; 130 } 131 132 nsAutoCString addonId; 133 if (!GetAddonId(aChannel, addonId)) { 134 return; 135 } 136 137 glean::network::urlclassifier_addon_block.Record( 138 Some(glean::network::UrlclassifierAddonBlockExtra{ 139 mozilla::Some(addonId), mozilla::Some(etld), 140 mozilla::Some(nsCString(aTableStr))})); 141 } 142 143 } // namespace 144 145 UrlClassifierFeatureHarmfulAddonProtection:: 146 UrlClassifierFeatureHarmfulAddonProtection() 147 : UrlClassifierFeatureAntiTrackingBase( 148 nsLiteralCString(HARMFULADDON_FEATURE_NAME), 149 nsLiteralCString(URLCLASSIFIER_HARMFULADDON_BLOCKLIST), 150 nsLiteralCString(URLCLASSIFIER_HARMFULADDON_ENTITYLIST), 151 nsLiteralCString(URLCLASSIFIER_HARMFULADDON_BLOCKLIST_TEST_ENTRIES), 152 nsLiteralCString(URLCLASSIFIER_HARMFULADDON_ENTITYLIST_TEST_ENTRIES), 153 nsLiteralCString(TABLE_HARMFULADDON_BLOCKLIST_PREF), 154 nsLiteralCString(TABLE_HARMFULADDON_ENTITYLIST_PREF), 155 nsLiteralCString(URLCLASSIFIER_HARMFULADDON_EXCEPTION_URLS)) {} 156 157 /* static */ const char* UrlClassifierFeatureHarmfulAddonProtection::Name() { 158 return HARMFULADDON_FEATURE_NAME; 159 } 160 161 /* static */ 162 void UrlClassifierFeatureHarmfulAddonProtection::MaybeInitialize() { 163 UC_LOG_LEAK(("UrlClassifierFeatureHarmfulAddonProtection::MaybeInitialize")); 164 165 if (!gFeatureHarmfulAddonProtection) { 166 gFeatureHarmfulAddonProtection = 167 new UrlClassifierFeatureHarmfulAddonProtection(); 168 gFeatureHarmfulAddonProtection->InitializePreferences(); 169 } 170 } 171 172 /* static */ 173 void UrlClassifierFeatureHarmfulAddonProtection::MaybeShutdown() { 174 UC_LOG_LEAK(("UrlClassifierFeatureHarmfulAddonProtection::MaybeShutdown")); 175 176 if (gFeatureHarmfulAddonProtection) { 177 gFeatureHarmfulAddonProtection->ShutdownPreferences(); 178 gFeatureHarmfulAddonProtection = nullptr; 179 } 180 } 181 182 /* static */ 183 already_AddRefed<UrlClassifierFeatureHarmfulAddonProtection> 184 UrlClassifierFeatureHarmfulAddonProtection::MaybeCreate(nsIChannel* aChannel) { 185 MOZ_ASSERT(aChannel); 186 187 UC_LOG_LEAK( 188 ("UrlClassifierFeatureHarmfulAddonProtection::MaybeCreate - channel %p", 189 aChannel)); 190 191 if (!StaticPrefs::privacy_trackingprotection_harmfuladdon_enabled()) { 192 return nullptr; 193 } 194 195 RefPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo(); 196 nsIPrincipal* triggeringPrincipal = loadInfo->TriggeringPrincipal(); 197 bool addonTriggeringPrincipal = 198 triggeringPrincipal && 199 triggeringPrincipal->GetIsAddonOrExpandedAddonPrincipal(); 200 201 nsIPrincipal* loadingPrincipal = loadInfo->GetLoadingPrincipal(); 202 bool addonLoadingPrincipal = 203 loadingPrincipal && 204 loadingPrincipal->GetIsAddonOrExpandedAddonPrincipal(); 205 206 if (!addonTriggeringPrincipal && !addonLoadingPrincipal) { 207 return nullptr; 208 } 209 210 // Recommended add-ons are exempt. 211 extensions::WebExtensionPolicy* policy = GetAddonPolicy(aChannel); 212 if (policy && policy->HasRecommendedState()) { 213 return nullptr; 214 } 215 216 MaybeInitialize(); 217 MOZ_ASSERT(gFeatureHarmfulAddonProtection); 218 219 RefPtr<UrlClassifierFeatureHarmfulAddonProtection> self = 220 gFeatureHarmfulAddonProtection; 221 return self.forget(); 222 } 223 224 /* static */ 225 already_AddRefed<nsIUrlClassifierFeature> 226 UrlClassifierFeatureHarmfulAddonProtection::GetIfNameMatches( 227 const nsACString& aName) { 228 if (!aName.EqualsLiteral(HARMFULADDON_FEATURE_NAME)) { 229 return nullptr; 230 } 231 232 MaybeInitialize(); 233 MOZ_ASSERT(gFeatureHarmfulAddonProtection); 234 235 RefPtr<UrlClassifierFeatureHarmfulAddonProtection> self = 236 gFeatureHarmfulAddonProtection; 237 return self.forget(); 238 } 239 240 NS_IMETHODIMP 241 UrlClassifierFeatureHarmfulAddonProtection::ProcessChannel( 242 nsIChannel* aChannel, const nsTArray<nsCString>& aList, 243 const nsTArray<nsCString>& aHashes, bool* aShouldContinue) { 244 NS_ENSURE_ARG_POINTER(aChannel); 245 NS_ENSURE_ARG_POINTER(aShouldContinue); 246 247 bool isAllowListed = UrlClassifierCommon::IsAllowListed(aChannel); 248 249 // This is a blocking feature. 250 *aShouldContinue = isAllowListed; 251 252 if (isAllowListed) { 253 return NS_OK; 254 } 255 256 bool ShouldProcessByProtectionFeature = 257 UrlClassifierCommon::ShouldProcessWithProtectionFeature(aChannel); 258 259 *aShouldContinue = !ShouldProcessByProtectionFeature; 260 261 if (!ShouldProcessByProtectionFeature) { 262 return NS_OK; 263 } 264 265 nsAutoCString list; 266 UrlClassifierCommon::TablesToString(aList, list); 267 268 ChannelBlockDecision decision = 269 ChannelClassifierService::OnBeforeBlockChannel(aChannel, mName, list); 270 if (decision != ChannelBlockDecision::Blocked) { 271 *aShouldContinue = true; 272 return NS_OK; 273 } 274 275 RecordGleanAddonBlocked(aChannel, list); 276 277 nsAutoCString addonName; 278 if (GetAddonName(aChannel, addonName)) { 279 nsCOMPtr<nsIWritablePropertyBag2> props(do_QueryInterface(aChannel)); 280 if (props) { 281 props->SetPropertyAsACString(u"blockedExtension"_ns, addonName); 282 } 283 } 284 285 UrlClassifierCommon::SetBlockedContent(aChannel, NS_ERROR_HARMFULADDON_URI, 286 list, ""_ns, ""_ns); 287 288 UC_LOG( 289 ("UrlClassifierFeatureHarmfulAddonProtection::ProcessChannel - " 290 "cancelling channel %p", 291 aChannel)); 292 293 (void)aChannel->Cancel(NS_ERROR_HARMFULADDON_URI); 294 return NS_OK; 295 } 296 297 NS_IMETHODIMP 298 UrlClassifierFeatureHarmfulAddonProtection::GetURIByListType( 299 nsIChannel* aChannel, nsIUrlClassifierFeature::listType aListType, 300 nsIUrlClassifierFeature::URIType* aURIType, nsIURI** aURI) { 301 NS_ENSURE_ARG_POINTER(aChannel); 302 NS_ENSURE_ARG_POINTER(aURIType); 303 NS_ENSURE_ARG_POINTER(aURI); 304 305 if (aListType == nsIUrlClassifierFeature::blocklist) { 306 *aURIType = nsIUrlClassifierFeature::blocklistURI; 307 return aChannel->GetURI(aURI); 308 } 309 310 MOZ_ASSERT(aListType == nsIUrlClassifierFeature::entitylist); 311 312 *aURIType = nsIUrlClassifierFeature::pairwiseEntitylistURI; 313 return aChannel->GetURI(aURI); 314 } 315 316 } // namespace net 317 } // namespace mozilla