FeaturePolicyUtils.cpp (9369B)
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 "FeaturePolicyUtils.h" 8 9 #include "ipc/IPCMessageUtilsSpecializations.h" 10 #include "mozilla/StaticPrefs_dom.h" 11 #include "mozilla/dom/BrowsingContext.h" 12 #include "mozilla/dom/Document.h" 13 #include "mozilla/dom/FeaturePolicyViolationReportBody.h" 14 #include "mozilla/dom/PermissionMessageUtils.h" 15 #include "mozilla/dom/ReportingUtils.h" 16 #include "nsContentUtils.h" 17 #include "nsIOService.h" 18 #include "nsJSUtils.h" 19 20 namespace mozilla { 21 namespace dom { 22 23 struct FeatureMap { 24 const char* mFeatureName; 25 FeaturePolicyUtils::FeaturePolicyValue mDefaultAllowList; 26 }; 27 28 /* 29 * IMPORTANT: Do not change this list without review from a DOM peer _AND_ a 30 * DOM Security peer! 31 */ 32 static FeatureMap sSupportedFeatures[] = { 33 {"camera", FeaturePolicyUtils::FeaturePolicyValue::eSelf}, 34 {"geolocation", FeaturePolicyUtils::FeaturePolicyValue::eSelf}, 35 {"microphone", FeaturePolicyUtils::FeaturePolicyValue::eSelf}, 36 {"display-capture", FeaturePolicyUtils::FeaturePolicyValue::eSelf}, 37 {"fullscreen", FeaturePolicyUtils::FeaturePolicyValue::eSelf}, 38 {"web-share", FeaturePolicyUtils::FeaturePolicyValue::eSelf}, 39 {"gamepad", FeaturePolicyUtils::FeaturePolicyValue::eAll}, 40 {"publickey-credentials-create", 41 FeaturePolicyUtils::FeaturePolicyValue::eSelf}, 42 {"publickey-credentials-get", 43 FeaturePolicyUtils::FeaturePolicyValue::eSelf}, 44 {"speaker-selection", FeaturePolicyUtils::FeaturePolicyValue::eSelf}, 45 {"storage-access", FeaturePolicyUtils::FeaturePolicyValue::eAll}, 46 {"screen-wake-lock", FeaturePolicyUtils::FeaturePolicyValue::eSelf}, 47 }; 48 49 /* 50 * This is experimental features list, which is disabled by default by pref 51 * dom.security.featurePolicy.experimental.enabled. 52 */ 53 static FeatureMap sExperimentalFeatures[] = { 54 // We don't support 'autoplay' for now, because it would be overwrote by 55 // 'user-gesture-activation' policy. However, we can still keep it in the 56 // list as we might start supporting it after we use different autoplay 57 // policy. 58 {"autoplay", FeaturePolicyUtils::FeaturePolicyValue::eAll}, 59 {"encrypted-media", FeaturePolicyUtils::FeaturePolicyValue::eAll}, 60 {"midi", FeaturePolicyUtils::FeaturePolicyValue::eSelf}, 61 {"payment", FeaturePolicyUtils::FeaturePolicyValue::eAll}, 62 {"document-domain", FeaturePolicyUtils::FeaturePolicyValue::eAll}, 63 {"vr", FeaturePolicyUtils::FeaturePolicyValue::eAll}, 64 // https://immersive-web.github.io/webxr/#feature-policy 65 {"xr-spatial-tracking", FeaturePolicyUtils::FeaturePolicyValue::eSelf}, 66 }; 67 68 /* static */ 69 bool FeaturePolicyUtils::IsExperimentalFeature(const nsAString& aFeatureName) { 70 uint32_t numFeatures = 71 (sizeof(sExperimentalFeatures) / sizeof(sExperimentalFeatures[0])); 72 for (uint32_t i = 0; i < numFeatures; ++i) { 73 if (aFeatureName.LowerCaseEqualsASCII( 74 sExperimentalFeatures[i].mFeatureName)) { 75 return true; 76 } 77 } 78 79 return false; 80 } 81 82 /* static */ 83 bool FeaturePolicyUtils::IsSupportedFeature(const nsAString& aFeatureName) { 84 uint32_t numFeatures = 85 (sizeof(sSupportedFeatures) / sizeof(sSupportedFeatures[0])); 86 for (uint32_t i = 0; i < numFeatures; ++i) { 87 if (aFeatureName.LowerCaseEqualsASCII(sSupportedFeatures[i].mFeatureName)) { 88 return true; 89 } 90 } 91 92 return StaticPrefs::dom_security_featurePolicy_experimental_enabled() && 93 IsExperimentalFeature(aFeatureName); 94 } 95 96 /* static */ 97 void FeaturePolicyUtils::ForEachFeature( 98 const std::function<void(const char*)>& aCallback) { 99 uint32_t numFeatures = 100 (sizeof(sSupportedFeatures) / sizeof(sSupportedFeatures[0])); 101 for (uint32_t i = 0; i < numFeatures; ++i) { 102 aCallback(sSupportedFeatures[i].mFeatureName); 103 } 104 105 if (StaticPrefs::dom_security_featurePolicy_experimental_enabled()) { 106 numFeatures = 107 (sizeof(sExperimentalFeatures) / sizeof(sExperimentalFeatures[0])); 108 for (uint32_t i = 0; i < numFeatures; ++i) { 109 aCallback(sExperimentalFeatures[i].mFeatureName); 110 } 111 } 112 } 113 114 /* static */ FeaturePolicyUtils::FeaturePolicyValue 115 FeaturePolicyUtils::DefaultAllowListFeature(const nsAString& aFeatureName) { 116 uint32_t numFeatures = 117 (sizeof(sSupportedFeatures) / sizeof(sSupportedFeatures[0])); 118 for (uint32_t i = 0; i < numFeatures; ++i) { 119 if (aFeatureName.LowerCaseEqualsASCII(sSupportedFeatures[i].mFeatureName)) { 120 return sSupportedFeatures[i].mDefaultAllowList; 121 } 122 } 123 124 if (StaticPrefs::dom_security_featurePolicy_experimental_enabled()) { 125 numFeatures = 126 (sizeof(sExperimentalFeatures) / sizeof(sExperimentalFeatures[0])); 127 for (uint32_t i = 0; i < numFeatures; ++i) { 128 if (aFeatureName.LowerCaseEqualsASCII( 129 sExperimentalFeatures[i].mFeatureName)) { 130 return sExperimentalFeatures[i].mDefaultAllowList; 131 } 132 } 133 } 134 135 return FeaturePolicyValue::eNone; 136 } 137 138 static bool IsSameOriginAsTop(Document* aDocument) { 139 MOZ_ASSERT(aDocument); 140 141 BrowsingContext* browsingContext = aDocument->GetBrowsingContext(); 142 if (!browsingContext) { 143 return false; 144 } 145 146 nsPIDOMWindowOuter* topWindow = browsingContext->Top()->GetDOMWindow(); 147 if (!topWindow) { 148 // If we don't have a DOMWindow, We are not in same origin. 149 return false; 150 } 151 152 Document* topLevelDocument = topWindow->GetExtantDoc(); 153 if (!topLevelDocument) { 154 return false; 155 } 156 157 return NS_SUCCEEDED( 158 nsContentUtils::CheckSameOrigin(topLevelDocument, aDocument)); 159 } 160 161 /* static */ 162 bool FeaturePolicyUtils::IsFeatureUnsafeAllowedAll( 163 Document* aDocument, const nsAString& aFeatureName) { 164 MOZ_ASSERT(aDocument); 165 166 if (!aDocument->IsHTMLDocument()) { 167 return false; 168 } 169 170 FeaturePolicy* policy = aDocument->FeaturePolicy(); 171 MOZ_ASSERT(policy); 172 173 return policy->HasFeatureUnsafeAllowsAll(aFeatureName) && 174 !policy->IsSameOriginAsSrc(aDocument->NodePrincipal()) && 175 !policy->AllowsFeatureExplicitlyInAncestorChain( 176 aFeatureName, policy->DefaultOrigin()) && 177 !IsSameOriginAsTop(aDocument); 178 } 179 180 /* static */ 181 bool FeaturePolicyUtils::IsFeatureAllowed(Document* aDocument, 182 const nsAString& aFeatureName) { 183 MOZ_ASSERT(aDocument); 184 185 // Skip apply features in experimental phase 186 if (!StaticPrefs::dom_security_featurePolicy_experimental_enabled() && 187 IsExperimentalFeature(aFeatureName)) { 188 return true; 189 } 190 191 FeaturePolicy* policy = aDocument->FeaturePolicy(); 192 MOZ_ASSERT(policy); 193 194 if (policy->AllowsFeatureInternal(aFeatureName, policy->DefaultOrigin())) { 195 return true; 196 } 197 198 ReportViolation(aDocument, aFeatureName); 199 return false; 200 } 201 202 /* static */ 203 void FeaturePolicyUtils::ReportViolation(Document* aDocument, 204 const nsAString& aFeatureName) { 205 MOZ_ASSERT(aDocument); 206 207 nsCOMPtr<nsIURI> uri = aDocument->GetDocumentURI(); 208 if (NS_WARN_IF(!uri)) { 209 return; 210 } 211 212 // Strip the URL of any possible username/password and make it ready to be 213 // presented in the UI. 214 nsCOMPtr<nsIURI> exposableURI = net::nsIOService::CreateExposableURI(uri); 215 nsAutoCString spec; 216 nsresult rv = exposableURI->GetSpec(spec); 217 if (NS_WARN_IF(NS_FAILED(rv))) { 218 return; 219 } 220 JSContext* cx = nsContentUtils::GetCurrentJSContext(); 221 if (NS_WARN_IF(!cx)) { 222 return; 223 } 224 225 Nullable<int32_t> lineNumber; 226 Nullable<int32_t> columnNumber; 227 auto loc = JSCallingLocation::Get(); 228 if (loc) { 229 lineNumber.SetValue(static_cast<int32_t>(loc.mLine)); 230 columnNumber.SetValue(static_cast<int32_t>(loc.mColumn)); 231 } 232 233 nsPIDOMWindowInner* window = aDocument->GetInnerWindow(); 234 if (NS_WARN_IF(!window)) { 235 return; 236 } 237 238 RefPtr<FeaturePolicyViolationReportBody> body = 239 new FeaturePolicyViolationReportBody(window->AsGlobal(), aFeatureName, 240 loc.FileName(), lineNumber, 241 columnNumber, u"enforce"_ns); 242 243 ReportingUtils::Report(window->AsGlobal(), nsGkAtoms::featurePolicyViolation, 244 u"default"_ns, NS_ConvertUTF8toUTF16(spec), body); 245 } 246 247 } // namespace dom 248 } // namespace mozilla 249 250 namespace IPC { 251 252 void ParamTraits<mozilla::dom::FeaturePolicyInfo>::Write( 253 MessageWriter* aWriter, const mozilla::dom::FeaturePolicyInfo& aParam) { 254 WriteParam(aWriter, aParam.mInheritedDeniedFeatureNames); 255 WriteParam(aWriter, aParam.mAttributeEnabledFeatureNames); 256 WriteParam(aWriter, aParam.mDeclaredString); 257 WriteParam(aWriter, aParam.mDefaultOrigin); 258 WriteParam(aWriter, aParam.mSelfOrigin); 259 WriteParam(aWriter, aParam.mSrcOrigin); 260 } 261 262 bool ParamTraits<mozilla::dom::FeaturePolicyInfo>::Read( 263 MessageReader* aReader, mozilla::dom::FeaturePolicyInfo* aResult) { 264 return ReadParam(aReader, &aResult->mInheritedDeniedFeatureNames) && 265 ReadParam(aReader, &aResult->mAttributeEnabledFeatureNames) && 266 ReadParam(aReader, &aResult->mDeclaredString) && 267 ReadParam(aReader, &aResult->mDefaultOrigin) && 268 ReadParam(aReader, &aResult->mSelfOrigin) && 269 ReadParam(aReader, &aResult->mSrcOrigin); 270 } 271 272 } // namespace IPC