FeaturePolicy.cpp (11759B)
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 "FeaturePolicy.h" 8 9 #include "mozilla/BasePrincipal.h" 10 #include "mozilla/StaticPrefs_dom.h" 11 #include "mozilla/dom/BrowsingContext.h" 12 #include "mozilla/dom/Feature.h" 13 #include "mozilla/dom/FeaturePolicyBinding.h" 14 #include "mozilla/dom/FeaturePolicyParser.h" 15 #include "mozilla/dom/FeaturePolicyUtils.h" 16 #include "mozilla/dom/HTMLIFrameElement.h" 17 #include "nsContentUtils.h" 18 #include "nsNetUtil.h" 19 20 namespace mozilla::dom { 21 22 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(FeaturePolicy) 23 NS_IMPL_CYCLE_COLLECTING_ADDREF(FeaturePolicy) 24 NS_IMPL_CYCLE_COLLECTING_RELEASE(FeaturePolicy) 25 26 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(FeaturePolicy) 27 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY 28 NS_INTERFACE_MAP_ENTRY(nsISupports) 29 NS_INTERFACE_MAP_END 30 31 FeaturePolicy::FeaturePolicy(nsINode* aNode) : mParentNode(aNode) {} 32 33 void FeaturePolicy::InheritPolicy(FeaturePolicy* aParentPolicy) { 34 MOZ_ASSERT(aParentPolicy); 35 36 mInheritedDeniedFeatureNames.Clear(); 37 38 RefPtr<FeaturePolicy> dest = this; 39 RefPtr<FeaturePolicy> src = aParentPolicy; 40 41 // Inherit origins which explicitly declared policy in chain 42 for (const Feature& featureInChain : 43 aParentPolicy->mDeclaredFeaturesInAncestorChain) { 44 dest->AppendToDeclaredAllowInAncestorChain(featureInChain); 45 } 46 47 FeaturePolicyUtils::ForEachFeature([dest, src](const char* aFeatureName) { 48 nsString featureName; 49 featureName.AppendASCII(aFeatureName); 50 // Store unsafe allows all (allow=*) 51 if (src->HasFeatureUnsafeAllowsAll(featureName)) { 52 dest->mParentAllowedAllFeatures.AppendElement(featureName); 53 } 54 55 // If the destination has a declared feature (via the HTTP header or 'allow' 56 // attribute) we allow the feature if the destination allows it and the 57 // parent allows its origin or the destinations' one. 58 if (dest->HasDeclaredFeature(featureName) && 59 dest->AllowsFeatureInternal(featureName, dest->mDefaultOrigin)) { 60 if (!src->AllowsFeatureInternal(featureName, src->mDefaultOrigin) && 61 !src->AllowsFeatureInternal(featureName, dest->mDefaultOrigin)) { 62 dest->SetInheritedDeniedFeature(featureName); 63 } 64 return; 65 } 66 67 // If there was not a declared feature, we allow the feature if the parent 68 // FeaturePolicy allows the current origin. 69 if (!src->AllowsFeatureInternal(featureName, dest->mDefaultOrigin)) { 70 dest->SetInheritedDeniedFeature(featureName); 71 } 72 }); 73 } 74 75 void FeaturePolicy::InheritPolicy( 76 const FeaturePolicyInfo& aContainerFeaturePolicyInfo) { 77 // We create a temporary FeaturePolicy from the FeaturePolicyInfo to be able 78 // to re-use the inheriting functionality from FeaturePolicy. 79 RefPtr<dom::FeaturePolicy> featurePolicy = new dom::FeaturePolicy(nullptr); 80 featurePolicy->SetDefaultOrigin(aContainerFeaturePolicyInfo.mDefaultOrigin); 81 featurePolicy->SetInheritedDeniedFeatureNames( 82 aContainerFeaturePolicyInfo.mInheritedDeniedFeatureNames); 83 84 const auto& declaredString = aContainerFeaturePolicyInfo.mDeclaredString; 85 if (aContainerFeaturePolicyInfo.mSelfOrigin && !declaredString.IsEmpty()) { 86 featurePolicy->SetDeclaredPolicy(nullptr, declaredString, 87 aContainerFeaturePolicyInfo.mSelfOrigin, 88 aContainerFeaturePolicyInfo.mSrcOrigin); 89 } 90 91 for (const auto& featureName : 92 aContainerFeaturePolicyInfo.mAttributeEnabledFeatureNames) { 93 featurePolicy->MaybeSetAllowedPolicy(featureName); 94 } 95 96 InheritPolicy(featurePolicy); 97 } 98 99 void FeaturePolicy::SetInheritedDeniedFeature(const nsAString& aFeatureName) { 100 MOZ_ASSERT(!HasInheritedDeniedFeature(aFeatureName)); 101 mInheritedDeniedFeatureNames.AppendElement(aFeatureName); 102 } 103 104 bool FeaturePolicy::HasInheritedDeniedFeature( 105 const nsAString& aFeatureName) const { 106 return mInheritedDeniedFeatureNames.Contains(aFeatureName); 107 } 108 109 bool FeaturePolicy::HasDeclaredFeature(const nsAString& aFeatureName) const { 110 for (const Feature& feature : mFeatures) { 111 if (feature.Name().Equals(aFeatureName)) { 112 return true; 113 } 114 } 115 116 return false; 117 } 118 119 bool FeaturePolicy::HasFeatureUnsafeAllowsAll( 120 const nsAString& aFeatureName) const { 121 for (const Feature& feature : mFeatures) { 122 if (feature.AllowsAll() && feature.Name().Equals(aFeatureName)) { 123 return true; 124 } 125 } 126 127 // We should look into parent too (for example, document of iframe which 128 // allows all, would be unsafe) 129 return mParentAllowedAllFeatures.Contains(aFeatureName); 130 } 131 132 void FeaturePolicy::AppendToDeclaredAllowInAncestorChain( 133 const Feature& aFeature) { 134 for (Feature& featureInChain : mDeclaredFeaturesInAncestorChain) { 135 if (featureInChain.Name().Equals(aFeature.Name())) { 136 MOZ_ASSERT(featureInChain.HasAllowList()); 137 138 nsTArray<nsCOMPtr<nsIPrincipal>> list; 139 aFeature.GetAllowList(list); 140 141 for (nsIPrincipal* principal : list) { 142 featureInChain.AppendToAllowList(principal); 143 } 144 continue; 145 } 146 } 147 148 mDeclaredFeaturesInAncestorChain.AppendElement(aFeature); 149 } 150 151 bool FeaturePolicy::IsSameOriginAsSrc(nsIPrincipal* aPrincipal) const { 152 MOZ_ASSERT(aPrincipal); 153 154 if (!mSrcOrigin) { 155 return false; 156 } 157 158 return BasePrincipal::Cast(mSrcOrigin) 159 ->Subsumes(aPrincipal, BasePrincipal::ConsiderDocumentDomain); 160 } 161 162 void FeaturePolicy::SetDeclaredPolicy(Document* aDocument, 163 const nsAString& aPolicyString, 164 nsIPrincipal* aSelfOrigin, 165 nsIPrincipal* aSrcOrigin) { 166 ResetDeclaredPolicy(); 167 168 mDeclaredString = aPolicyString; 169 mSelfOrigin = aSelfOrigin; 170 mSrcOrigin = aSrcOrigin; 171 172 (void)NS_WARN_IF(!FeaturePolicyParser::ParseString( 173 aPolicyString, aDocument, aSelfOrigin, aSrcOrigin, mFeatures)); 174 175 // Only store explicitly declared allowlist 176 for (const Feature& feature : mFeatures) { 177 if (feature.HasAllowList()) { 178 AppendToDeclaredAllowInAncestorChain(feature); 179 } 180 } 181 } 182 183 void FeaturePolicy::ResetDeclaredPolicy() { 184 mFeatures.Clear(); 185 mDeclaredString.Truncate(); 186 mSelfOrigin = nullptr; 187 mSrcOrigin = nullptr; 188 mDeclaredFeaturesInAncestorChain.Clear(); 189 mAttributeEnabledFeatureNames.Clear(); 190 } 191 192 JSObject* FeaturePolicy::WrapObject(JSContext* aCx, 193 JS::Handle<JSObject*> aGivenProto) { 194 return FeaturePolicy_Binding::Wrap(aCx, this, aGivenProto); 195 } 196 197 bool FeaturePolicy::AllowsFeature(const nsAString& aFeatureName, 198 const Optional<nsAString>& aOrigin) const { 199 nsCOMPtr<nsIPrincipal> origin; 200 if (aOrigin.WasPassed()) { 201 nsCOMPtr<nsIURI> uri; 202 nsresult rv = NS_NewURI(getter_AddRefs(uri), aOrigin.Value()); 203 if (NS_FAILED(rv)) { 204 return false; 205 } 206 origin = BasePrincipal::CreateContentPrincipal( 207 uri, BasePrincipal::Cast(mDefaultOrigin)->OriginAttributesRef()); 208 } else { 209 origin = mDefaultOrigin; 210 } 211 212 if (NS_WARN_IF(!origin)) { 213 return false; 214 } 215 216 return AllowsFeatureInternal(aFeatureName, origin); 217 } 218 219 bool FeaturePolicy::AllowsFeatureExplicitlyInAncestorChain( 220 const nsAString& aFeatureName, nsIPrincipal* aOrigin) const { 221 MOZ_ASSERT(aOrigin); 222 223 for (const Feature& feature : mDeclaredFeaturesInAncestorChain) { 224 if (feature.Name().Equals(aFeatureName)) { 225 return feature.AllowListContains(aOrigin); 226 } 227 } 228 229 return false; 230 } 231 232 bool FeaturePolicy::AllowsFeatureInternal(const nsAString& aFeatureName, 233 nsIPrincipal* aOrigin) const { 234 MOZ_ASSERT(aOrigin); 235 236 // Let's see if have to disable this feature because inherited policy. 237 if (HasInheritedDeniedFeature(aFeatureName)) { 238 return false; 239 } 240 241 for (const Feature& feature : mFeatures) { 242 if (feature.Name().Equals(aFeatureName)) { 243 return feature.Allows(aOrigin); 244 } 245 } 246 247 switch (FeaturePolicyUtils::DefaultAllowListFeature(aFeatureName)) { 248 case FeaturePolicyUtils::FeaturePolicyValue::eAll: 249 return true; 250 251 case FeaturePolicyUtils::FeaturePolicyValue::eSelf: 252 return BasePrincipal::Cast(mDefaultOrigin) 253 ->Subsumes(aOrigin, BasePrincipal::ConsiderDocumentDomain); 254 255 case FeaturePolicyUtils::FeaturePolicyValue::eNone: 256 return false; 257 258 default: 259 MOZ_CRASH("Unknown default value"); 260 } 261 262 return false; 263 } 264 265 void FeaturePolicy::Features(nsTArray<nsString>& aFeatures) { 266 RefPtr<FeaturePolicy> self = this; 267 FeaturePolicyUtils::ForEachFeature( 268 [self, &aFeatures](const char* aFeatureName) { 269 nsString featureName; 270 featureName.AppendASCII(aFeatureName); 271 aFeatures.AppendElement(featureName); 272 }); 273 } 274 275 void FeaturePolicy::AllowedFeatures(nsTArray<nsString>& aAllowedFeatures) { 276 RefPtr<FeaturePolicy> self = this; 277 FeaturePolicyUtils::ForEachFeature( 278 [self, &aAllowedFeatures](const char* aFeatureName) { 279 nsString featureName; 280 featureName.AppendASCII(aFeatureName); 281 282 if (self->AllowsFeatureInternal(featureName, self->mDefaultOrigin)) { 283 aAllowedFeatures.AppendElement(featureName); 284 } 285 }); 286 } 287 288 void FeaturePolicy::GetAllowlistForFeature(const nsAString& aFeatureName, 289 nsTArray<nsString>& aList) const { 290 if (!AllowsFeatureInternal(aFeatureName, mDefaultOrigin)) { 291 return; 292 } 293 294 for (const Feature& feature : mFeatures) { 295 if (feature.Name().Equals(aFeatureName)) { 296 if (feature.AllowsAll()) { 297 aList.AppendElement(u"*"_ns); 298 return; 299 } 300 301 nsTArray<nsCOMPtr<nsIPrincipal>> list; 302 feature.GetAllowList(list); 303 304 for (nsIPrincipal* principal : list) { 305 nsAutoCString originNoSuffix; 306 nsresult rv = principal->GetOriginNoSuffix(originNoSuffix); 307 if (NS_WARN_IF(NS_FAILED(rv))) { 308 return; 309 } 310 311 aList.AppendElement(NS_ConvertUTF8toUTF16(originNoSuffix)); 312 } 313 return; 314 } 315 } 316 317 switch (FeaturePolicyUtils::DefaultAllowListFeature(aFeatureName)) { 318 case FeaturePolicyUtils::FeaturePolicyValue::eAll: 319 aList.AppendElement(u"*"_ns); 320 return; 321 322 case FeaturePolicyUtils::FeaturePolicyValue::eSelf: { 323 nsAutoCString originNoSuffix; 324 nsresult rv = mDefaultOrigin->GetOriginNoSuffix(originNoSuffix); 325 if (NS_WARN_IF(NS_FAILED(rv))) { 326 return; 327 } 328 329 aList.AppendElement(NS_ConvertUTF8toUTF16(originNoSuffix)); 330 return; 331 } 332 333 case FeaturePolicyUtils::FeaturePolicyValue::eNone: 334 return; 335 336 default: 337 MOZ_CRASH("Unknown default value"); 338 } 339 } 340 341 void FeaturePolicy::MaybeSetAllowedPolicy(const nsAString& aFeatureName) { 342 MOZ_ASSERT(FeaturePolicyUtils::IsSupportedFeature(aFeatureName) || 343 FeaturePolicyUtils::IsExperimentalFeature(aFeatureName)); 344 // Skip if feature is in experimental phase 345 if (!StaticPrefs::dom_security_featurePolicy_experimental_enabled() && 346 FeaturePolicyUtils::IsExperimentalFeature(aFeatureName)) { 347 return; 348 } 349 350 if (HasDeclaredFeature(aFeatureName)) { 351 return; 352 } 353 354 Feature feature(aFeatureName); 355 feature.SetAllowsAll(); 356 357 mFeatures.AppendElement(feature); 358 mAttributeEnabledFeatureNames.AppendElement(aFeatureName); 359 } 360 361 FeaturePolicyInfo FeaturePolicy::ToFeaturePolicyInfo() const { 362 return {mInheritedDeniedFeatureNames.Clone(), 363 mAttributeEnabledFeatureNames.Clone(), 364 mDeclaredString, 365 mDefaultOrigin, 366 mSelfOrigin, 367 mSrcOrigin}; 368 } 369 370 } // namespace mozilla::dom