tor-browser

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

commit 5641e22232ab301d9b8d19a2e0a5408cb2f85c0d
parent 6c938c67c4fce17a1a7f291790d3cac5bc76f59a
Author: Frédéric Wang <fwang@igalia.com>
Date:   Mon,  8 Dec 2025 11:45:44 +0000

Bug 2001929 Don't use a AddScriptRunner when processing untrusted inline scripts in a document without TrustedTypes enforcement. r=smaug

Currently, for untrusted inline scripts, ScriptElement's
MaybeProcessScript() will always:

1) Run GetTrustedTypesCompliantInlineScriptText() in a script runner.
2) Call MaybeProcessScript(nsAString&) with the retrieved source.
3) Return false to indicate parser should not be blocked.

In various cases (no CSP enforcement, web extension, UA widget, etc),
step 1 won't actually run the default policy and the raw string will
be used instead. Consequently, we can just do the same processing as
for trusted script i.e. immediately call and return the result of
MaybeProcessScript(nsAString&), lazily retrieving the source text.

Note that step 3 is incorrect in cases when a script runner is still
needed, but this will be handled in bug 1997818.

Differential Revision: https://phabricator.services.mozilla.com/D273776

Diffstat:
Mdom/script/ScriptElement.cpp | 56+++++++++++++++++++++++++++++++++++---------------------
Mdom/script/ScriptLoadContext.cpp | 1+
Mdom/security/trusted-types/TrustedTypeUtils.cpp | 94+++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------
Mdom/security/trusted-types/TrustedTypeUtils.h | 5+++--
4 files changed, 105 insertions(+), 51 deletions(-)

