diff --git a/dom/base/Document.cpp b/dom/base/Document.cpp index 6fe478835839..99fa58a163a8 100644 --- a/dom/base/Document.cpp +++ b/dom/base/Document.cpp @@ -1256,6 +1256,7 @@ Document::Document(const char* aContentType) mHasCSP(false), mHasUnsafeEvalCSP(false), mHasUnsafeInlineCSP(false), + mHasCSPDeliveredThroughHeader(false), mBFCacheDisallowed(false), mHasHadDefaultView(false), mStyleSheetChangeEventsEnabled(false), @@ -3396,6 +3397,7 @@ nsresult Document::InitCSP(nsIChannel* aChannel) { // ----- if there's a full-strength CSP header, apply it. if (!cspHeaderValue.IsEmpty()) { + mHasCSPDeliveredThroughHeader = true; rv = CSP_AppendCSPFromHeader(mCSP, cspHeaderValue, false); NS_ENSURE_SUCCESS(rv, rv); } diff --git a/dom/base/Document.h b/dom/base/Document.h index 1efab443986b..7e345b50ff47 100644 --- a/dom/base/Document.h +++ b/dom/base/Document.h @@ -1116,6 +1116,14 @@ class Document : public nsINode, mHasUnsafeEvalCSP = aHasUnsafeEvalCSP; } + /** + * Returns true if the document holds a CSP + * delivered through an HTTP Header. + */ + bool GetHasCSPDeliveredThroughHeader() { + return mHasCSPDeliveredThroughHeader; + } + /** * Return a promise which resolves to the content blocking events. */ @@ -4417,6 +4425,9 @@ class Document : public nsINode, // True if a document load has a CSP with unsafe-inline attached. bool mHasUnsafeInlineCSP : 1; + // True if the document has a CSP delivered throuh a header + bool mHasCSPDeliveredThroughHeader : 1; + // True if DisallowBFCaching has been called on this document. bool mBFCacheDisallowed : 1; diff --git a/dom/base/nsINode.h b/dom/base/nsINode.h index 47a3eebd3111..f31ce948b324 100644 --- a/dom/base/nsINode.h +++ b/dom/base/nsINode.h @@ -175,8 +175,11 @@ enum { NODE_HAS_BEEN_IN_UA_WIDGET = NODE_FLAG_BIT(15), + // Set if the node has a nonce value and a header delivered CSP. + NODE_HAS_NONCE_AND_HEADER_CSP = NODE_FLAG_BIT(16), + // Remaining bits are node type specific. - NODE_TYPE_SPECIFIC_BITS_OFFSET = 16 + NODE_TYPE_SPECIFIC_BITS_OFFSET = 17 }; // Make sure we have space for our bits diff --git a/dom/html/HTMLLinkElement.cpp b/dom/html/HTMLLinkElement.cpp index d28a67579e31..c8b83ba39077 100644 --- a/dom/html/HTMLLinkElement.cpp +++ b/dom/html/HTMLLinkElement.cpp @@ -473,7 +473,10 @@ Maybe HTMLLinkElement::GetStyleSheetInfo() { referrerInfo->InitWithNode(this); nsAutoString nonce; - GetAttr(kNameSpaceID_None, nsGkAtoms::nonce, nonce); + nsString* cspNonce = static_cast(GetProperty(nsGkAtoms::nonce)); + if (cspNonce) { + nonce = *cspNonce; + } return Some(SheetInfo{ *OwnerDoc(), diff --git a/dom/html/nsGenericHTMLElement.cpp b/dom/html/nsGenericHTMLElement.cpp index 1b66ebb4d8d0..c518aba2c852 100644 --- a/dom/html/nsGenericHTMLElement.cpp +++ b/dom/html/nsGenericHTMLElement.cpp @@ -109,7 +109,15 @@ nsresult nsGenericHTMLElement::CopyInnerTo(Element* aDst) { auto reparse = aDst->OwnerDoc() == OwnerDoc() ? ReparseAttributes::No : ReparseAttributes::Yes; - return Element::CopyInnerTo(aDst, reparse); + nsresult rv = Element::CopyInnerTo(aDst, reparse); + NS_ENSURE_SUCCESS(rv, rv); + + // cloning a node must retain its internal nonce slot + nsString* nonce = static_cast(GetProperty(nsGkAtoms::nonce)); + if (nonce) { + static_cast(aDst)->SetNonce(*nonce); + } + return NS_OK; } static const nsAttrValue::EnumTable kDirTable[] = { @@ -386,6 +394,21 @@ nsresult nsGenericHTMLElement::BindToTree(BindContext& aContext, aContext.OwnerDoc().ChangeContentEditableCount(this, +1); } + // Hide any nonce from the DOM, but keep the internal value of the + // nonce by copying and resetting the internal nonce value. + if (HasFlag(NODE_HAS_NONCE_AND_HEADER_CSP) && IsInComposedDoc() && + OwnerDoc()->GetBrowsingContext()) { + nsContentUtils::AddScriptRunner(NS_NewRunnableFunction( + "nsGenericHTMLElement::ResetNonce::Runnable", + [self = RefPtr(this)]() { + nsAutoString nonce; + self->GetNonce(nonce); + self->SetAttr(kNameSpaceID_None, nsGkAtoms::nonce, EmptyString(), + true); + self->SetNonce(nonce); + })); + } + // We need to consider a labels element is moved to another subtree // with different root, it needs to update labels list and its root // as well. @@ -641,6 +664,20 @@ nsresult nsGenericHTMLElement::AfterSetAttr( } } } + + // The nonce will be copied over to an internal slot and cleared from the + // Element within BindToTree to avoid CSS Selector nonce exfiltration if + // the CSP list contains a header-delivered CSP. + if (nsGkAtoms::nonce == aName) { + if (aValue) { + SetNonce(aValue->GetStringValue()); + if (OwnerDoc()->GetHasCSPDeliveredThroughHeader()) { + SetFlags(NODE_HAS_NONCE_AND_HEADER_CSP); + } + } else { + RemoveNonce(); + } + } } return nsGenericHTMLElementBase::AfterSetAttr( diff --git a/dom/html/nsGenericHTMLElement.h b/dom/html/nsGenericHTMLElement.h index 81d740e5d540..d31e4797e0cc 100644 --- a/dom/html/nsGenericHTMLElement.h +++ b/dom/html/nsGenericHTMLElement.h @@ -140,6 +140,18 @@ class nsGenericHTMLElement : public nsGenericHTMLElementBase { return false; } + void SetNonce(const nsAString& aNonce) { + SetProperty(nsGkAtoms::nonce, new nsString(aNonce), + nsINode::DeleteProperty); + } + void RemoveNonce() { RemoveProperty(nsGkAtoms::nonce); } + void GetNonce(nsAString& aNonce) const { + nsString* cspNonce = static_cast(GetProperty(nsGkAtoms::nonce)); + if (cspNonce) { + aNonce = *cspNonce; + } + } + /** * Returns the count of descendants (inclusive of this node) in * the uncomposed document that are explicitly set as editable. diff --git a/dom/script/ScriptLoader.cpp b/dom/script/ScriptLoader.cpp index 0344b217c975..dd989d818e28 100644 --- a/dom/script/ScriptLoader.cpp +++ b/dom/script/ScriptLoader.cpp @@ -323,11 +323,13 @@ nsresult ScriptLoader::CheckContentPolicy(Document* aDocument, // snapshot the nonce at load start time for performing CSP checks if (contentPolicyType == nsIContentPolicy::TYPE_INTERNAL_SCRIPT || contentPolicyType == nsIContentPolicy::TYPE_INTERNAL_MODULE) { - nsCOMPtr element = do_QueryInterface(aContext); - if (element && element->IsHTMLElement()) { - nsAutoString cspNonce; - element->GetAttr(nsGkAtoms::nonce, cspNonce); - secCheckLoadInfo->SetCspNonce(cspNonce); + nsCOMPtr node = do_QueryInterface(aContext); + if (node) { + nsString* cspNonce = + static_cast(node->GetProperty(nsGkAtoms::nonce)); + if (cspNonce) { + secCheckLoadInfo->SetCspNonce(*cspNonce); + } } } @@ -1322,12 +1324,13 @@ nsresult ScriptLoader::StartLoad(ScriptLoadRequest* aRequest) { // snapshot the nonce at load start time for performing CSP checks if (contentPolicyType == nsIContentPolicy::TYPE_INTERNAL_SCRIPT || contentPolicyType == nsIContentPolicy::TYPE_INTERNAL_MODULE) { - nsCOMPtr element = do_QueryInterface(context); - if (element && element->IsHTMLElement()) { - nsAutoString cspNonce; - element->GetAttr(nsGkAtoms::nonce, cspNonce); - nsCOMPtr loadInfo = channel->LoadInfo(); - loadInfo->SetCspNonce(cspNonce); + if (context) { + nsString* cspNonce = + static_cast(context->GetProperty(nsGkAtoms::nonce)); + if (cspNonce) { + nsCOMPtr loadInfo = channel->LoadInfo(); + loadInfo->SetCspNonce(*cspNonce); + } } } @@ -1497,7 +1500,14 @@ static bool CSPAllowsInlineScript(nsIScriptElement* aElement, // query the nonce nsCOMPtr scriptContent = do_QueryInterface(aElement); nsAutoString nonce; - scriptContent->GetAttr(kNameSpaceID_None, nsGkAtoms::nonce, nonce); + if (scriptContent) { + nsString* cspNonce = + static_cast(scriptContent->GetProperty(nsGkAtoms::nonce)); + if (cspNonce) { + nonce = *cspNonce; + } + } + bool parserCreated = aElement->GetParserCreated() != mozilla::dom::NOT_FROM_PARSER; diff --git a/dom/svg/SVGElement.cpp b/dom/svg/SVGElement.cpp index f3ab501c304c..f197993e8246 100644 --- a/dom/svg/SVGElement.cpp +++ b/dom/svg/SVGElement.cpp @@ -104,6 +104,18 @@ JSObject* SVGElement::WrapNode(JSContext* aCx, return SVGElement_Binding::Wrap(aCx, this, aGivenProto); } +nsresult SVGElement::CopyInnerTo(mozilla::dom::Element* aDest) { + nsresult rv = Element::CopyInnerTo(aDest); + NS_ENSURE_SUCCESS(rv, rv); + + // cloning a node must retain its internal nonce slot + nsString* nonce = static_cast(GetProperty(nsGkAtoms::nonce)); + if (nonce) { + static_cast(aDest)->SetNonce(*nonce); + } + return NS_OK; +} + //---------------------------------------------------------------------- // SVGElement methods @@ -233,6 +245,21 @@ nsresult SVGElement::BindToTree(BindContext& aContext, nsINode& aParent) { nsresult rv = SVGElementBase::BindToTree(aContext, aParent); NS_ENSURE_SUCCESS(rv, rv); + // Hide any nonce from the DOM, but keep the internal value of the + // nonce by copying and resetting the internal nonce value. + if (HasFlag(NODE_HAS_NONCE_AND_HEADER_CSP) && IsInComposedDoc() && + OwnerDoc()->GetBrowsingContext()) { + nsContentUtils::AddScriptRunner(NS_NewRunnableFunction( + "SVGElement::ResetNonce::Runnable", + [self = RefPtr(this)]() { + nsAutoString nonce; + self->GetNonce(nonce); + self->SetAttr(kNameSpaceID_None, nsGkAtoms::nonce, EmptyString(), + true); + self->SetNonce(nonce); + })); + } + if (!MayHaveStyle()) { return NS_OK; } @@ -289,6 +316,20 @@ nsresult SVGElement::AfterSetAttr(int32_t aNamespaceID, nsAtom* aName, SetEventHandler(GetEventNameForAttr(aName), aValue->GetStringValue()); } + // The nonce will be copied over to an internal slot and cleared from the + // Element within BindToTree to avoid CSS Selector nonce exfiltration if + // the CSP list contains a header-delivered CSP. + if (nsGkAtoms::nonce == aName && kNameSpaceID_None == aNamespaceID) { + if (aValue) { + SetNonce(aValue->GetStringValue()); + if (OwnerDoc()->GetHasCSPDeliveredThroughHeader()) { + SetFlags(NODE_HAS_NONCE_AND_HEADER_CSP); + } + } else { + RemoveNonce(); + } + } + return SVGElementBase::AfterSetAttr(aNamespaceID, aName, aValue, aOldValue, aSubjectPrincipal, aNotify); } diff --git a/dom/svg/SVGElement.h b/dom/svg/SVGElement.h index ffe0051a048d..c11b7c214988 100644 --- a/dom/svg/SVGElement.h +++ b/dom/svg/SVGElement.h @@ -78,6 +78,9 @@ class SVGElement : public SVGElementBase // nsIContent virtual nsresult Clone(mozilla::dom::NodeInfo*, nsINode** aResult) const MOZ_MUST_OVERRIDE override; + // From Element + nsresult CopyInnerTo(mozilla::dom::Element* aDest); + // nsISupports NS_INLINE_DECL_REFCOUNTING_INHERITED(SVGElement, SVGElementBase) @@ -85,6 +88,18 @@ class SVGElement : public SVGElementBase // nsIContent void DidAnimateClass(); + void SetNonce(const nsAString& aNonce) { + SetProperty(nsGkAtoms::nonce, new nsString(aNonce), + nsINode::DeleteProperty); + } + void RemoveNonce() { RemoveProperty(nsGkAtoms::nonce); } + void GetNonce(nsAString& aNonce) const { + nsString* cspNonce = static_cast(GetProperty(nsGkAtoms::nonce)); + if (cspNonce) { + aNonce = *cspNonce; + } + } + // nsIContent interface methods virtual nsresult BindToTree(BindContext&, nsINode& aParent) override; diff --git a/dom/webidl/HTMLElement.webidl b/dom/webidl/HTMLElement.webidl index 57c8e06f695c..09181e802314 100644 --- a/dom/webidl/HTMLElement.webidl +++ b/dom/webidl/HTMLElement.webidl @@ -51,6 +51,8 @@ interface HTMLElement : Element { [CEReactions, SetterThrows, Pure] attribute boolean spellcheck; + attribute DOMString nonce; + // command API //readonly attribute DOMString? commandType; //readonly attribute DOMString? commandLabel; diff --git a/dom/webidl/SVGElement.webidl b/dom/webidl/SVGElement.webidl index a74e5e26b9b0..27d918c48c20 100644 --- a/dom/webidl/SVGElement.webidl +++ b/dom/webidl/SVGElement.webidl @@ -19,6 +19,8 @@ interface SVGElement : Element { readonly attribute SVGSVGElement? ownerSVGElement; readonly attribute SVGElement? viewportElement; + + attribute DOMString nonce; }; SVGElement includes GlobalEventHandlers; diff --git a/layout/style/Loader.cpp b/layout/style/Loader.cpp index a2a5a899b815..881b798ec009 100644 --- a/layout/style/Loader.cpp +++ b/layout/style/Loader.cpp @@ -1402,13 +1402,14 @@ nsresult Loader::LoadSheet(SheetLoadData& aLoadData, SheetState aSheetState, // snapshot the nonce at load start time for performing CSP checks if (contentPolicyType == nsIContentPolicy::TYPE_INTERNAL_STYLESHEET) { - nsCOMPtr element = do_QueryInterface(aLoadData.mRequestingNode); - if (element && element->IsHTMLElement()) { - nsAutoString cspNonce; + if (aLoadData.mRequestingNode) { // TODO(bug 1607009) move to SheetLoadData - element->GetAttr(nsGkAtoms::nonce, cspNonce); - nsCOMPtr loadInfo = channel->LoadInfo(); - loadInfo->SetCspNonce(cspNonce); + nsString* cspNonce = static_cast( + aLoadData.mRequestingNode->GetProperty(nsGkAtoms::nonce)); + if (cspNonce) { + nsCOMPtr loadInfo = channel->LoadInfo(); + loadInfo->SetCspNonce(*cspNonce); + } } } @@ -1534,13 +1535,14 @@ nsresult Loader::LoadSheet(SheetLoadData& aLoadData, SheetState aSheetState, // snapshot the nonce at load start time for performing CSP checks if (contentPolicyType == nsIContentPolicy::TYPE_INTERNAL_STYLESHEET) { - nsCOMPtr element = do_QueryInterface(aLoadData.mRequestingNode); - if (element && element->IsHTMLElement()) { - nsAutoString cspNonce; + if (aLoadData.mRequestingNode) { // TODO(bug 1607009) move to SheetLoadData - element->GetAttr(nsGkAtoms::nonce, cspNonce); - nsCOMPtr loadInfo = channel->LoadInfo(); - loadInfo->SetCspNonce(cspNonce); + nsString* cspNonce = static_cast( + aLoadData.mRequestingNode->GetProperty(nsGkAtoms::nonce)); + if (cspNonce) { + nsCOMPtr loadInfo = channel->LoadInfo(); + loadInfo->SetCspNonce(*cspNonce); + } } } diff --git a/layout/style/nsStyleUtil.cpp b/layout/style/nsStyleUtil.cpp index 0ddca7e34caa..1a08570ad7f3 100644 --- a/layout/style/nsStyleUtil.cpp +++ b/layout/style/nsStyleUtil.cpp @@ -309,7 +309,11 @@ bool nsStyleUtil::CSPAllowsInlineStyle( // query the nonce nsAutoString nonce; if (aElement && aElement->NodeInfo()->NameAtom() == nsGkAtoms::style) { - aElement->GetAttr(nsGkAtoms::nonce, nonce); + nsString* cspNonce = + static_cast(aElement->GetProperty(nsGkAtoms::nonce)); + if (cspNonce) { + nonce = *cspNonce; + } } bool allowInlineStyle = true; diff --git a/testing/web-platform/meta/content-security-policy/nonce-hiding/nonces.html.ini b/testing/web-platform/meta/content-security-policy/nonce-hiding/nonces.html.ini deleted file mode 100644 index f9fa2f5b2db6..000000000000 --- a/testing/web-platform/meta/content-security-policy/nonce-hiding/nonces.html.ini +++ /dev/null @@ -1,37 +0,0 @@ -[nonces.html] - [Ensure that removal of content attribute does not affect IDL attribute for meh in HTML namespace] - expected: FAIL - - [Ensure that removal of content attribute does not affect IDL attribute for div in HTML namespace] - expected: FAIL - - [Ensure that removal of content attribute does not affect IDL attribute for meh in SVG namespace] - expected: FAIL - - [Ensure that removal of content attribute does not affect IDL attribute for script in SVG namespace] - expected: FAIL - - [Basic nonce tests for script in HTML namespace] - expected: FAIL - - [Basic nonce tests for script in SVG namespace] - expected: FAIL - - [Ensure that removal of content attribute does not affect IDL attribute for script in HTML namespace] - expected: FAIL - - [Basic nonce tests for div in HTML namespace] - expected: FAIL - - [Basic nonce tests for svg in SVG namespace] - expected: FAIL - - [Basic nonce tests for meh in HTML namespace] - expected: FAIL - - [Basic nonce tests for meh in SVG namespace] - expected: FAIL - - [Ensure that removal of content attribute does not affect IDL attribute for svg in SVG namespace] - expected: FAIL - diff --git a/testing/web-platform/meta/content-security-policy/nonce-hiding/script-nonces-hidden-meta.sub.html.ini b/testing/web-platform/meta/content-security-policy/nonce-hiding/script-nonces-hidden-meta.sub.html.ini deleted file mode 100644 index 4808fcf2471d..000000000000 --- a/testing/web-platform/meta/content-security-policy/nonce-hiding/script-nonces-hidden-meta.sub.html.ini +++ /dev/null @@ -1,25 +0,0 @@ -[script-nonces-hidden-meta.sub.html] - [Cloned node retains nonce.] - expected: FAIL - - [createElement.nonce.] - expected: FAIL - - [Writing 'nonce' content attribute.] - expected: FAIL - - [Cloned node retains nonce when inserted.] - expected: FAIL - - [Reading 'nonce' content attribute and IDL attribute.] - expected: FAIL - - [Document-written script's nonce value.] - expected: FAIL - - [setAttribute('nonce') overwrites '.nonce' upon insertion.] - expected: FAIL - - [createElement.setAttribute.] - expected: FAIL - diff --git a/testing/web-platform/meta/content-security-policy/nonce-hiding/script-nonces-hidden.html.ini b/testing/web-platform/meta/content-security-policy/nonce-hiding/script-nonces-hidden.html.ini deleted file mode 100644 index 7947ed718622..000000000000 --- a/testing/web-platform/meta/content-security-policy/nonce-hiding/script-nonces-hidden.html.ini +++ /dev/null @@ -1,31 +0,0 @@ -[script-nonces-hidden.html] - [Cloned node retains nonce.] - expected: FAIL - - [createElement.nonce.] - expected: FAIL - - [Writing 'nonce' content attribute.] - expected: FAIL - - [Cloned node retains nonce when inserted.] - expected: FAIL - - [Reading 'nonce' content attribute and IDL attribute.] - expected: FAIL - - [Document-written script's nonce value.] - expected: FAIL - - [setAttribute('nonce') overwrites '.nonce' upon insertion.] - expected: FAIL - - [Custom elements expose the correct events.] - expected: FAIL - - [Nonces don't leak via CSS side-channels.] - expected: FAIL - - [createElement.setAttribute.] - expected: FAIL - diff --git a/testing/web-platform/meta/content-security-policy/nonce-hiding/svgscript-nonces-hidden-meta.sub.html.ini b/testing/web-platform/meta/content-security-policy/nonce-hiding/svgscript-nonces-hidden-meta.sub.html.ini deleted file mode 100644 index 2e1ed3ff13bc..000000000000 --- a/testing/web-platform/meta/content-security-policy/nonce-hiding/svgscript-nonces-hidden-meta.sub.html.ini +++ /dev/null @@ -1,19 +0,0 @@ -[svgscript-nonces-hidden-meta.sub.html] - [Cloned node retains nonce.] - expected: FAIL - - [Writing 'nonce' content attribute.] - expected: FAIL - - [Cloned node retains nonce when inserted.] - expected: FAIL - - [Reading 'nonce' content attribute and IDL attribute.] - expected: FAIL - - [Document-written script's nonce value.] - expected: FAIL - - [createElement.setAttribute.] - expected: FAIL - diff --git a/testing/web-platform/meta/content-security-policy/nonce-hiding/svgscript-nonces-hidden.html.ini b/testing/web-platform/meta/content-security-policy/nonce-hiding/svgscript-nonces-hidden.html.ini deleted file mode 100644 index b6ae31b0977a..000000000000 --- a/testing/web-platform/meta/content-security-policy/nonce-hiding/svgscript-nonces-hidden.html.ini +++ /dev/null @@ -1,19 +0,0 @@ -[svgscript-nonces-hidden.html] - [Cloned node retains nonce.] - expected: FAIL - - [Writing 'nonce' content attribute.] - expected: FAIL - - [Cloned node retains nonce when inserted.] - expected: FAIL - - [Reading 'nonce' content attribute and IDL attribute.] - expected: FAIL - - [Document-written script's nonce value.] - expected: FAIL - - [createElement.setAttribute.] - expected: FAIL - diff --git a/testing/web-platform/meta/html/dom/idlharness.https.html.ini b/testing/web-platform/meta/html/dom/idlharness.https.html.ini index cd10b8e24bc8..5f5e9a12e17f 100644 --- a/testing/web-platform/meta/html/dom/idlharness.https.html.ini +++ b/testing/web-platform/meta/html/dom/idlharness.https.html.ini @@ -480,9 +480,6 @@ prefs: [dom.security.featurePolicy.enabled:true, dom.security.featurePolicy.expe [External interface: operation IsSearchProviderInstalled()] expected: FAIL - [SVGElement interface: attribute nonce] - expected: FAIL - [TextMetrics interface: attribute fontBoundingBoxAscent] expected: FAIL @@ -1038,9 +1035,6 @@ prefs: [dom.security.featurePolicy.enabled:true, dom.security.featurePolicy.expe [HTMLMediaElement interface: new Audio() must inherit property "getStartDate()" with the proper type] expected: FAIL - [HTMLElement interface: attribute nonce] - expected: FAIL - [HTMLMediaElement interface: document.createElement("video") must inherit property "audioTracks" with the proper type] expected: FAIL @@ -1056,9 +1050,6 @@ prefs: [dom.security.featurePolicy.enabled:true, dom.security.featurePolicy.expe [HTMLMediaElement interface: document.createElement("audio") must inherit property "getStartDate()" with the proper type] expected: FAIL - [HTMLElement interface: document.createElement("noscript") must inherit property "nonce" with the proper type] - expected: FAIL - [HTMLInputElement interface: createInput("month") must inherit property "dirName" with the proper type] expected: FAIL