Bug 1529338 - Implement CSP 'style-src-elem' and 'style-src-attr' directives. r=freddyb,emilio,dveditz

Differential Revision: https://phabricator.services.mozilla.com/D151926
This commit is contained in:
Tom Schuster 2022-08-01 12:32:59 +00:00
parent 059ff10a60
commit 4b8ffda4e2
35 changed files with 118 additions and 146 deletions

View File

@ -64,6 +64,8 @@ interface nsIContentSecurityPolicy : nsISerializable
NAVIGATE_TO_DIRECTIVE = 21, NAVIGATE_TO_DIRECTIVE = 21,
SCRIPT_SRC_ELEM_DIRECTIVE = 22, SCRIPT_SRC_ELEM_DIRECTIVE = 22,
SCRIPT_SRC_ATTR_DIRECTIVE = 23, SCRIPT_SRC_ATTR_DIRECTIVE = 23,
STYLE_SRC_ELEM_DIRECTIVE = 24,
STYLE_SRC_ATTR_DIRECTIVE = 25,
}; };
/** /**

View File

@ -201,11 +201,17 @@ bool nsCSPContext::permitsInternal(
permits = false; permits = false;
} }
// See the comment in nsCSPContext::GetAllowsInline. // In CSP 3.0 the effective directive doesn't become the actually used
// directive in case of a fallback from e.g. script-src-elem to
// script-src or default-src.
// TODO(bug 1779369): Fix this for all directive types.
nsAutoString effectiveDirective(violatedDirective); nsAutoString effectiveDirective(violatedDirective);
if ((StaticPrefs::security_csp_script_src_attr_elem_enabled() && if ((StaticPrefs::security_csp_script_src_attr_elem_enabled() &&
(aDir == SCRIPT_SRC_ELEM_DIRECTIVE || (aDir == SCRIPT_SRC_ELEM_DIRECTIVE ||
aDir == SCRIPT_SRC_ATTR_DIRECTIVE))) { aDir == SCRIPT_SRC_ATTR_DIRECTIVE)) ||
(StaticPrefs::security_csp_style_src_attr_elem_enabled() &&
(aDir == STYLE_SRC_ELEM_DIRECTIVE ||
aDir == STYLE_SRC_ATTR_DIRECTIVE))) {
effectiveDirective.AssignASCII(CSP_CSPDirectiveToString(aDir)); effectiveDirective.AssignASCII(CSP_CSPDirectiveToString(aDir));
} }
@ -584,9 +590,10 @@ nsCSPContext::GetAllowsInline(CSPDirective aDirective, const nsAString& aNonce,
if (aDirective != SCRIPT_SRC_ELEM_DIRECTIVE && if (aDirective != SCRIPT_SRC_ELEM_DIRECTIVE &&
aDirective != SCRIPT_SRC_ATTR_DIRECTIVE && aDirective != SCRIPT_SRC_ATTR_DIRECTIVE &&
aDirective != STYLE_SRC_DIRECTIVE) { aDirective != STYLE_SRC_ELEM_DIRECTIVE &&
aDirective != STYLE_SRC_ATTR_DIRECTIVE) {
MOZ_ASSERT(false, MOZ_ASSERT(false,
"can only allow inline for script-src-(attr/elem) or style"); "can only allow inline for (script/style)-src-(attr/elem)");
return NS_OK; return NS_OK;
} }
@ -634,6 +641,7 @@ nsCSPContext::GetAllowsInline(CSPDirective aDirective, const nsAString& aNonce,
bool reportSample = false; bool reportSample = false;
mPolicies[i]->getDirectiveStringAndReportSampleForContentType( mPolicies[i]->getDirectiveStringAndReportSampleForContentType(
aDirective, violatedDirective, &reportSample); aDirective, violatedDirective, &reportSample);
// In CSP 3.0 the effective directive doesn't become the actually used // In CSP 3.0 the effective directive doesn't become the actually used
// directive in case of a fallback from e.g. script-src-elem to // directive in case of a fallback from e.g. script-src-elem to
// script-src or default-src. // script-src or default-src.
@ -641,7 +649,10 @@ nsCSPContext::GetAllowsInline(CSPDirective aDirective, const nsAString& aNonce,
nsAutoString effectiveDirective(violatedDirective); nsAutoString effectiveDirective(violatedDirective);
if ((StaticPrefs::security_csp_script_src_attr_elem_enabled() && if ((StaticPrefs::security_csp_script_src_attr_elem_enabled() &&
(aDirective == SCRIPT_SRC_ELEM_DIRECTIVE || (aDirective == SCRIPT_SRC_ELEM_DIRECTIVE ||
aDirective == SCRIPT_SRC_ATTR_DIRECTIVE))) { aDirective == SCRIPT_SRC_ATTR_DIRECTIVE)) ||
(StaticPrefs::security_csp_style_src_attr_elem_enabled() &&
(aDirective == STYLE_SRC_ELEM_DIRECTIVE ||
aDirective == STYLE_SRC_ATTR_DIRECTIVE))) {
effectiveDirective.AssignASCII(CSP_CSPDirectiveToString(aDirective)); effectiveDirective.AssignASCII(CSP_CSPDirectiveToString(aDirective));
} }
@ -651,6 +662,7 @@ nsCSPContext::GetAllowsInline(CSPDirective aDirective, const nsAString& aNonce,
aLineNumber, aColumnNumber); aLineNumber, aColumnNumber);
} }
} }
return NS_OK; return NS_OK;
} }
@ -1519,8 +1531,8 @@ class CSPReportSenderRunnable final : public Runnable {
} }
// 4) fire violation event // 4) fire violation event
// A frame-ancestors violation has occurred, but we should not dispatch the // A frame-ancestors violation has occurred, but we should not dispatch
// violation event to a potentially cross-origin ancestor. // the violation event to a potentially cross-origin ancestor.
if (!mViolatedDirective.EqualsLiteral("frame-ancestors")) { if (!mViolatedDirective.EqualsLiteral("frame-ancestors")) {
mCSPContext->FireViolationEvent(mTriggeringElement, mCSPEventListener, mCSPContext->FireViolationEvent(mTriggeringElement, mCSPEventListener,
init); init);
@ -1869,9 +1881,9 @@ CSPReportRedirectSink::AsyncOnChannelRedirect(
nsresult rv = aOldChannel->Cancel(NS_ERROR_ABORT); nsresult rv = aOldChannel->Cancel(NS_ERROR_ABORT);
NS_ENSURE_SUCCESS(rv, rv); NS_ENSURE_SUCCESS(rv, rv);
// notify an observer that we have blocked the report POST due to a redirect, // notify an observer that we have blocked the report POST due to a
// used in testing, do this async since we're in an async call now to begin // redirect, used in testing, do this async since we're in an async call now
// with // to begin with
nsCOMPtr<nsIURI> uri; nsCOMPtr<nsIURI> uri;
rv = aOldChannel->GetURI(getter_AddRefs(uri)); rv = aOldChannel->GetURI(getter_AddRefs(uri));
NS_ENSURE_SUCCESS(rv, rv); NS_ENSURE_SUCCESS(rv, rv);

View File

@ -50,6 +50,7 @@ nsCSPParser::nsCSPParser(policyTokens& aTokens, nsIURI* aSelfURI,
mFrameSrc(nullptr), mFrameSrc(nullptr),
mWorkerSrc(nullptr), mWorkerSrc(nullptr),
mScriptSrc(nullptr), mScriptSrc(nullptr),
mStyleSrc(nullptr),
mParsingFrameAncestorsDir(false), mParsingFrameAncestorsDir(false),
mTokens(aTokens.Clone()), mTokens(aTokens.Clone()),
mSelfURI(aSelfURI), mSelfURI(aSelfURI),
@ -859,9 +860,13 @@ nsCSPDirective* nsCSPParser::directiveName() {
} }
// script-src-attr and script-scr-elem might have been disabled. // script-src-attr and script-scr-elem might have been disabled.
if ((directive == nsIContentSecurityPolicy::SCRIPT_SRC_ATTR_DIRECTIVE || // Similarly style-src-{attr, elem}.
directive == nsIContentSecurityPolicy::SCRIPT_SRC_ELEM_DIRECTIVE) && if (((directive == nsIContentSecurityPolicy::SCRIPT_SRC_ATTR_DIRECTIVE ||
!StaticPrefs::security_csp_script_src_attr_elem_enabled()) { directive == nsIContentSecurityPolicy::SCRIPT_SRC_ELEM_DIRECTIVE) &&
!StaticPrefs::security_csp_script_src_attr_elem_enabled()) ||
((directive == nsIContentSecurityPolicy::STYLE_SRC_ATTR_DIRECTIVE ||
directive == nsIContentSecurityPolicy::STYLE_SRC_ELEM_DIRECTIVE) &&
!StaticPrefs::security_csp_style_src_attr_elem_enabled())) {
AutoTArray<nsString, 1> params = {mCurToken}; AutoTArray<nsString, 1> params = {mCurToken};
logWarningErrorToConsole(nsIScriptError::warningFlag, logWarningErrorToConsole(nsIScriptError::warningFlag,
"notSupportingDirective", params); "notSupportingDirective", params);
@ -941,6 +946,13 @@ nsCSPDirective* nsCSPParser::directiveName() {
return mScriptSrc; return mScriptSrc;
} }
// If we have a style-src, cache it as a fallback for style-src-elem and
// style-src-attr.
if (directive == nsIContentSecurityPolicy::STYLE_SRC_DIRECTIVE) {
mStyleSrc = new nsCSPStyleSrcDirective(directive);
return mStyleSrc;
}
return new nsCSPDirective(directive); return new nsCSPDirective(directive);
} }
@ -1155,6 +1167,20 @@ nsCSPPolicy* nsCSPParser::policy() {
mScriptSrc->setRestrictScriptAttr(); mScriptSrc->setRestrictScriptAttr();
} }
// If style-src is specified and style-src-elem is not specified, then
// style-src serves as a fallback.
if (mStyleSrc && !mPolicy->hasDirective(
nsIContentSecurityPolicy::STYLE_SRC_ELEM_DIRECTIVE)) {
mStyleSrc->setRestrictStyleElem();
}
// If style-src is specified and style-attr-elem is not specified, then
// style-src serves as a fallback.
if (mStyleSrc && !mPolicy->hasDirective(
nsIContentSecurityPolicy::STYLE_SRC_ATTR_DIRECTIVE)) {
mStyleSrc->setRestrictStyleAttr();
}
return mPolicy; return mPolicy;
} }

View File

@ -198,6 +198,7 @@ class nsCSPParser {
nsCSPDirective* mFrameSrc; nsCSPDirective* mFrameSrc;
nsCSPDirective* mWorkerSrc; nsCSPDirective* mWorkerSrc;
nsCSPScriptSrcDirective* mScriptSrc; nsCSPScriptSrcDirective* mScriptSrc;
nsCSPStyleSrcDirective* mStyleSrc;
// cache variable to let nsCSPHostSrc know that it's within // cache variable to let nsCSPHostSrc know that it's within
// the frame-ancestors directive. // the frame-ancestors directive.

View File

@ -289,7 +289,7 @@ CSPDirective CSP_ContentTypeToDirective(nsContentPolicyType aType) {
case nsIContentPolicy::TYPE_STYLESHEET: case nsIContentPolicy::TYPE_STYLESHEET:
case nsIContentPolicy::TYPE_INTERNAL_STYLESHEET: case nsIContentPolicy::TYPE_INTERNAL_STYLESHEET:
case nsIContentPolicy::TYPE_INTERNAL_STYLESHEET_PRELOAD: case nsIContentPolicy::TYPE_INTERNAL_STYLESHEET_PRELOAD:
return nsIContentSecurityPolicy::STYLE_SRC_DIRECTIVE; return nsIContentSecurityPolicy::STYLE_SRC_ELEM_DIRECTIVE;
case nsIContentPolicy::TYPE_FONT: case nsIContentPolicy::TYPE_FONT:
case nsIContentPolicy::TYPE_INTERNAL_FONT_PRELOAD: case nsIContentPolicy::TYPE_INTERNAL_FONT_PRELOAD:
@ -1293,10 +1293,7 @@ bool nsCSPChildSrcDirective::equals(CSPDirective aDirective) const {
/* =============== nsCSPScriptSrcDirective ============= */ /* =============== nsCSPScriptSrcDirective ============= */
nsCSPScriptSrcDirective::nsCSPScriptSrcDirective(CSPDirective aDirective) nsCSPScriptSrcDirective::nsCSPScriptSrcDirective(CSPDirective aDirective)
: nsCSPDirective(aDirective), : nsCSPDirective(aDirective) {}
mRestrictWorkers(false),
mRestrictScriptElem(false),
mRestrictScriptAttr(false) {}
nsCSPScriptSrcDirective::~nsCSPScriptSrcDirective() = default; nsCSPScriptSrcDirective::~nsCSPScriptSrcDirective() = default;
@ -1310,7 +1307,24 @@ bool nsCSPScriptSrcDirective::equals(CSPDirective aDirective) const {
if (aDirective == nsIContentSecurityPolicy::SCRIPT_SRC_ATTR_DIRECTIVE) { if (aDirective == nsIContentSecurityPolicy::SCRIPT_SRC_ATTR_DIRECTIVE) {
return mRestrictScriptAttr; return mRestrictScriptAttr;
} }
return (mDirective == aDirective); return mDirective == aDirective;
}
/* =============== nsCSPStyleSrcDirective ============= */
nsCSPStyleSrcDirective::nsCSPStyleSrcDirective(CSPDirective aDirective)
: nsCSPDirective(aDirective) {}
nsCSPStyleSrcDirective::~nsCSPStyleSrcDirective() = default;
bool nsCSPStyleSrcDirective::equals(CSPDirective aDirective) const {
if (aDirective == nsIContentSecurityPolicy::STYLE_SRC_ELEM_DIRECTIVE) {
return mRestrictStyleElem;
}
if (aDirective == nsIContentSecurityPolicy::STYLE_SRC_ATTR_DIRECTIVE) {
return mRestrictStyleAttr;
}
return mDirective == aDirective;
} }
/* =============== nsBlockAllMixedContentDirective ============= */ /* =============== nsBlockAllMixedContentDirective ============= */

