Skip to content

Commit

Permalink
Bug 1925468 - Implement Trusted Type support for setAttribute/setAttr…
Browse files Browse the repository at this point in the history
  • Loading branch information
fred-wang committed Nov 28, 2024
1 parent 23b1720 commit 6359ee4
Show file tree
Hide file tree
Showing 11 changed files with 203 additions and 396 deletions.
74 changes: 74 additions & 0 deletions dom/base/Element.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1674,6 +1674,80 @@ already_AddRefed<nsIPrincipal> Element::CreateDevtoolsPrincipal() {
return dtPrincipal.forget();
}

void Element::SetAttribute(
const nsAString& aName,
const TrustedHTMLOrTrustedScriptOrTrustedScriptURLOrString& aValue,
nsIPrincipal* aTriggeringPrincipal, ErrorResult& aError) {
aError = nsContentUtils::CheckQName(aName, false);
if (aError.Failed()) {
return;
}

nsAutoString nameToUse;
const nsAttrName* name = InternalGetAttrNameFromQName(aName, &nameToUse);
if (!name) {
RefPtr<nsAtom> nameAtom = NS_AtomizeMainThread(nameToUse);
Maybe<nsAutoString> compliantStringHolder;
const nsAString* compliantString =
TrustedTypeUtils::GetTrustedTypesCompliantAttributeValue(
*this, nameAtom, kNameSpaceID_None, aValue, compliantStringHolder,
aError);
if (aError.Failed()) {
return;
}
aError = SetAttr(kNameSpaceID_None, nameAtom, *compliantString,
aTriggeringPrincipal, true);
return;
}

Maybe<nsAutoString> compliantStringHolder;
RefPtr<nsAtom> attributeName = name->LocalName();
nsMutationGuard guard;
const nsAString* compliantString =
TrustedTypeUtils::GetTrustedTypesCompliantAttributeValue(
*this, attributeName, name->NamespaceID(), aValue,
compliantStringHolder, aError);
if (aError.Failed()) {
return;
}
if (!guard.Mutated(0)) {
aError = SetAttr(name->NamespaceID(), name->LocalName(), name->GetPrefix(),
*compliantString, aTriggeringPrincipal, true);
return;
}

// GetTrustedTypesCompliantAttributeValue may have modified mAttrs and made
// the result of InternalGetAttrNameFromQName above invalid. It may now return
// a different value, perhaps a nullptr. To be safe, just call the version of
// Element::SetAttribute accepting a string value.
SetAttribute(aName, *compliantString, aTriggeringPrincipal, aError);
}

void Element::SetAttributeNS(
const nsAString& aNamespaceURI, const nsAString& aQualifiedName,
const TrustedHTMLOrTrustedScriptOrTrustedScriptURLOrString& aValue,
nsIPrincipal* aTriggeringPrincipal, ErrorResult& aError) {
RefPtr<mozilla::dom::NodeInfo> ni;
aError = nsContentUtils::GetNodeInfoFromQName(
aNamespaceURI, aQualifiedName, mNodeInfo->NodeInfoManager(),
ATTRIBUTE_NODE, getter_AddRefs(ni));
if (aError.Failed()) {
return;
}

Maybe<nsAutoString> compliantStringHolder;
RefPtr<nsAtom> attributeName = ni->NameAtom();
const nsAString* compliantString =
TrustedTypeUtils::GetTrustedTypesCompliantAttributeValue(
*this, attributeName, ni->NamespaceID(), aValue,
compliantStringHolder, aError);
if (aError.Failed()) {
return;
}
aError = SetAttr(ni->NamespaceID(), ni->NameAtom(), ni->GetPrefixAtom(),
*compliantString, aTriggeringPrincipal, true);
}

