diff options
Diffstat (limited to 'dom/security/featurepolicy/FeaturePolicy.cpp')
-rw-r--r-- | dom/security/featurepolicy/FeaturePolicy.cpp | 334 |
1 files changed, 334 insertions, 0 deletions
diff --git a/dom/security/featurepolicy/FeaturePolicy.cpp b/dom/security/featurepolicy/FeaturePolicy.cpp new file mode 100644 index 0000000000..cb5c1ea44a --- /dev/null +++ b/dom/security/featurepolicy/FeaturePolicy.cpp @@ -0,0 +1,334 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 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 "FeaturePolicy.h" +#include "mozilla/BasePrincipal.h" +#include "mozilla/dom/Feature.h" +#include "mozilla/dom/FeaturePolicyBinding.h" +#include "mozilla/dom/FeaturePolicyParser.h" +#include "mozilla/dom/FeaturePolicyUtils.h" +#include "mozilla/StaticPrefs_dom.h" +#include "nsContentUtils.h" +#include "nsNetUtil.h" + +namespace mozilla::dom { + +NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(FeaturePolicy) +NS_IMPL_CYCLE_COLLECTING_ADDREF(FeaturePolicy) +NS_IMPL_CYCLE_COLLECTING_RELEASE(FeaturePolicy) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(FeaturePolicy) + NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +FeaturePolicy::FeaturePolicy(nsINode* aNode) : mParentNode(aNode) {} + +void FeaturePolicy::InheritPolicy(FeaturePolicy* aParentPolicy) { + MOZ_ASSERT(aParentPolicy); + + mInheritedDeniedFeatureNames.Clear(); + + RefPtr<FeaturePolicy> dest = this; + RefPtr<FeaturePolicy> src = aParentPolicy; + + // Inherit origins which explicitly declared policy in chain + for (const Feature& featureInChain : + aParentPolicy->mDeclaredFeaturesInAncestorChain) { + dest->AppendToDeclaredAllowInAncestorChain(featureInChain); + } + + FeaturePolicyUtils::ForEachFeature([dest, src](const char* aFeatureName) { + nsString featureName; + featureName.AppendASCII(aFeatureName); + // Store unsafe allows all (allow=*) + if (src->HasFeatureUnsafeAllowsAll(featureName)) { + dest->mParentAllowedAllFeatures.AppendElement(featureName); + } + + // If the destination has a declared feature (via the HTTP header or 'allow' + // attribute) we allow the feature if the destination allows it and the + // parent allows its origin or the destinations' one. + if (dest->HasDeclaredFeature(featureName) && + dest->AllowsFeatureInternal(featureName, dest->mDefaultOrigin)) { + if (!src->AllowsFeatureInternal(featureName, src->mDefaultOrigin) && + !src->AllowsFeatureInternal(featureName, dest->mDefaultOrigin)) { + dest->SetInheritedDeniedFeature(featureName); + } + return; + } + + // If there was not a declared feature, we allow the feature if the parent + // FeaturePolicy allows the current origin. + if (!src->AllowsFeatureInternal(featureName, dest->mDefaultOrigin)) { + dest->SetInheritedDeniedFeature(featureName); + } + }); +} + +void FeaturePolicy::SetInheritedDeniedFeature(const nsAString& aFeatureName) { + MOZ_ASSERT(!HasInheritedDeniedFeature(aFeatureName)); + mInheritedDeniedFeatureNames.AppendElement(aFeatureName); +} + +bool FeaturePolicy::HasInheritedDeniedFeature( + const nsAString& aFeatureName) const { + return mInheritedDeniedFeatureNames.Contains(aFeatureName); +} + +bool FeaturePolicy::HasDeclaredFeature(const nsAString& aFeatureName) const { + for (const Feature& feature : mFeatures) { + if (feature.Name().Equals(aFeatureName)) { + return true; + } + } + + return false; +} + +bool FeaturePolicy::HasFeatureUnsafeAllowsAll( + const nsAString& aFeatureName) const { + for (const Feature& feature : mFeatures) { + if (feature.AllowsAll() && feature.Name().Equals(aFeatureName)) { + return true; + } + } + + // We should look into parent too (for example, document of iframe which + // allows all, would be unsafe) + return mParentAllowedAllFeatures.Contains(aFeatureName); +} + +void FeaturePolicy::AppendToDeclaredAllowInAncestorChain( + const Feature& aFeature) { + for (Feature& featureInChain : mDeclaredFeaturesInAncestorChain) { + if (featureInChain.Name().Equals(aFeature.Name())) { + MOZ_ASSERT(featureInChain.HasAllowList()); + + nsTArray<nsCOMPtr<nsIPrincipal>> list; + aFeature.GetAllowList(list); + + for (nsIPrincipal* principal : list) { + featureInChain.AppendToAllowList(principal); + } + continue; + } + } + + mDeclaredFeaturesInAncestorChain.AppendElement(aFeature); +} + +bool FeaturePolicy::IsSameOriginAsSrc(nsIPrincipal* aPrincipal) const { + MOZ_ASSERT(aPrincipal); + + if (!mSrcOrigin) { + return false; + } + + return BasePrincipal::Cast(mSrcOrigin) + ->Subsumes(aPrincipal, BasePrincipal::ConsiderDocumentDomain); +} + +void FeaturePolicy::SetDeclaredPolicy(Document* aDocument, + const nsAString& aPolicyString, + nsIPrincipal* aSelfOrigin, + nsIPrincipal* aSrcOrigin) { + ResetDeclaredPolicy(); + + mDeclaredString = aPolicyString; + mSelfOrigin = aSelfOrigin; + mSrcOrigin = aSrcOrigin; + + Unused << NS_WARN_IF(!FeaturePolicyParser::ParseString( + aPolicyString, aDocument, aSelfOrigin, aSrcOrigin, mFeatures)); + + // Only store explicitly declared allowlist + for (const Feature& feature : mFeatures) { + if (feature.HasAllowList()) { + AppendToDeclaredAllowInAncestorChain(feature); + } + } +} + +void FeaturePolicy::ResetDeclaredPolicy() { + mFeatures.Clear(); + mDeclaredString.Truncate(); + mSelfOrigin = nullptr; + mSrcOrigin = nullptr; + mDeclaredFeaturesInAncestorChain.Clear(); + mAttributeEnabledFeatureNames.Clear(); +} + +JSObject* FeaturePolicy::WrapObject(JSContext* aCx, + JS::Handle<JSObject*> aGivenProto) { + return FeaturePolicy_Binding::Wrap(aCx, this, aGivenProto); +} + +bool FeaturePolicy::AllowsFeature(const nsAString& aFeatureName, + const Optional<nsAString>& aOrigin) const { + nsCOMPtr<nsIPrincipal> origin; + if (aOrigin.WasPassed()) { + nsCOMPtr<nsIURI> uri; + nsresult rv = NS_NewURI(getter_AddRefs(uri), aOrigin.Value()); + if (NS_FAILED(rv)) { + return false; + } + origin = BasePrincipal::CreateContentPrincipal( + uri, BasePrincipal::Cast(mDefaultOrigin)->OriginAttributesRef()); + } else { + origin = mDefaultOrigin; + } + + if (NS_WARN_IF(!origin)) { + return false; + } + + return AllowsFeatureInternal(aFeatureName, origin); +} + +bool FeaturePolicy::AllowsFeatureExplicitlyInAncestorChain( + const nsAString& aFeatureName, nsIPrincipal* aOrigin) const { + MOZ_ASSERT(aOrigin); + + for (const Feature& feature : mDeclaredFeaturesInAncestorChain) { + if (feature.Name().Equals(aFeatureName)) { + return feature.AllowListContains(aOrigin); + } + } + + return false; +} + +bool FeaturePolicy::AllowsFeatureInternal(const nsAString& aFeatureName, + nsIPrincipal* aOrigin) const { + MOZ_ASSERT(aOrigin); + + // Let's see if have to disable this feature because inherited policy. + if (HasInheritedDeniedFeature(aFeatureName)) { + return false; + } + + for (const Feature& feature : mFeatures) { + if (feature.Name().Equals(aFeatureName)) { + return feature.Allows(aOrigin); + } + } + + switch (FeaturePolicyUtils::DefaultAllowListFeature(aFeatureName)) { + case FeaturePolicyUtils::FeaturePolicyValue::eAll: + return true; + + case FeaturePolicyUtils::FeaturePolicyValue::eSelf: + return BasePrincipal::Cast(mDefaultOrigin) + ->Subsumes(aOrigin, BasePrincipal::ConsiderDocumentDomain); + + case FeaturePolicyUtils::FeaturePolicyValue::eNone: + return false; + + default: + MOZ_CRASH("Unknown default value"); + } + + return false; +} + +void FeaturePolicy::Features(nsTArray<nsString>& aFeatures) { + RefPtr<FeaturePolicy> self = this; + FeaturePolicyUtils::ForEachFeature( + [self, &aFeatures](const char* aFeatureName) { + nsString featureName; + featureName.AppendASCII(aFeatureName); + aFeatures.AppendElement(featureName); + }); +} + +void FeaturePolicy::AllowedFeatures(nsTArray<nsString>& aAllowedFeatures) { + RefPtr<FeaturePolicy> self = this; + FeaturePolicyUtils::ForEachFeature( + [self, &aAllowedFeatures](const char* aFeatureName) { + nsString featureName; + featureName.AppendASCII(aFeatureName); + + if (self->AllowsFeatureInternal(featureName, self->mDefaultOrigin)) { + aAllowedFeatures.AppendElement(featureName); + } + }); +} + +void FeaturePolicy::GetAllowlistForFeature(const nsAString& aFeatureName, + nsTArray<nsString>& aList) const { + if (!AllowsFeatureInternal(aFeatureName, mDefaultOrigin)) { + return; + } + + for (const Feature& feature : mFeatures) { + if (feature.Name().Equals(aFeatureName)) { + if (feature.AllowsAll()) { + aList.AppendElement(u"*"_ns); + return; + } + + nsTArray<nsCOMPtr<nsIPrincipal>> list; + feature.GetAllowList(list); + + for (nsIPrincipal* principal : list) { + nsAutoCString originNoSuffix; + nsresult rv = principal->GetOriginNoSuffix(originNoSuffix); + if (NS_WARN_IF(NS_FAILED(rv))) { + return; + } + + aList.AppendElement(NS_ConvertUTF8toUTF16(originNoSuffix)); + } + return; + } + } + + switch (FeaturePolicyUtils::DefaultAllowListFeature(aFeatureName)) { + case FeaturePolicyUtils::FeaturePolicyValue::eAll: + aList.AppendElement(u"*"_ns); + return; + + case FeaturePolicyUtils::FeaturePolicyValue::eSelf: { + nsAutoCString originNoSuffix; + nsresult rv = mDefaultOrigin->GetOriginNoSuffix(originNoSuffix); + if (NS_WARN_IF(NS_FAILED(rv))) { + return; + } + + aList.AppendElement(NS_ConvertUTF8toUTF16(originNoSuffix)); + return; + } + + case FeaturePolicyUtils::FeaturePolicyValue::eNone: + return; + + default: + MOZ_CRASH("Unknown default value"); + } +} + +void FeaturePolicy::MaybeSetAllowedPolicy(const nsAString& aFeatureName) { + MOZ_ASSERT(FeaturePolicyUtils::IsSupportedFeature(aFeatureName) || + FeaturePolicyUtils::IsExperimentalFeature(aFeatureName)); + // Skip if feature is in experimental phase + if (!StaticPrefs::dom_security_featurePolicy_experimental_enabled() && + FeaturePolicyUtils::IsExperimentalFeature(aFeatureName)) { + return; + } + + if (HasDeclaredFeature(aFeatureName)) { + return; + } + + Feature feature(aFeatureName); + feature.SetAllowsAll(); + + mFeatures.AppendElement(feature); + mAttributeEnabledFeatureNames.AppendElement(aFeatureName); +} + +} // namespace mozilla::dom |