tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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