View File

@ -91,6 +91,8 @@ static const char* CSPStrDirectives[] = {
"navigate-to", // NAVIGATE_TO_DIRECTIVE "navigate-to", // NAVIGATE_TO_DIRECTIVE
"script-src-elem", // SCRIPT_SRC_ELEM_DIRECTIVE "script-src-elem", // SCRIPT_SRC_ELEM_DIRECTIVE
"script-src-attr", // SCRIPT_SRC_ATTR_DIRECTIVE "script-src-attr", // SCRIPT_SRC_ATTR_DIRECTIVE
"style-src-elem", // STYLE_SRC_ELEM_DIRECTIVE
"style-src-attr", // STYLE_SRC_ATTR_DIRECTIVE
}; };
inline const char* CSP_CSPDirectiveToString(CSPDirective aDir) { inline const char* CSP_CSPDirectiveToString(CSPDirective aDir) {
@ -515,12 +517,33 @@ class nsCSPScriptSrcDirective : public nsCSPDirective {
void setRestrictScriptElem() { mRestrictScriptElem = true; } void setRestrictScriptElem() { mRestrictScriptElem = true; }
void setRestrictScriptAttr() { mRestrictScriptAttr = true; } void setRestrictScriptAttr() { mRestrictScriptAttr = true; }
virtual bool equals(CSPDirective aDirective) const override; bool equals(CSPDirective aDirective) const override;
private: private:
bool mRestrictWorkers; bool mRestrictWorkers = false;
bool mRestrictScriptElem; bool mRestrictScriptElem = false;
bool mRestrictScriptAttr; bool mRestrictScriptAttr = false;
};
/* =============== nsCSPStyleSrcDirective ============= */
/*
* In CSP 3 style-src is use as a fallback for style-src-elem and
* style-src-attr.
*/
class nsCSPStyleSrcDirective : public nsCSPDirective {
public:
explicit nsCSPStyleSrcDirective(CSPDirective aDirective);
virtual ~nsCSPStyleSrcDirective();
void setRestrictStyleElem() { mRestrictStyleElem = true; }
void setRestrictStyleAttr() { mRestrictStyleAttr = true; }
bool equals(CSPDirective aDirective) const override;
private:
bool mRestrictStyleElem = false;
bool mRestrictStyleAttr = false;
}; };
/* =============== nsBlockAllMixedContentDirective === */ /* =============== nsBlockAllMixedContentDirective === */

View File

@ -50,7 +50,7 @@ function checkResults(reportStr) {
"http://mochi.test:8888/tests/dom/security/test/csp/test_report_for_import.html", "http://mochi.test:8888/tests/dom/security/test/csp/test_report_for_import.html",
"Incorrect referrer"); "Incorrect referrer");
is(cspReport["violated-directive"], is(cspReport["violated-directive"],
"style-src", "style-src-elem",
"Incorrect violated-directive"); "Incorrect violated-directive");
is(cspReport["original-policy"], POLICY, "Incorrect original-policy"); is(cspReport["original-policy"], POLICY, "Incorrect original-policy");
is(cspReport["blocked-uri"], is(cspReport["blocked-uri"],

View File

@ -306,9 +306,12 @@ bool nsStyleUtil::CSPAllowsInlineStyle(
return true; return true;
} }
nsIContentSecurityPolicy::CSPDirective directive =
nsIContentSecurityPolicy::STYLE_SRC_ATTR_DIRECTIVE;
// query the nonce // query the nonce
nsAutoString nonce; nsAutoString nonce;
if (aElement && aElement->NodeInfo()->NameAtom() == nsGkAtoms::style) { if (aElement && aElement->NodeInfo()->NameAtom() == nsGkAtoms::style) {
directive = nsIContentSecurityPolicy::STYLE_SRC_ELEM_DIRECTIVE;
nsString* cspNonce = nsString* cspNonce =
static_cast<nsString*>(aElement->GetProperty(nsGkAtoms::nonce)); static_cast<nsString*>(aElement->GetProperty(nsGkAtoms::nonce));
if (cspNonce) { if (cspNonce) {
@ -317,11 +320,11 @@ bool nsStyleUtil::CSPAllowsInlineStyle(
} }
bool allowInlineStyle = true; bool allowInlineStyle = true;
rv = csp->GetAllowsInline( rv = csp->GetAllowsInline(directive, nonce,
nsIContentSecurityPolicy::STYLE_SRC_DIRECTIVE, nonce, false, // aParserCreated only applies to scripts
false, // aParserCreated only applies to scripts aElement, nullptr, // nsICSPEventListener
aElement, nullptr, // nsICSPEventListener aStyleText, aLineNumber, aColumnNumber,
aStyleText, aLineNumber, aColumnNumber, &allowInlineStyle); &allowInlineStyle);
NS_ENSURE_SUCCESS(rv, false); NS_ENSURE_SUCCESS(rv, false);
return allowInlineStyle; return allowInlineStyle;

View File

@ -12337,6 +12337,12 @@
value: @IS_NIGHTLY_BUILD@ value: @IS_NIGHTLY_BUILD@
mirror: always mirror: always
# The style-src-attr and style-src-elem directive
- name: security.csp.style-src-attr-elem.enabled
type: bool
value: @IS_NIGHTLY_BUILD@
mirror: always
# No way to enable on Android, Bug 1552602 # No way to enable on Android, Bug 1552602
- name: security.webauth.u2f - name: security.webauth.u2f
type: bool type: bool

View File

@ -1 +1 @@
prefs: [security.csp.script-src-attr-elem.enabled:true] prefs: [security.csp.script-src-attr-elem.enabled:true, security.csp.style-src-attr-elem.enabled:true]

View File

@ -1,4 +0,0 @@
[combine-header-and-meta-policies.sub.html]
[Expecting logs: ["TEST COMPLETE", "violated-directive=img-src", "violated-directive=style-src-elem"\]]
expected: FAIL

View File

@ -1,4 +0,0 @@
[style-src-attr-allowed-src-blocked.html]
[Should apply the style attribute]
expected: FAIL

View File

@ -1,8 +0,0 @@
[style-src-attr-blocked-src-allowed.html]
expected: TIMEOUT
[The attribute style should not be applied]
expected: FAIL
[Should fire a security policy violation event]
expected: NOTRUN

View File

@ -1,8 +0,0 @@
[style-src-elem-allowed-attr-blocked.html]
expected: TIMEOUT
[The attribute style should not be applied and the inline style should be applied]
expected: FAIL
[Should fire a security policy violation for the attribute]
expected: NOTRUN

View File

@ -1,4 +0,0 @@
[style-src-elem-allowed-src-blocked.html]
[Inline style should be applied]
expected: FAIL

View File

@ -1,8 +0,0 @@
[style-src-elem-blocked-attr-allowed.html]
expected: TIMEOUT
[Should fire a security policy violation for the inline block]
expected: NOTRUN
[The inline style should not be applied and the attribute style should be applied]
expected: FAIL

View File

@ -1,8 +0,0 @@
[style-src-elem-blocked-src-allowed.html]
expected: TIMEOUT
[Should fire a security policy violation event]
expected: NOTRUN
[The inline style should not be applied]
expected: FAIL

View File

@ -1,3 +0,0 @@
[injected-inline-style-blocked.sub.html]
[Expecting logs: ["violated-directive=style-src-elem","violated-directive=style-src-elem","PASS"\]]
expected: FAIL

View File

@ -1,7 +1,4 @@
[inline-style-allowed-while-cloning-objects.sub.html] [inline-style-allowed-while-cloning-objects.sub.html]
[Test that violation report event was fired]
expected: FAIL
[inline-style-allowed-while-cloning-objects 18] [inline-style-allowed-while-cloning-objects 18]
expected: FAIL expected: FAIL

View File

@ -1,5 +0,0 @@
implementation-status: backlog
[inline-style-attribute-blocked.sub.html]
[Expecting logs: ["violated-directive=style-src-attr","PASS"\]]
expected: FAIL

View File

@ -1,6 +1,3 @@
[style-blocked.html] [style-blocked.html]
[Violated directive is script-src-elem.]
expected: FAIL
[document.styleSheets should contain an item for the blocked CSS.] [document.styleSheets should contain an item for the blocked CSS.]
expected: FAIL expected: FAIL

View File

@ -1,4 +0,0 @@
[style-src-hash-blocked.html]
[Should fire a securitypolicyviolation event]
expected: FAIL

View File

@ -1,4 +0,0 @@
[style-src-imported-style-blocked.html]
[Should fire a securitypolicyviolation event]
expected: FAIL

View File

@ -1,4 +0,0 @@
[style-src-injected-inline-style-blocked.html]
[Should fire a securitypolicyviolation event]
expected: FAIL

View File

@ -1,4 +0,0 @@
[style-src-injected-stylesheet-blocked.sub.html]
[Should fire a securitypolicyviolation event]
expected: FAIL

View File

@ -1,5 +0,0 @@
implementation-status: backlog
[style-src-inline-style-attribute-blocked.html]
[Should fire a securitypolicyviolation event]
expected: FAIL

View File

@ -1,4 +0,0 @@
[style-src-inline-style-blocked.html]
[Should fire a securitypolicyviolation event]
expected: FAIL

View File

@ -4,6 +4,3 @@ implementation-status: backlog
[Test that paragraph remains unmodified and error events received.] [Test that paragraph remains unmodified and error events received.]
expected: NOTRUN expected: NOTRUN
[Should fire a securitypolicyviolation event]
expected: FAIL

View File

@ -1,4 +0,0 @@
[style-src-inline-style-nonce-blocked.html]
[Should fire a securitypolicyviolation event]
expected: FAIL

View File

@ -1,4 +0,0 @@
[style-src-none-blocked.html]
[Should fire a securitypolicyviolation event]
expected: FAIL

View File

@ -1,4 +0,0 @@
[style-src-stylesheet-nonce-blocked.html]
[Should fire a securitypolicyviolation event]
expected: FAIL

View File

@ -1,4 +0,0 @@
[stylehash-basic-blocked.sub.html]
[Expecting alerts: ["PASS: The 'p' element's text is green, which means the style was correctly applied.", "violated-directive=style-src-elem"\]]
expected: FAIL

View File

@ -1,4 +0,0 @@
[stylenonce-allowed.sub.html]
[Should fire securitypolicyviolation]
expected: FAIL

View File

@ -1,4 +0,0 @@
[stylenonce-blocked.sub.html]
[Should fire securitypolicyviolation]
expected: FAIL

View File

@ -1,5 +0,0 @@
implementation-status: backlog
[style_attribute_denied_wrong_hash.html]
[Test that the inline style attribute is blocked]
expected: FAIL