Bug 1374612 - CSP: Hide nonce values from the DOM. r=smaug

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

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Christoph Kerschbaumer 2020-02-27 09:32:04 +00:00
Родитель f370e3346e
Коммит ec996a7e9d
18 изменённых файлов: 172 добавлений и 159 удалений

Просмотреть файл

@ -1255,6 +1255,7 @@ Document::Document(const char* aContentType)
mHasCSP(false),
mHasUnsafeEvalCSP(false),
mHasUnsafeInlineCSP(false),
mHasCSPDeliveredThroughHeader(false),
mBFCacheDisallowed(false),
mHasHadDefaultView(false),
mStyleSheetChangeEventsEnabled(false),
@ -3393,6 +3394,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);
}

Просмотреть файл

@ -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;

Просмотреть файл

@ -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

Просмотреть файл

@ -473,7 +473,10 @@ Maybe<nsStyleLinkElement::SheetInfo> HTMLLinkElement::GetStyleSheetInfo() {
referrerInfo->InitWithNode(this);
nsAutoString nonce;
GetAttr(kNameSpaceID_None, nsGkAtoms::nonce, nonce);
nsString* cspNonce = static_cast<nsString*>(GetProperty(nsGkAtoms::nonce));
if (cspNonce) {
nonce = *cspNonce;
}
return Some(SheetInfo{
*OwnerDoc(),

Просмотреть файл

@ -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<nsString*>(GetProperty(nsGkAtoms::nonce));
if (nonce) {
static_cast<nsGenericHTMLElement*>(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<nsGenericHTMLElement>(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(

Просмотреть файл

@ -140,6 +140,18 @@ class nsGenericHTMLElement : public nsGenericHTMLElementBase {
return false;
}
void SetNonce(const nsAString& aNonce) {
SetProperty(nsGkAtoms::nonce, new nsString(aNonce),
nsINode::DeleteProperty<nsString>);
}
void RemoveNonce() { RemoveProperty(nsGkAtoms::nonce); }
void GetNonce(nsAString& aNonce) const {
nsString* cspNonce = static_cast<nsString*>(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.

Просмотреть файл

@ -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> element = do_QueryInterface(aContext);
if (element && element->IsHTMLElement()) {
nsAutoString cspNonce;
element->GetAttr(nsGkAtoms::nonce, cspNonce);
secCheckLoadInfo->SetCspNonce(cspNonce);
nsCOMPtr<nsINode> node = do_QueryInterface(aContext);
if (node) {
nsString* cspNonce =
static_cast<nsString*>(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> element = do_QueryInterface(context);
if (element && element->IsHTMLElement()) {
nsAutoString cspNonce;
element->GetAttr(nsGkAtoms::nonce, cspNonce);
nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
loadInfo->SetCspNonce(cspNonce);
if (context) {
nsString* cspNonce =
static_cast<nsString*>(context->GetProperty(nsGkAtoms::nonce));
if (cspNonce) {
nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
loadInfo->SetCspNonce(*cspNonce);
}
}
}
@ -1497,7 +1500,14 @@ static bool CSPAllowsInlineScript(nsIScriptElement* aElement,
// query the nonce
nsCOMPtr<Element> scriptContent = do_QueryInterface(aElement);
nsAutoString nonce;
scriptContent->GetAttr(kNameSpaceID_None, nsGkAtoms::nonce, nonce);
if (scriptContent) {
nsString* cspNonce =
static_cast<nsString*>(scriptContent->GetProperty(nsGkAtoms::nonce));
if (cspNonce) {
nonce = *cspNonce;
}
}
bool parserCreated =
aElement->GetParserCreated() != mozilla::dom::NOT_FROM_PARSER;

Просмотреть файл

@ -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<nsString*>(GetProperty(nsGkAtoms::nonce));
if (nonce) {
static_cast<SVGElement*>(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<SVGElement>(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);
}

Просмотреть файл

@ -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<nsString>);
}
void RemoveNonce() { RemoveProperty(nsGkAtoms::nonce); }
void GetNonce(nsAString& aNonce) const {
nsString* cspNonce = static_cast<nsString*>(GetProperty(nsGkAtoms::nonce));
if (cspNonce) {
aNonce = *cspNonce;
}
}
// nsIContent interface methods
virtual nsresult BindToTree(BindContext&, nsINode& aParent) override;

Просмотреть файл

@ -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;

Просмотреть файл

@ -19,6 +19,8 @@ interface SVGElement : Element {
readonly attribute SVGSVGElement? ownerSVGElement;
readonly attribute SVGElement? viewportElement;
attribute DOMString nonce;
};
SVGElement includes GlobalEventHandlers;

Просмотреть файл

@ -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> 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<nsILoadInfo> loadInfo = channel->LoadInfo();
loadInfo->SetCspNonce(cspNonce);
nsString* cspNonce = static_cast<nsString*>(
aLoadData.mRequestingNode->GetProperty(nsGkAtoms::nonce));
if (cspNonce) {
nsCOMPtr<nsILoadInfo> 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> 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<nsILoadInfo> loadInfo = channel->LoadInfo();
loadInfo->SetCspNonce(cspNonce);
nsString* cspNonce = static_cast<nsString*>(
aLoadData.mRequestingNode->GetProperty(nsGkAtoms::nonce));
if (cspNonce) {
nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
loadInfo->SetCspNonce(*cspNonce);
}
}
}

Просмотреть файл

@ -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<nsString*>(aElement->GetProperty(nsGkAtoms::nonce));
if (cspNonce) {
nonce = *cspNonce;
}
}
bool allowInlineStyle = true;

Просмотреть файл

@ -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

Просмотреть файл

@ -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

Просмотреть файл

@ -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

Просмотреть файл

@ -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

Просмотреть файл

@ -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