diff options
Diffstat (limited to 'dom/security/featurepolicy/FeaturePolicyUtils.cpp')
-rw-r--r-- | dom/security/featurepolicy/FeaturePolicyUtils.cpp | 309 |
1 files changed, 309 insertions, 0 deletions
diff --git a/dom/security/featurepolicy/FeaturePolicyUtils.cpp b/dom/security/featurepolicy/FeaturePolicyUtils.cpp new file mode 100644 index 0000000000..1b2a383642 --- /dev/null +++ b/dom/security/featurepolicy/FeaturePolicyUtils.cpp @@ -0,0 +1,309 @@ +/* -*- 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 "FeaturePolicyUtils.h" +#include "nsIOService.h" + +#include "mozilla/dom/DOMTypes.h" +#include "mozilla/ipc/IPDLParamTraits.h" +#include "mozilla/dom/FeaturePolicyViolationReportBody.h" +#include "mozilla/dom/ReportingUtils.h" +#include "mozilla/StaticPrefs_dom.h" +#include "mozilla/dom/Document.h" +#include "nsContentUtils.h" +#include "nsJSUtils.h" + +namespace mozilla { +namespace dom { + +struct FeatureMap { + const char* mFeatureName; + FeaturePolicyUtils::FeaturePolicyValue mDefaultAllowList; +}; + +/* + * IMPORTANT: Do not change this list without review from a DOM peer _AND_ a + * DOM Security peer! + */ +static FeatureMap sSupportedFeatures[] = { + {"camera", FeaturePolicyUtils::FeaturePolicyValue::eSelf}, + {"geolocation", FeaturePolicyUtils::FeaturePolicyValue::eSelf}, + {"microphone", FeaturePolicyUtils::FeaturePolicyValue::eSelf}, + {"display-capture", FeaturePolicyUtils::FeaturePolicyValue::eSelf}, + {"fullscreen", FeaturePolicyUtils::FeaturePolicyValue::eSelf}, + {"web-share", FeaturePolicyUtils::FeaturePolicyValue::eSelf}, + {"gamepad", FeaturePolicyUtils::FeaturePolicyValue::eAll}, + {"speaker-selection", FeaturePolicyUtils::FeaturePolicyValue::eSelf}, +}; + +/* + * This is experimental features list, which is disabled by default by pref + * dom.security.featurePolicy.experimental.enabled. + */ +static FeatureMap sExperimentalFeatures[] = { + // We don't support 'autoplay' for now, because it would be overwrote by + // 'user-gesture-activation' policy. However, we can still keep it in the + // list as we might start supporting it after we use different autoplay + // policy. + {"autoplay", FeaturePolicyUtils::FeaturePolicyValue::eAll}, + {"encrypted-media", FeaturePolicyUtils::FeaturePolicyValue::eAll}, + {"midi", FeaturePolicyUtils::FeaturePolicyValue::eSelf}, + {"payment", FeaturePolicyUtils::FeaturePolicyValue::eAll}, + {"document-domain", FeaturePolicyUtils::FeaturePolicyValue::eAll}, + {"vr", FeaturePolicyUtils::FeaturePolicyValue::eAll}, + // https://immersive-web.github.io/webxr/#feature-policy + {"xr-spatial-tracking", FeaturePolicyUtils::FeaturePolicyValue::eSelf}, +}; + +/* static */ +bool FeaturePolicyUtils::IsExperimentalFeature(const nsAString& aFeatureName) { + uint32_t numFeatures = + (sizeof(sExperimentalFeatures) / sizeof(sExperimentalFeatures[0])); + for (uint32_t i = 0; i < numFeatures; ++i) { + if (aFeatureName.LowerCaseEqualsASCII( + sExperimentalFeatures[i].mFeatureName)) { + return true; + } + } + + return false; +} + +/* static */ +bool FeaturePolicyUtils::IsSupportedFeature(const nsAString& aFeatureName) { + uint32_t numFeatures = + (sizeof(sSupportedFeatures) / sizeof(sSupportedFeatures[0])); + for (uint32_t i = 0; i < numFeatures; ++i) { + if (aFeatureName.LowerCaseEqualsASCII(sSupportedFeatures[i].mFeatureName)) { + return true; + } + } + + return StaticPrefs::dom_security_featurePolicy_experimental_enabled() && + IsExperimentalFeature(aFeatureName); +} + +/* static */ +void FeaturePolicyUtils::ForEachFeature( + const std::function<void(const char*)>& aCallback) { + uint32_t numFeatures = + (sizeof(sSupportedFeatures) / sizeof(sSupportedFeatures[0])); + for (uint32_t i = 0; i < numFeatures; ++i) { + aCallback(sSupportedFeatures[i].mFeatureName); + } + + if (StaticPrefs::dom_security_featurePolicy_experimental_enabled()) { + numFeatures = + (sizeof(sExperimentalFeatures) / sizeof(sExperimentalFeatures[0])); + for (uint32_t i = 0; i < numFeatures; ++i) { + aCallback(sExperimentalFeatures[i].mFeatureName); + } + } +} + +/* static */ FeaturePolicyUtils::FeaturePolicyValue +FeaturePolicyUtils::DefaultAllowListFeature(const nsAString& aFeatureName) { + uint32_t numFeatures = + (sizeof(sSupportedFeatures) / sizeof(sSupportedFeatures[0])); + for (uint32_t i = 0; i < numFeatures; ++i) { + if (aFeatureName.LowerCaseEqualsASCII(sSupportedFeatures[i].mFeatureName)) { + return sSupportedFeatures[i].mDefaultAllowList; + } + } + + if (StaticPrefs::dom_security_featurePolicy_experimental_enabled()) { + numFeatures = + (sizeof(sExperimentalFeatures) / sizeof(sExperimentalFeatures[0])); + for (uint32_t i = 0; i < numFeatures; ++i) { + if (aFeatureName.LowerCaseEqualsASCII( + sExperimentalFeatures[i].mFeatureName)) { + return sExperimentalFeatures[i].mDefaultAllowList; + } + } + } + + return FeaturePolicyValue::eNone; +} + +static bool IsSameOriginAsTop(Document* aDocument) { + MOZ_ASSERT(aDocument); + + BrowsingContext* browsingContext = aDocument->GetBrowsingContext(); + if (!browsingContext) { + return false; + } + + nsPIDOMWindowOuter* topWindow = browsingContext->Top()->GetDOMWindow(); + if (!topWindow) { + // If we don't have a DOMWindow, We are not in same origin. + return false; + } + + Document* topLevelDocument = topWindow->GetExtantDoc(); + if (!topLevelDocument) { + return false; + } + + return NS_SUCCEEDED( + nsContentUtils::CheckSameOrigin(topLevelDocument, aDocument)); +} + +/* static */ +bool FeaturePolicyUtils::IsFeatureUnsafeAllowedAll( + Document* aDocument, const nsAString& aFeatureName) { + MOZ_ASSERT(aDocument); + + if (!aDocument->IsHTMLDocument()) { + return false; + } + + FeaturePolicy* policy = aDocument->FeaturePolicy(); + MOZ_ASSERT(policy); + + return policy->HasFeatureUnsafeAllowsAll(aFeatureName) && + !policy->IsSameOriginAsSrc(aDocument->NodePrincipal()) && + !policy->AllowsFeatureExplicitlyInAncestorChain( + aFeatureName, policy->DefaultOrigin()) && + !IsSameOriginAsTop(aDocument); +} + +/* static */ +bool FeaturePolicyUtils::IsFeatureAllowed(Document* aDocument, + const nsAString& aFeatureName) { + MOZ_ASSERT(aDocument); + + // Skip apply features in experimental phase + if (!StaticPrefs::dom_security_featurePolicy_experimental_enabled() && + IsExperimentalFeature(aFeatureName)) { + return true; + } + + FeaturePolicy* policy = aDocument->FeaturePolicy(); + MOZ_ASSERT(policy); + + if (policy->AllowsFeatureInternal(aFeatureName, policy->DefaultOrigin())) { + return true; + } + + ReportViolation(aDocument, aFeatureName); + return false; +} + +/* static */ +void FeaturePolicyUtils::ReportViolation(Document* aDocument, + const nsAString& aFeatureName) { + MOZ_ASSERT(aDocument); + + nsCOMPtr<nsIURI> uri = aDocument->GetDocumentURI(); + if (NS_WARN_IF(!uri)) { + return; + } + + // Strip the URL of any possible username/password and make it ready to be + // presented in the UI. + nsCOMPtr<nsIURI> exposableURI = net::nsIOService::CreateExposableURI(uri); + nsAutoCString spec; + nsresult rv = exposableURI->GetSpec(spec); + if (NS_WARN_IF(NS_FAILED(rv))) { + return; + } + JSContext* cx = nsContentUtils::GetCurrentJSContext(); + if (NS_WARN_IF(!cx)) { + return; + } + + nsAutoString fileName; + Nullable<int32_t> lineNumber; + Nullable<int32_t> columnNumber; + uint32_t line = 0; + uint32_t column = 0; + if (nsJSUtils::GetCallingLocation(cx, fileName, &line, &column)) { + lineNumber.SetValue(static_cast<int32_t>(line)); + columnNumber.SetValue(static_cast<int32_t>(column)); + } + + nsPIDOMWindowInner* window = aDocument->GetInnerWindow(); + if (NS_WARN_IF(!window)) { + return; + } + + RefPtr<FeaturePolicyViolationReportBody> body = + new FeaturePolicyViolationReportBody(window->AsGlobal(), aFeatureName, + fileName, lineNumber, columnNumber, + u"enforce"_ns); + + ReportingUtils::Report(window->AsGlobal(), nsGkAtoms::featurePolicyViolation, + u"default"_ns, NS_ConvertUTF8toUTF16(spec), body); +} + +} // namespace dom + +namespace ipc { +void IPDLParamTraits<dom::FeaturePolicy*>::Write(IPC::MessageWriter* aWriter, + IProtocol* aActor, + dom::FeaturePolicy* aParam) { + if (!aParam) { + WriteIPDLParam(aWriter, aActor, false); + return; + } + + WriteIPDLParam(aWriter, aActor, true); + + dom::FeaturePolicyInfo info; + info.defaultOrigin() = aParam->DefaultOrigin(); + info.selfOrigin() = aParam->GetSelfOrigin(); + info.srcOrigin() = aParam->GetSrcOrigin(); + + info.declaredString() = aParam->DeclaredString(); + info.inheritedDeniedFeatureNames() = + aParam->InheritedDeniedFeatureNames().Clone(); + info.attributeEnabledFeatureNames() = + aParam->AttributeEnabledFeatureNames().Clone(); + + WriteIPDLParam(aWriter, aActor, info); +} + +bool IPDLParamTraits<dom::FeaturePolicy*>::Read( + IPC::MessageReader* aReader, IProtocol* aActor, + RefPtr<dom::FeaturePolicy>* aResult) { + *aResult = nullptr; + bool notnull = false; + if (!ReadIPDLParam(aReader, aActor, ¬null)) { + return false; + } + + if (!notnull) { + return true; + } + + dom::FeaturePolicyInfo info; + if (!ReadIPDLParam(aReader, aActor, &info)) { + return false; + } + + // Note that we only do IPC for feature policy to inherit policy from parent + // to child document. That does not need to bind feature policy with a node. + RefPtr<dom::FeaturePolicy> featurePolicy = new dom::FeaturePolicy(nullptr); + featurePolicy->SetDefaultOrigin(info.defaultOrigin()); + featurePolicy->SetInheritedDeniedFeatureNames( + info.inheritedDeniedFeatureNames()); + + const auto& declaredString = info.declaredString(); + if (info.selfOrigin() && !declaredString.IsEmpty()) { + featurePolicy->SetDeclaredPolicy(nullptr, declaredString, info.selfOrigin(), + info.srcOrigin()); + } + + for (auto& featureName : info.attributeEnabledFeatureNames()) { + featurePolicy->MaybeSetAllowedPolicy(featureName); + } + + *aResult = std::move(featurePolicy); + return true; +} +} // namespace ipc + +} // namespace mozilla |