TrustedTypePolicyFactory.cpp (15174B)
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 "mozilla/dom/TrustedTypePolicyFactory.h" 8 9 #include <utility> 10 11 #include "mozilla/AlreadyAddRefed.h" 12 #include "mozilla/ErrorResult.h" 13 #include "mozilla/RefPtr.h" 14 #include "mozilla/dom/CSPViolationData.h" 15 #include "mozilla/dom/PolicyContainer.h" 16 #include "mozilla/dom/TrustedTypePolicy.h" 17 #include "mozilla/dom/TrustedTypeUtils.h" 18 #include "mozilla/dom/WorkerPrivate.h" 19 #include "mozilla/dom/WorkerRunnable.h" 20 #include "mozilla/dom/WorkerScope.h" 21 #include "mozilla/dom/nsCSPUtils.h" 22 #include "nsLiteralString.h" 23 24 using namespace mozilla::dom::TrustedTypeUtils; 25 26 namespace mozilla::dom { 27 28 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(TrustedTypePolicyFactory, mGlobalObject, 29 mDefaultPolicy) 30 31 TrustedTypePolicyFactory::TrustedTypePolicyFactory( 32 nsIGlobalObject* aGlobalObject) 33 : mGlobalObject{aGlobalObject} {} 34 35 JSObject* TrustedTypePolicyFactory::WrapObject( 36 JSContext* aCx, JS::Handle<JSObject*> aGivenProto) { 37 return TrustedTypePolicyFactory_Binding::Wrap(aCx, this, aGivenProto); 38 } 39 40 // Default implementation in the .cpp file required because `mDefaultPolicy` 41 // requires a definition for `TrustedTypePolicy`. 42 TrustedTypePolicyFactory::~TrustedTypePolicyFactory() = default; 43 44 // Implement reporting of violations for policy creation. 45 // https://w3c.github.io/trusted-types/dist/spec/#should-block-create-policy 46 static void ReportPolicyCreationViolations( 47 nsIContentSecurityPolicy* aCSP, nsICSPEventListener* aCSPEventListener, 48 const nsCString& aFileName, uint32_t aLine, uint32_t aColumn, 49 const nsTArray<nsString>& aCreatedPolicyNames, 50 const nsAString& aPolicyName) { 51 MOZ_ASSERT(aCSP); 52 uint32_t numPolicies = 0; 53 aCSP->GetPolicyCount(&numPolicies); 54 for (uint32_t i = 0; i < numPolicies; ++i) { 55 const nsCSPPolicy* policy = aCSP->GetPolicy(i); 56 if (policy->hasDirective( 57 nsIContentSecurityPolicy::TRUSTED_TYPES_DIRECTIVE)) { 58 if (policy->ShouldCreateViolationForNewTrustedTypesPolicy( 59 aPolicyName, aCreatedPolicyNames)) { 60 CSPViolationData cspViolationData{ 61 i, 62 CSPViolationData::Resource{ 63 CSPViolationData::BlockedContentSource::TrustedTypesPolicy}, 64 nsIContentSecurityPolicy::TRUSTED_TYPES_DIRECTIVE, 65 aFileName, 66 aLine, 67 aColumn, 68 /* aElement */ nullptr, 69 CSPViolationData::MaybeTruncateSample(aPolicyName)}; 70 aCSP->LogTrustedTypesViolationDetailsUnchecked( 71 std::move(cspViolationData), 72 NS_LITERAL_STRING_FROM_CSTRING( 73 TRUSTED_TYPES_VIOLATION_OBSERVER_TOPIC), 74 aCSPEventListener); 75 } 76 } 77 } 78 } 79 80 class LogPolicyCreationViolationsRunnable final 81 : public WorkerMainThreadRunnable { 82 public: 83 LogPolicyCreationViolationsRunnable( 84 WorkerPrivate* aWorker, const nsCString& aFileName, uint32_t aLine, 85 uint32_t aColumn, const nsTArray<nsString>& aCreatedPolicyNames, 86 const nsAString& aPolicyName) 87 : WorkerMainThreadRunnable( 88 aWorker, 89 "RuntimeService :: LogPolicyCreationViolationsRunnable"_ns), 90 mFileName(aFileName), 91 mLine(aLine), 92 mColumn(aColumn), 93 mCreatedPolicyNames(aCreatedPolicyNames), 94 mPolicyName(aPolicyName) { 95 MOZ_ASSERT(aWorker); 96 } 97 98 virtual bool MainThreadRun() override { 99 AssertIsOnMainThread(); 100 MOZ_ASSERT(mWorkerRef); 101 if (nsIContentSecurityPolicy* csp = mWorkerRef->Private()->GetCsp()) { 102 ReportPolicyCreationViolations( 103 csp, mWorkerRef->Private()->CSPEventListener(), mFileName, mLine, 104 mColumn, mCreatedPolicyNames, mPolicyName); 105 } 106 return true; 107 } 108 109 private: 110 ~LogPolicyCreationViolationsRunnable() = default; 111 const nsCString& mFileName; 112 uint32_t mLine; 113 uint32_t mColumn; 114 const nsTArray<nsString>& mCreatedPolicyNames; 115 const nsString mPolicyName; 116 }; 117 118 auto TrustedTypePolicyFactory::ShouldTrustedTypePolicyCreationBeBlockedByCSP( 119 JSContext* aJSContext, const nsAString& aPolicyName) const 120 -> PolicyCreation { 121 auto shouldBlock = [this, &aPolicyName](const nsCSPPolicy* aPolicy) { 122 return aPolicy->hasDirective( 123 nsIContentSecurityPolicy::TRUSTED_TYPES_DIRECTIVE) && 124 aPolicy->getDisposition() == nsCSPPolicy::Disposition::Enforce && 125 aPolicy->ShouldCreateViolationForNewTrustedTypesPolicy( 126 aPolicyName, mCreatedPolicyNames); 127 }; 128 129 auto result = PolicyCreation::Allowed; 130 auto location = JSCallingLocation::Get(aJSContext); 131 if (auto* piDOMWindowInner = mGlobalObject->GetAsInnerWindow()) { 132 if (auto* csp = 133 PolicyContainer::GetCSP(piDOMWindowInner->GetPolicyContainer())) { 134 ReportPolicyCreationViolations( 135 csp, nullptr /* aCSPEventListener */, location.FileName(), 136 location.mLine, location.mColumn, mCreatedPolicyNames, aPolicyName); 137 uint32_t numPolicies = 0; 138 csp->GetPolicyCount(&numPolicies); 139 for (uint64_t i = 0; i < numPolicies; ++i) { 140 const nsCSPPolicy* policy = csp->GetPolicy(i); 141 if (shouldBlock(policy)) { 142 result = PolicyCreation::Blocked; 143 break; 144 } 145 } 146 } 147 } else { 148 MOZ_ASSERT(IsWorkerGlobal(mGlobalObject->GetGlobalJSObject())); 149 MOZ_ASSERT(!NS_IsMainThread()); 150 WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate(); 151 RefPtr<LogPolicyCreationViolationsRunnable> runnable = 152 new LogPolicyCreationViolationsRunnable( 153 workerPrivate, location.FileName(), location.mLine, 154 location.mColumn, mCreatedPolicyNames, aPolicyName); 155 ErrorResult rv; 156 runnable->Dispatch(workerPrivate, Killing, rv); 157 if (NS_WARN_IF(rv.Failed())) { 158 rv.SuppressException(); 159 } 160 if (WorkerCSPContext* ctx = workerPrivate->GetCSPContext()) { 161 for (const UniquePtr<const nsCSPPolicy>& policy : ctx->Policies()) { 162 if (shouldBlock(policy.get())) { 163 result = PolicyCreation::Blocked; 164 break; 165 } 166 } 167 } 168 } 169 170 return result; 171 } 172 173 constexpr nsLiteralString kDefaultPolicyName = u"default"_ns; 174 175 already_AddRefed<TrustedTypePolicy> TrustedTypePolicyFactory::CreatePolicy( 176 JSContext* aJSContext, const nsAString& aPolicyName, 177 const TrustedTypePolicyOptions& aPolicyOptions, ErrorResult& aRv) { 178 if (PolicyCreation::Blocked == 179 ShouldTrustedTypePolicyCreationBeBlockedByCSP(aJSContext, aPolicyName)) { 180 nsCString errorMessage = 181 "Content-Security-Policy blocked creating policy named '"_ns + 182 NS_ConvertUTF16toUTF8(aPolicyName) + "'"_ns; 183 184 // TODO: perhaps throw different TypeError messages, 185 // https://github.com/w3c/trusted-types/issues/511. 186 aRv.ThrowTypeError(errorMessage); 187 return nullptr; 188 } 189 190 if (aPolicyName.Equals(kDefaultPolicyName) && mDefaultPolicy) { 191 aRv.ThrowTypeError("Tried to create a second default policy"_ns); 192 return nullptr; 193 } 194 195 TrustedTypePolicy::Options options; 196 197 if (aPolicyOptions.mCreateHTML.WasPassed()) { 198 options.mCreateHTMLCallback = &aPolicyOptions.mCreateHTML.Value(); 199 } 200 201 if (aPolicyOptions.mCreateScript.WasPassed()) { 202 options.mCreateScriptCallback = &aPolicyOptions.mCreateScript.Value(); 203 } 204 205 if (aPolicyOptions.mCreateScriptURL.WasPassed()) { 206 options.mCreateScriptURLCallback = &aPolicyOptions.mCreateScriptURL.Value(); 207 } 208 209 RefPtr<TrustedTypePolicy> policy = 210 MakeRefPtr<TrustedTypePolicy>(this, aPolicyName, std::move(options)); 211 212 if (aPolicyName.Equals(kDefaultPolicyName)) { 213 mDefaultPolicy = policy; 214 } 215 216 mCreatedPolicyNames.AppendElement(aPolicyName); 217 218 return policy.forget(); 219 } 220 221 #define IS_TRUSTED_TYPE_IMPL(_trustedTypeSuffix) \ 222 bool TrustedTypePolicyFactory::Is##_trustedTypeSuffix( \ 223 JSContext*, const JS::Handle<JS::Value>& aValue) const { \ 224 /** \ 225 * No need to check the internal slot. \ 226 * Ensured by the corresponding test: \ 227 * <https://searchfox.org/mozilla-central/rev/b60cb73160843adb5a5a3ec8058e75a69b46acf7/testing/web-platform/tests/trusted-types/TrustedTypePolicyFactory-isXXX.html> \ 228 */ \ 229 return aValue.isObject() && \ 230 IS_INSTANCE_OF(Trusted##_trustedTypeSuffix, &aValue.toObject()); \ 231 } 232 233 IS_TRUSTED_TYPE_IMPL(HTML); 234 IS_TRUSTED_TYPE_IMPL(Script); 235 IS_TRUSTED_TYPE_IMPL(ScriptURL); 236 237 already_AddRefed<TrustedHTML> TrustedTypePolicyFactory::EmptyHTML() { 238 // Preserving the wrapper ensures: 239 // ``` 240 // const e = trustedTypes.emptyHTML; 241 // e === trustedTypes.emptyHTML; 242 // ``` 243 // which comes with the cost of keeping the factory, one per global, alive. 244 // An additional benefit is it saves the cost of re-instantiating potentially 245 // multiple emptyHML objects. Both, the JS- and the C++-objects. 246 dom::PreserveWrapper(this); 247 248 RefPtr<TrustedHTML> result = new TrustedHTML(EmptyString()); 249 return result.forget(); 250 } 251 252 already_AddRefed<TrustedScript> TrustedTypePolicyFactory::EmptyScript() { 253 // See the explanation in `EmptyHTML()`. 254 dom::PreserveWrapper(this); 255 256 RefPtr<TrustedScript> result = new TrustedScript(EmptyString()); 257 return result.forget(); 258 } 259 260 // TODO(fwang): Improve this API: 261 // - Rename aTagName parameter to use aLocalName instead 262 // (https://github.com/w3c/trusted-types/issues/496) 263 // - Remove ASCII-case-insensitivity for aTagName and aAttribute 264 // (https://github.com/w3c/trusted-types/issues/424) 265 // - Make aElementNs default to HTML namespace, so special handling for an empty 266 // string is not needed (https://github.com/w3c/trusted-types/issues/381). 267 void TrustedTypePolicyFactory::GetAttributeType(const nsAString& aTagName, 268 const nsAString& aAttribute, 269 const nsAString& aElementNs, 270 const nsAString& aAttrNs, 271 DOMString& aResult) { 272 // We first determine the namespace IDs for the element and attribute. 273 // Currently, GetTrustedTypeDataForAttribute() only test a few of them so use 274 // direct string comparisons instead of relying on 275 // nsNameSpaceManager::GetNameSpaceID(). 276 277 // GetTrustedTypeDataForAttribute() can only return true for empty or XLink 278 // attribute namespaces, so don't bother calling it for other namespaces. 279 int32_t attributeNamespaceID = kNameSpaceID_Unknown; 280 if (aAttrNs.IsEmpty()) { 281 attributeNamespaceID = kNameSpaceID_None; 282 } else if (nsGkAtoms::nsuri_xlink->Equals(aAttrNs)) { 283 attributeNamespaceID = kNameSpaceID_XLink; 284 } else { 285 aResult.SetNull(); 286 return; 287 } 288 289 // GetTrustedTypeDataForAttribute() only return true for HTML, SVG or MathML 290 // element namespaces, so don't bother calling it for other namespaces. 291 int32_t elementNamespaceID = kNameSpaceID_Unknown; 292 if (aElementNs.IsEmpty() || nsGkAtoms::nsuri_xhtml->Equals(aElementNs)) { 293 elementNamespaceID = kNameSpaceID_XHTML; 294 } else if (nsGkAtoms::nsuri_svg->Equals(aElementNs)) { 295 elementNamespaceID = kNameSpaceID_SVG; 296 } else if (nsGkAtoms::nsuri_mathml->Equals(aElementNs)) { 297 elementNamespaceID = kNameSpaceID_MathML; 298 } else { 299 aResult.SetNull(); 300 return; 301 } 302 303 nsAutoString attribute; 304 nsContentUtils::ASCIIToLower(aAttribute, attribute); 305 RefPtr<nsAtom> attributeAtom = NS_Atomize(attribute); 306 307 nsAutoString localName; 308 nsContentUtils::ASCIIToLower(aTagName, localName); 309 RefPtr<nsAtom> elementAtom = NS_Atomize(localName); 310 311 TrustedType trustedType; 312 nsAutoString unusedSink; 313 if (GetTrustedTypeDataForAttribute(elementAtom, elementNamespaceID, 314 attributeAtom, attributeNamespaceID, 315 trustedType, unusedSink)) { 316 aResult.SetKnownLiveString(GetTrustedTypeName(trustedType)); 317 return; 318 } 319 320 aResult.SetNull(); 321 } 322 323 // TODO(fwang): Improve this API: 324 // - Rename aTagName parameter to use aLocalName instead 325 // (https://github.com/w3c/trusted-types/issues/496) 326 // - Remove ASCII-case-insensitivity for aTagName 327 // (https://github.com/w3c/trusted-types/issues/424) 328 // - Make aElementNs default to HTML namespace, so special handling for an empty 329 // string is not needed (https://github.com/w3c/trusted-types/issues/381). 330 void TrustedTypePolicyFactory::GetPropertyType(const nsAString& aTagName, 331 const nsAString& aProperty, 332 const nsAString& aElementNs, 333 DOMString& aResult) { 334 RefPtr<nsAtom> propertyAtom = NS_Atomize(aProperty); 335 if (aElementNs.IsEmpty() || nsGkAtoms::nsuri_xhtml->Equals(aElementNs)) { 336 if (nsContentUtils::EqualsIgnoreASCIICase( 337 aTagName, nsDependentAtomString(nsGkAtoms::iframe))) { 338 // HTMLIFrameElement 339 if (propertyAtom == nsGkAtoms::srcdoc) { 340 aResult.SetKnownLiveString(GetTrustedTypeName<TrustedHTML>()); 341 return; 342 } 343 } else if (nsContentUtils::EqualsIgnoreASCIICase( 344 aTagName, nsDependentAtomString(nsGkAtoms::script))) { 345 // HTMLScriptElement 346 if (propertyAtom == nsGkAtoms::innerText || 347 propertyAtom == nsGkAtoms::text || 348 propertyAtom == nsGkAtoms::textContent) { 349 aResult.SetKnownLiveString(GetTrustedTypeName<TrustedScript>()); 350 return; 351 } 352 if (propertyAtom == nsGkAtoms::src) { 353 aResult.SetKnownLiveString(GetTrustedTypeName<TrustedScriptURL>()); 354 return; 355 } 356 } 357 } 358 // * 359 if (propertyAtom == nsGkAtoms::innerHTML || 360 propertyAtom == nsGkAtoms::outerHTML) { 361 aResult.SetKnownLiveString(GetTrustedTypeName<TrustedHTML>()); 362 return; 363 } 364 365 aResult.SetNull(); 366 } 367 368 } // namespace mozilla::dom