/* 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 "ipc/IPCMessageUtilsSpecializations.h"
#include "mozilla/StaticPrefs_dom.h"
#include "mozilla/dom/BrowsingContext.h"
#include "mozilla/dom/Document.h"
#include "mozilla/dom/FeaturePolicyViolationReportBody.h"
#include "mozilla/dom/PermissionMessageUtils.h"
#include "mozilla/dom/ReportingUtils.h"
#include "nsContentUtils.h"
#include "nsIOService.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},
{
"digital-credentials-create",
FeaturePolicyUtils::FeaturePolicyValue::eSelf},
{
"digital-credentials-get", FeaturePolicyUtils::FeaturePolicyValue::eSelf},
{
"display-capture", FeaturePolicyUtils::FeaturePolicyValue::eSelf},
{
"fullscreen", FeaturePolicyUtils::FeaturePolicyValue::eSelf},
{
"web-share", FeaturePolicyUtils::FeaturePolicyValue::eSelf},
{
"gamepad", FeaturePolicyUtils::FeaturePolicyValue::eAll},
{
"publickey-credentials-create",
FeaturePolicyUtils::FeaturePolicyValue::eSelf},
{
"publickey-credentials-get",
FeaturePolicyUtils::FeaturePolicyValue::eSelf},
{
"serial", FeaturePolicyUtils::FeaturePolicyValue::eSelf},
{
"speaker-selection", FeaturePolicyUtils::FeaturePolicyValue::eSelf},
{
"storage-access", FeaturePolicyUtils::FeaturePolicyValue::eAll},
{
"screen-wake-lock", FeaturePolicyUtils::FeaturePolicyValue::eSelf},
{
"loopback-network", FeaturePolicyUtils::FeaturePolicyValue::eSelf},
{
"local-network", FeaturePolicyUtils::FeaturePolicyValue::eSelf},
{
"aria-notify", FeaturePolicyUtils::FeaturePolicyValue::eAll},
{
"picture-in-picture", FeaturePolicyUtils::FeaturePolicyValue::eAll},
};
/*
* 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;
}
nsAutoCString url;
ReportingUtils::StripURL(uri, url);
JSContext* cx = nsContentUtils::GetCurrentJSContext();
if (NS_WARN_IF(!cx)) {
return;
}
Nullable<int32_t> lineNumber;
Nullable<int32_t> columnNumber;
nsAutoCString sourceFile;
if (
auto loc = JSCallingLocation::Get()) {
lineNumber.SetValue(static_cast<int32_t>(loc.mLine));
columnNumber.SetValue(static_cast<int32_t>(loc.mColumn));
ReportingUtils::StripLocationFileName(loc, sourceFile);
}
nsPIDOMWindowInner* window = aDocument->GetInnerWindow();
if (NS_WARN_IF(!window)) {
return;
}
RefPtr<FeaturePolicyViolationReportBody> body =
new FeaturePolicyViolationReportBody(window->AsGlobal(), aFeatureName,
sourceFile, lineNumber, columnNumber,
u
"enforce"_ns);
ReportingUtils::Report(window->AsGlobal(), nsGkAtoms::featurePolicyViolation,
u
"default"_ns, NS_ConvertUTF8toUTF16(url), body);
}
}
// namespace dom
}
// namespace mozilla
namespace IPC {
void ParamTraits<mozilla::dom::FeaturePolicyInfo>::Write(
MessageWriter* aWriter,
const mozilla::dom::FeaturePolicyInfo& aParam) {
WriteParam(aWriter, aParam.mInheritedDeniedFeatureNames);
WriteParam(aWriter, aParam.mAttributeEnabledFeatureNames);
WriteParam(aWriter, aParam.mDeclaredString);
WriteParam(aWriter, aParam.mDefaultOrigin);
WriteParam(aWriter, aParam.mSelfOrigin);
WriteParam(aWriter, aParam.mSrcOrigin);
}
bool ParamTraits<mozilla::dom::FeaturePolicyInfo>::Read(
MessageReader* aReader, mozilla::dom::FeaturePolicyInfo* aResult) {
return ReadParam(aReader, &aResult->mInheritedDeniedFeatureNames) &&
ReadParam(aReader, &aResult->mAttributeEnabledFeatureNames) &&
ReadParam(aReader, &aResult->mDeclaredString) &&
ReadParam(aReader, &aResult->mDefaultOrigin) &&
ReadParam(aReader, &aResult->mSelfOrigin) &&
ReadParam(aReader, &aResult->mSrcOrigin);
}
}
// namespace IPC