diff --git a/dom/script/ScriptElement.cpp b/dom/script/ScriptElement.cpp @@ -15,6 +15,7 @@ #include "mozilla/dom/Element.h" #include "mozilla/dom/TrustedTypeUtils.h" #include "mozilla/dom/TrustedTypesConstants.h" +#include "mozilla/extensions/WebExtensionPolicy.h" #include "nsContentSink.h" #include "nsContentUtils.h" #include "nsGkAtoms.h" @@ -159,18 +160,19 @@ bool ScriptElement::MaybeProcessScript() { // use a void string. // - If it is an external script, we similarly just pass a void string. if (!HasExternalScriptContent() && !mIsTrusted) { - // TODO: We should likely block parser if IsClassicNonAsyncDefer() returns - // true but this is tricky because the default policy callback can actually - // change the script type. - nsContentUtils::AddScriptRunner(NS_NewRunnableFunction( - "ScriptElement::MaybeProcessScript", - [self = RefPtr<nsIScriptElement>(this)]() - MOZ_CAN_RUN_SCRIPT_BOUNDARY_LAMBDA { - nsString sourceText; - self->GetTrustedTypesCompliantInlineScriptText(sourceText); - ((ScriptElement*)self.get())->MaybeProcessScript(sourceText); - })); - return false; + if (!TrustedTypeUtils::CanSkipTrustedTypesEnforcement( + *GetAsContent()->AsElement())) { + // TODO(bug 1997818): Ensure we properly block the parser. + nsContentUtils::AddScriptRunner(NS_NewRunnableFunction( + "ScriptElement::MaybeProcessScript", + [self = RefPtr<nsIScriptElement>(this)]() + MOZ_CAN_RUN_SCRIPT_BOUNDARY_LAMBDA { + nsString sourceText; + self->GetTrustedTypesCompliantInlineScriptText(sourceText); + ((ScriptElement*)self.get())->MaybeProcessScript(sourceText); + })); + return false; + } } return MaybeProcessScript(VoidString()); } @@ -178,9 +180,14 @@ bool ScriptElement::MaybeProcessScript() { bool ScriptElement::MaybeProcessScript(const nsAString& aSourceText) { nsIContent* cont = GetAsContent(); if (!HasExternalScriptContent()) { + // If el has no src attribute, and source text is the empty string, then + // return (https://html.spec.whatwg.org/#prepare-the-script-element). + // + // A void aSourceText means we want to retrieve it lazily (bug 1376651), in + // that case we browse the subtree to try and find a non-empty text node. bool hasInlineScriptContent = - mIsTrusted ? nsContentUtils::HasNonEmptyTextContent(cont) - : !aSourceText.IsEmpty(); + aSourceText.IsVoid() ? nsContentUtils::HasNonEmptyTextContent(cont) + : !aSourceText.IsEmpty(); if (!hasInlineScriptContent) { // In the case of an empty, non-external classic script, there is nothing // to process. However, we must perform a microtask checkpoint afterwards, @@ -191,7 +198,6 @@ bool ScriptElement::MaybeProcessScript(const nsAString& aSourceText) { } return false; } - MOZ_ASSERT(mIsTrusted == aSourceText.IsVoid()); } // Check the type attribute to determine language and version. If type exists, @@ -285,6 +291,19 @@ void ScriptElement::UpdateTrustWorthiness( MutationEffectOnScript aMutationEffectOnScript) { if (aMutationEffectOnScript == MutationEffectOnScript::DropTrustWorthiness && StaticPrefs::dom_security_trusted_types_enabled()) { + nsCOMPtr<nsIPrincipal> subjectPrincipal; + if (JSContext* cx = nsContentUtils::GetCurrentJSContext()) { + subjectPrincipal = nsContentUtils::SubjectPrincipal(cx); + if (auto* principal = BasePrincipal::Cast(subjectPrincipal)) { + if (principal->IsSystemPrincipal() || + principal->ContentScriptAddonPolicyCore()) { + // This script was modified by a priviledged scripts, so continue to + // consider it as trusted. + return; + } + } + } + mIsTrusted = false; } } @@ -303,15 +322,10 @@ nsresult ScriptElement::GetTrustedTypesCompliantInlineScriptText( constexpr nsLiteralString svgSinkName = u"SVGScriptElement text"_ns; ErrorResult error; - nsCOMPtr<nsIPrincipal> subjectPrincipal; - if (JSContext* cx = nsContentUtils::GetCurrentJSContext()) { - subjectPrincipal = nsContentUtils::SubjectPrincipal(cx); - } const nsAString* compliantString = TrustedTypeUtils::GetTrustedTypesCompliantStringForTrustedScript( sourceText, element->IsHTMLElement() ? htmlSinkName : svgSinkName, - kTrustedTypesOnlySinkGroup, *element, subjectPrincipal, - compliantStringHolder, error); + kTrustedTypesOnlySinkGroup, *element, compliantStringHolder, error); if (!error.Failed()) { aSourceText.Assign(*compliantString); } diff --git a/dom/script/ScriptLoadContext.cpp b/dom/script/ScriptLoadContext.cpp @@ -150,6 +150,7 @@ bool ScriptLoadContext::HasScriptElement() const { return !!mScriptElement; } void ScriptLoadContext::GetInlineScriptText(nsAString& aText) const { MOZ_ASSERT(mIsInline); if (mSourceText.IsVoid()) { + // Lazily retrieve the text of inline script, see bug 1376651. mScriptElement->GetScriptText(aText); } else { aText.Append(mSourceText); diff --git a/dom/security/trusted-types/TrustedTypeUtils.cpp b/dom/security/trusted-types/TrustedTypeUtils.cpp @@ -407,29 +407,23 @@ static inline const nsAString* GetContent( return IsString(aInput) ? GetAsString(aInput) : GetAsTrustedType(aInput); } -template <typename ExpectedType, typename TrustedTypeOrString, - typename NodeOrGlobalObject> -MOZ_CAN_RUN_SCRIPT inline const nsAString* GetTrustedTypesCompliantString( - const TrustedTypeOrString& aInput, const nsAString& aSink, - const nsAString& aSinkGroup, NodeOrGlobalObject& aNodeOrGlobalObject, - nsIPrincipal* aPrincipalOrNull, Maybe<nsAutoString>& aResultHolder, +template <typename NodeOrGlobalObject> +inline bool PrepareExecutionOfTrustedTypesDefaultPolicy( + NodeOrGlobalObject& aNodeOrGlobalObject, nsIPrincipal* aPrincipalOrNull, + nsIGlobalObject*& aGlobalObject, nsPIDOMWindowInner*& aPIDOMWindowInner, + RequireTrustedTypesForDirectiveState* aRequireTrustedTypesForDirectiveState, ErrorResult& aError) { - MOZ_ASSERT(aSinkGroup == kTrustedTypesOnlySinkGroup); if (!StaticPrefs::dom_security_trusted_types_enabled()) { // A trusted type might've been created before the pref was set to `false`, // so we cannot assume aInput.IsString(). - return GetContent(aInput); - } - - if (IsTrustedType(aInput)) { - return GetAsTrustedType(aInput); + return false; } // Exempt web extension content scripts from trusted types policies defined by // the page in which they are running. if (auto* principal = BasePrincipal::Cast(aPrincipalOrNull)) { if (principal->ContentScriptAddonPolicyCore()) { - return GetAsString(aInput); + return false; } } @@ -444,30 +438,30 @@ MOZ_CAN_RUN_SCRIPT inline const nsAString* GetTrustedTypesCompliantString( nsPIDOMWindowInner* piDOMWindowInner = nullptr; if constexpr (std::is_same_v<NodeOrGlobalObjectArg, nsINode>) { if (aNodeOrGlobalObject.HasBeenInUAWidget()) { - return GetAsString(aInput); + return false; } Document* ownerDoc = aNodeOrGlobalObject.OwnerDoc(); const bool ownerDocLoadedAsData = ownerDoc->IsLoadedAsData(); if (!ownerDoc->HasPolicyWithRequireTrustedTypesForDirective() && !ownerDocLoadedAsData) { - return GetAsString(aInput); + return false; } globalObject = ownerDoc->GetScopeObject(); if (!globalObject) { aError.ThrowTypeError("No global object"); - return nullptr; + return false; } piDOMWindowInner = globalObject->GetAsInnerWindow(); if (!piDOMWindowInner) { // Global object is not a Window. This can happen when DOM APIs are used // in some contexts where Trusted Types don't apply (e.g. bug 1942517), // so just return the input string. - return GetAsString(aInput); + return false; } if (ownerDocLoadedAsData && piDOMWindowInner->GetExtantDoc() && !piDOMWindowInner->GetExtantDoc() ->HasPolicyWithRequireTrustedTypesForDirective()) { - return GetAsString(aInput); + return false; } } else if constexpr (std::is_same_v<NodeOrGlobalObjectArg, nsIGlobalObject>) { piDOMWindowInner = aNodeOrGlobalObject.GetAsInnerWindow(); @@ -475,7 +469,7 @@ MOZ_CAN_RUN_SCRIPT inline const nsAString* GetTrustedTypesCompliantString( const Document* extantDoc = piDOMWindowInner->GetExtantDoc(); if (extantDoc && !extantDoc->HasPolicyWithRequireTrustedTypesForDirective()) { - return GetAsString(aInput); + return false; } } globalObject = &aNodeOrGlobalObject; @@ -489,13 +483,13 @@ MOZ_CAN_RUN_SCRIPT inline const nsAString* GetTrustedTypesCompliantString( // (https://w3c.github.io/trusted-types/dist/spec/#abstract-opdef-does-sink-type-require-trusted-types) // and "Should sink type mismatch violation be blocked by CSP?" // (https://w3c.github.io/trusted-types/dist/spec/#should-block-sink-type-mismatch). - RefPtr<nsIContentSecurityPolicy> csp; RequireTrustedTypesForDirectiveState requireTrustedTypesForDirectiveState = RequireTrustedTypesForDirectiveState::NONE; if (piDOMWindowInner) { - csp = PolicyContainer::GetCSP(piDOMWindowInner->GetPolicyContainer()); + RefPtr<nsIContentSecurityPolicy> csp = + PolicyContainer::GetCSP(piDOMWindowInner->GetPolicyContainer()); if (!csp) { - return GetAsString(aInput); + return false; } requireTrustedTypesForDirectiveState = csp->GetRequireTrustedTypesForDirectiveState(); @@ -511,13 +505,53 @@ MOZ_CAN_RUN_SCRIPT inline const nsAString* GetTrustedTypesCompliantString( cspInfo.requireTrustedTypesForDirectiveState(); if (requireTrustedTypesForDirectiveState == RequireTrustedTypesForDirectiveState::NONE) { - return GetAsString(aInput); + return false; } } else { // Global object is neither Window nor WorkerGlobalScope. This can happen // when DOM APIs are used in some contexts where Trusted Types don't apply // (e.g. bugs 1942517 and 1936219), so just return the input string. - return GetAsString(aInput); + return false; + } + + aGlobalObject = globalObject; + aPIDOMWindowInner = piDOMWindowInner; + *aRequireTrustedTypesForDirectiveState = requireTrustedTypesForDirectiveState; + return true; +} + +bool CanSkipTrustedTypesEnforcement(const nsINode& aNode) { + nsIGlobalObject* dummyGlobal = nullptr; + nsPIDOMWindowInner* dummyWindow = nullptr; + RequireTrustedTypesForDirectiveState dummyState; + // Specify a null principal, as ScriptElement::UpdateTrustWorthiness() has + // already checked whether this is running in an isolated web extension + // context. + return !PrepareExecutionOfTrustedTypesDefaultPolicy( + aNode, nullptr /* aPrincipalOrNull */, dummyGlobal, dummyWindow, + &dummyState, IgnoreErrors()); +} + +template <typename ExpectedType, typename TrustedTypeOrString, + typename NodeOrGlobalObject> +MOZ_CAN_RUN_SCRIPT inline const nsAString* GetTrustedTypesCompliantString( + const TrustedTypeOrString& aInput, const nsAString& aSink, + const nsAString& aSinkGroup, NodeOrGlobalObject& aNodeOrGlobalObject, + nsIPrincipal* aPrincipalOrNull, Maybe<nsAutoString>& aResultHolder, + ErrorResult& aError) { + MOZ_ASSERT(aSinkGroup == kTrustedTypesOnlySinkGroup); + + if (IsTrustedType(aInput)) { + return GetAsTrustedType(aInput); + } + + nsIGlobalObject* globalObject = nullptr; + nsPIDOMWindowInner* piDOMWindowInner = nullptr; + RequireTrustedTypesForDirectiveState requireTrustedTypesForDirectiveState; + if (!PrepareExecutionOfTrustedTypesDefaultPolicy( + aNodeOrGlobalObject, aPrincipalOrNull, globalObject, piDOMWindowInner, + &requireTrustedTypesForDirectiveState, aError)) { + return aError.Failed() ? nullptr : GetAsString(aInput); } RefPtr<ExpectedType> convertedInput; @@ -533,6 +567,8 @@ MOZ_CAN_RUN_SCRIPT inline const nsAString* GetTrustedTypesCompliantString( if (!convertedInput) { auto location = JSCallingLocation::Get(); if (piDOMWindowInner) { + RefPtr<nsIContentSecurityPolicy> csp = + PolicyContainer::GetCSP(piDOMWindowInner->GetPolicyContainer()); ReportSinkTypeMismatchViolations(csp, nullptr /* aCSPEventListener */, location.FileName(), location.mLine, location.mColumn, aSink, aSinkGroup, @@ -625,11 +661,13 @@ MOZ_CAN_RUN_SCRIPT const nsAString* GetTrustedTypesCompliantStringForTrustedScript( const nsAString& aInput, const nsAString& aSink, const nsAString& aSinkGroup, const nsINode& aNode, - nsIPrincipal* aPrincipalOrNull, Maybe<nsAutoString>& aResultHolder, - ErrorResult& aError) { + Maybe<nsAutoString>& aResultHolder, ErrorResult& aError) { + // Specify a null principal, as ScriptElement::UpdateTrustWorthiness() has + // already checked whether this is running in an isolated web extension + // context. return GetTrustedTypesCompliantString<TrustedScript>( - &aInput, aSink, aSinkGroup, aNode, aPrincipalOrNull, aResultHolder, - aError); + &aInput, aSink, aSinkGroup, aNode, nullptr /* aPrincipalOrNull */, + aResultHolder, aError); } bool GetTrustedTypeDataForAttribute(const nsAtom* aElementName, diff --git a/dom/security/trusted-types/TrustedTypeUtils.h b/dom/security/trusted-types/TrustedTypeUtils.h @@ -69,6 +69,8 @@ void ReportSinkTypeMismatchViolations(nsIContentSecurityPolicy* aCSP, const nsAString& aSinkGroup, const nsAString& aSource); +bool CanSkipTrustedTypesEnforcement(const nsINode& aNode); + // https://w3c.github.io/trusted-types/dist/spec/#get-trusted-type-compliant-string-algorithm // // May only run script if aInput is not a trusted type and if the trusted types @@ -145,8 +147,7 @@ MOZ_CAN_RUN_SCRIPT const nsAString* GetTrustedTypesCompliantStringForTrustedScript( const nsAString& aInput, const nsAString& aSink, const nsAString& aSinkGroup, const nsINode& aNode, - nsIPrincipal* aPrincipalOrNull, Maybe<nsAutoString>& aResultHolder, - ErrorResult& aError); + Maybe<nsAutoString>& aResultHolder, ErrorResult& aError); // https://w3c.github.io/trusted-types/dist/spec/#abstract-opdef-process-value-with-a-default-policy template <typename ExpectedType>