void Element::SetAttributeDevtools(const nsAString& aName,
const nsAString& aValue,
ErrorResult& aError) {
Expand Down
17 changes: 17 additions & 0 deletions dom/base/Element.h
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,7 @@ class Grid;
class OwningTrustedHTMLOrNullIsEmptyString;
class TrustedHTML;
class TrustedHTMLOrNullIsEmptyString;
class TrustedHTMLOrTrustedScriptOrTrustedScriptURLOrString;

// IID for the dom::Element interface
#define NS_ELEMENT_IID \
Expand Down Expand Up @@ -1253,6 +1254,22 @@ class Element : public FragmentOrElement {
ErrorResult& aError) {
SetAttribute(aName, aValue, nullptr, aError);
}

MOZ_CAN_RUN_SCRIPT void SetAttribute(
const nsAString& aName,
const TrustedHTMLOrTrustedScriptOrTrustedScriptURLOrString& aValue,
nsIPrincipal* aTriggeringPrincipal, ErrorResult& aError);
MOZ_CAN_RUN_SCRIPT void SetAttributeNS(
const nsAString& aNamespaceURI, const nsAString& aLocalName,
const TrustedHTMLOrTrustedScriptOrTrustedScriptURLOrString& aValue,
nsIPrincipal* aTriggeringPrincipal, ErrorResult& aError);
MOZ_CAN_RUN_SCRIPT void SetAttribute(
const nsAString& aName,
const TrustedHTMLOrTrustedScriptOrTrustedScriptURLOrString& aValue,
ErrorResult& aError) {
SetAttribute(aName, aValue, nullptr, aError);
}

/**
* This method creates a principal that subsumes this element's NodePrincipal
* and which has flags set for elevated permissions that devtools needs to
Expand Down
87 changes: 87 additions & 0 deletions dom/security/trusted-types/TrustedTypeUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#include "mozilla/dom/TrustedScriptURL.h"
#include "mozilla/dom/TrustedTypePolicy.h"
#include "mozilla/dom/TrustedTypePolicyFactory.h"
#include "mozilla/dom/TrustedTypesConstants.h"
#include "nsGlobalWindowInner.h"
#include "nsLiteralString.h"
#include "nsTArray.h"
Expand Down Expand Up @@ -250,6 +251,10 @@ MOZ_CAN_RUN_SCRIPT inline const nsAString* GetTrustedTypesCompliantString(
TrustedScriptOrNullIsEmptyString>) {
return aInput.IsNullIsEmptyString();
}
if constexpr (std::is_same_v<TrustedTypeOrStringArg, const nsAString*>) {
Unused << aInput;
return true;
}
MOZ_ASSERT_UNREACHABLE();
return false;
};
Expand All @@ -267,6 +272,9 @@ MOZ_CAN_RUN_SCRIPT inline const nsAString* GetTrustedTypesCompliantString(
TrustedScriptOrNullIsEmptyString>) {
return &aInput.GetAsNullIsEmptyString();
}
if constexpr (std::is_same_v<TrustedTypeOrStringArg, const nsAString*>) {
return aInput;
}
MOZ_ASSERT_UNREACHABLE();
return static_cast<const nsAString*>(&EmptyString());
};
Expand All @@ -286,6 +294,10 @@ MOZ_CAN_RUN_SCRIPT inline const nsAString* GetTrustedTypesCompliantString(
TrustedScriptURLOrString>) {
return aInput.IsTrustedScriptURL();
}
if constexpr (std::is_same_v<TrustedTypeOrStringArg, const nsAString*>) {
Unused << aInput;
return false;
}
MOZ_ASSERT_UNREACHABLE();
return false;
};
Expand All @@ -305,6 +317,7 @@ MOZ_CAN_RUN_SCRIPT inline const nsAString* GetTrustedTypesCompliantString(
TrustedScriptURLOrString>) {
return &aInput.GetAsTrustedScriptURL().mData;
}
Unused << aInput;
MOZ_ASSERT_UNREACHABLE();
return &EmptyString();
};
Expand Down Expand Up @@ -410,13 +423,15 @@ bool GetTrustedTypeDataForAttribute(const nsAtom* aElementName,
if (aAttributeNamespaceID == kNameSpaceID_None &&
aAttributeName == nsGkAtoms::srcdoc) {
aTrustedType = TrustedType::TrustedHTML;
aSink.AssignLiteral(u"HTMLIFrameElement srcdoc");
return true;
}
} else if (aElementName == nsGkAtoms::script) {
// HTMLScriptElement
if (aAttributeNamespaceID == kNameSpaceID_None &&
aAttributeName == nsGkAtoms::src) {
aTrustedType = TrustedType::TrustedScriptURL;
aSink.AssignLiteral(u"HTMLScriptElement src");
return true;
}
}
Expand All @@ -427,6 +442,7 @@ bool GetTrustedTypeDataForAttribute(const nsAtom* aElementName,
aAttributeNamespaceID == kNameSpaceID_XLink) &&
aAttributeName == nsGkAtoms::href) {
aTrustedType = TrustedType::TrustedScriptURL;
aSink.AssignLiteral(u"SVGScriptElement href");
return true;
}
}
Expand All @@ -435,4 +451,75 @@ bool GetTrustedTypeDataForAttribute(const nsAtom* aElementName,
return false;
}

MOZ_CAN_RUN_SCRIPT const nsAString* GetTrustedTypesCompliantAttributeValue(
const nsINode& aElement, nsAtom* aAttributeName,
int32_t aAttributeNamespaceID,
const TrustedHTMLOrTrustedScriptOrTrustedScriptURLOrString& aNewValue,
Maybe<nsAutoString>& aResultHolder, ErrorResult& aError) {
auto getAsTrustedType = [&aNewValue] {
if (aNewValue.IsTrustedHTML()) {
return &aNewValue.GetAsTrustedHTML().mData;
}
if (aNewValue.IsTrustedScript()) {
return &aNewValue.GetAsTrustedScript().mData;
}
MOZ_ASSERT(aNewValue.IsTrustedScriptURL());
return &aNewValue.GetAsTrustedScriptURL().mData;
};
auto getContent = [&aNewValue, &getAsTrustedType] {
return aNewValue.IsString() ? &aNewValue.GetAsString() : getAsTrustedType();
};

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 aNewValue.IsString().
return getContent();
}

// In the common situation of non-data document without any
// require-trusted-types-for directive, we just return immediately.
const NodeInfo* nodeInfo = aElement.NodeInfo();
Document* ownerDoc = nodeInfo->GetDocument();
const bool ownerDocLoadedAsData = ownerDoc->IsLoadedAsData();
if (!ownerDoc->HasPolicyWithRequireTrustedTypesForDirective() &&
!ownerDocLoadedAsData) {
return getContent();
}

TrustedType expectedType;
nsAutoString sink;
if (!GetTrustedTypeDataForAttribute(
nodeInfo->NameAtom(), nodeInfo->NamespaceID(), aAttributeName,
aAttributeNamespaceID, expectedType, sink)) {
return getContent();
}

if ((expectedType == TrustedType::TrustedHTML && aNewValue.IsTrustedHTML()) ||
(expectedType == TrustedType::TrustedScript &&
aNewValue.IsTrustedScript()) ||
(expectedType == TrustedType::TrustedScriptURL &&
aNewValue.IsTrustedScriptURL())) {
return getAsTrustedType();
}

const nsAString* input =
aNewValue.IsString() ? &aNewValue.GetAsString() : getAsTrustedType();
switch (expectedType) {
case TrustedType::TrustedHTML:
return GetTrustedTypesCompliantString<TrustedHTML>(
input, sink, kTrustedTypesOnlySinkGroup, aElement, aResultHolder,
aError);
case TrustedType::TrustedScript:
return GetTrustedTypesCompliantString<TrustedScript>(
input, sink, kTrustedTypesOnlySinkGroup, aElement, aResultHolder,
aError);
case TrustedType::TrustedScriptURL:
return GetTrustedTypesCompliantString<TrustedScriptURL>(
input, sink, kTrustedTypesOnlySinkGroup, aElement, aResultHolder,
aError);
}
MOZ_ASSERT_UNREACHABLE();
return nullptr;
}

} // namespace mozilla::dom::TrustedTypeUtils
8 changes: 8 additions & 0 deletions dom/security/trusted-types/TrustedTypeUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ class TrustedScriptOrString;
class TrustedScriptOrNullIsEmptyString;
class TrustedScriptURL;
class TrustedScriptURLOrString;
class TrustedHTMLOrTrustedScriptOrTrustedScriptURLOrString;

namespace TrustedTypeUtils {

Expand Down Expand Up @@ -97,6 +98,13 @@ bool GetTrustedTypeDataForAttribute(const nsAtom* aElementName,
TrustedType& aTrustedType,
nsAString& aSink);

// https://w3c.github.io/trusted-types/dist/spec/#abstract-opdef-get-trusted-types-compliant-attribute-value
MOZ_CAN_RUN_SCRIPT const nsAString* GetTrustedTypesCompliantAttributeValue(
const nsINode& aElement, nsAtom* aAttributeName,
int32_t aAttributeNamespaceID,
const TrustedHTMLOrTrustedScriptOrTrustedScriptURLOrString& aNewValue,
Maybe<nsAutoString>& aResultHolder, ErrorResult& aError);

} // namespace TrustedTypeUtils

} // namespace dom
Expand Down
7 changes: 5 additions & 2 deletions dom/webidl/Element.webidl
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,9 @@ interface Element : Node {
[CEReactions, NeedsSubjectPrincipal=NonSystem, Throws]
boolean toggleAttribute(DOMString name, optional boolean force);
[CEReactions, NeedsSubjectPrincipal=NonSystem, Throws]
undefined setAttribute(DOMString name, DOMString value);
undefined setAttribute(DOMString name, (TrustedType or DOMString) value);
[CEReactions, NeedsSubjectPrincipal=NonSystem, Throws]
undefined setAttributeNS(DOMString? namespace, DOMString name, DOMString value);
undefined setAttributeNS(DOMString? namespace, DOMString name, (TrustedType or DOMString) value);
[CEReactions, Throws]
undefined removeAttribute(DOMString name);
[CEReactions, Throws]
Expand Down Expand Up @@ -417,3 +417,6 @@ partial interface Element {
[Pref="dom.webcomponents.shadowdom.declarative.enabled"]
DOMString getHTML(optional GetHTMLOptions options = {});
};

// https://w3c.github.io/trusted-types/dist/spec/#integrations
typedef (TrustedHTML or TrustedScript or TrustedScriptURL) TrustedType;

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,27 +1,3 @@
[block-string-assignment-to-Element-setAttribute.html]
[script.src accepts only TrustedScriptURL]
expected: FAIL

[iframe.srcdoc accepts only TrustedHTML]
expected: FAIL

[div.onclick accepts only TrustedScript]
expected: FAIL

[`Script.prototype.setAttribute.SrC = string` throws.]
expected: FAIL

[script.src's mutationobservers receive the default policy's value.]
expected: FAIL

[iframe.srcdoc's mutationobservers receive the default policy's value.]
expected: FAIL

[div.onclick's mutationobservers receive the default policy's value.]
expected: FAIL

[div.onclick accepts string and null after default policy was created.]
expected: FAIL

[`script.src = setAttributeNode(embed.src)` with string works.]
expected: FAIL

This file was deleted.

Loading

0 comments on commit 6359ee4

Please sign in to comment.