diff --git a/dom/events/EventListenerManager.cpp b/dom/events/EventListenerManager.cpp index c5c53ece18..405142a951 100644 --- a/dom/events/EventListenerManager.cpp +++ b/dom/events/EventListenerManager.cpp @@ -797,7 +797,7 @@ EventListenerManager::SetEventHandler(nsIAtom* aName, if (csp) { bool allowsInlineScript = true; - rv = csp->GetAllowsInline(nsIContentPolicy::TYPE_SCRIPT, + rv = csp->GetAllowsInline(nsIContentSecurityPolicy::SCRIPT_SRC_ATTR_DIRECTIVE, EmptyString(), // aNonce true, // aParserCreated (true because attribute event handler) aBody, diff --git a/dom/interfaces/security/nsIContentSecurityPolicy.idl b/dom/interfaces/security/nsIContentSecurityPolicy.idl index bdcbf908bf..191ddb6e4b 100644 --- a/dom/interfaces/security/nsIContentSecurityPolicy.idl +++ b/dom/interfaces/security/nsIContentSecurityPolicy.idl @@ -60,6 +60,10 @@ interface nsIContentSecurityPolicy : nsISerializable const unsigned short REQUIRE_SRI_FOR = 20; const unsigned short SANDBOX_DIRECTIVE = 21; const unsigned short WORKER_SRC_DIRECTIVE = 22; + const unsigned short SCRIPT_SRC_ELEM_DIRECTIVE = 23; + const unsigned short SCRIPT_SRC_ATTR_DIRECTIVE = 24; + const unsigned short STYLE_SRC_ELEM_DIRECTIVE = 25; + const unsigned short STYLE_SRC_ATTR_DIRECTIVE = 26; /** * Accessor method for a read-only string version of the policy at a given @@ -146,7 +150,7 @@ interface nsIContentSecurityPolicy : nsISerializable * Whether or not the effects of the inline style should be allowed * (block the rules if false). */ - boolean getAllowsInline(in nsContentPolicyType aContentPolicyType, + boolean getAllowsInline(in CSPDirective aContentPolicyType, in AString aNonce, in boolean aParserCreated, in AString aContent, diff --git a/dom/jsurl/nsJSProtocolHandler.cpp b/dom/jsurl/nsJSProtocolHandler.cpp index 9a9541164f..d16bafdc23 100644 --- a/dom/jsurl/nsJSProtocolHandler.cpp +++ b/dom/jsurl/nsJSProtocolHandler.cpp @@ -179,8 +179,10 @@ nsresult nsJSThunk::EvaluateScript(nsIChannel *aChannel, rv = principal->GetCsp(getter_AddRefs(csp)); NS_ENSURE_SUCCESS(rv, rv); if (csp) { + // javascript: is a "navigation" type, so script-src-elem applies. + // https://w3c.github.io/webappsec-csp/#effective-directive-for-inline-check bool allowsInlineScript = true; - rv = csp->GetAllowsInline(nsIContentPolicy::TYPE_SCRIPT, + rv = csp->GetAllowsInline(nsIContentSecurityPolicy::SCRIPT_SRC_ELEM_DIRECTIVE, EmptyString(), // aNonce true, // aParserCreated EmptyString(), // aContent diff --git a/dom/locales/en-US/chrome/security/csp.properties b/dom/locales/en-US/chrome/security/csp.properties index da38227403..28ad5afd91 100644 --- a/dom/locales/en-US/chrome/security/csp.properties +++ b/dom/locales/en-US/chrome/security/csp.properties @@ -35,17 +35,22 @@ ignoringDuplicateSrc = Ignoring duplicate source %1$S # LOCALIZATION NOTE (ignoringSrcFromMetaCSP): # %1$S defines the ignored src ignoringSrcFromMetaCSP = Ignoring source ‘%1$S’ (Not supported when delivered via meta element). -# LOCALIZATION NOTE (ignoringSrcWithinScriptStyleSrc): +# LOCALIZATION NOTE (ignoringSrcWithinNonceOrHashDirective): +# %1$S is the ignored src (e.g. "unsafe-inline") +# %2$S is the directive (e.g. "script-src-elem") +ignoringSrcWithinNonceOrHashDirective = Ignoring “%1$S” within %2$S: nonce-source or hash-source specified +# LOCALIZATION NOTE (ignoringScriptSrcForStrictDynamic): # %1$S is the ignored src -# script-src and style-src are directive names and should not be localized -ignoringSrcWithinScriptStyleSrc = Ignoring “%1$S” within script-src or style-src: nonce-source or hash-source specified -# LOCALIZATION NOTE (ignoringSrcForStrictDynamic): -# %1$S is the ignored src -# script-src, as well as 'strict-dynamic' should not be localized -ignoringSrcForStrictDynamic = Ignoring “%1$S” within script-src: ‘strict-dynamic’ specified +# %2$S is the directive src (e.g. "script-src-elem") +# 'strict-dynamic' should not be localized +ignoringScriptSrcForStrictDynamic = Ignoring “%1$S” within %2$S: ‘strict-dynamic’ specified # LOCALIZATION NOTE (ignoringStrictDynamic): # %1$S is the ignored src ignoringStrictDynamic = Ignoring source “%1$S” (Only supported within script-src). +# LOCALIZATION NOTE (ignoringUnsafeEval): +# %1$S is the csp directive (e.g. script-src-elem) +# 'unsafe-eval' and 'wasm-unsafe-eval' should not be localized +ignoringUnsafeEval = Ignoring ‘unsafe-eval’ or ‘wasm-unsafe-eval’ inside “%1$S”. # LOCALIZATION NOTE (strictDynamicButNoHashOrNonce): # %1$S is the csp directive that contains 'strict-dynamic' # 'strict-dynamic' should not be localized diff --git a/dom/script/ScriptLoader.cpp b/dom/script/ScriptLoader.cpp index a409b95741..a61ba92c23 100644 --- a/dom/script/ScriptLoader.cpp +++ b/dom/script/ScriptLoader.cpp @@ -1471,7 +1471,7 @@ CSPAllowsInlineScript(nsIScriptElement *aElement, nsIDocument *aDocument) aElement->GetScriptText(scriptText); bool allowInlineScript = false; - rv = csp->GetAllowsInline(nsIContentPolicy::TYPE_SCRIPT, + rv = csp->GetAllowsInline(nsIContentSecurityPolicy::SCRIPT_SRC_ELEM_DIRECTIVE, nonce, parserCreated, scriptText, aElement->GetScriptLineNumber(), aElement->GetScriptColumnNumber(), diff --git a/dom/security/nsCSPContext.cpp b/dom/security/nsCSPContext.cpp index 9eafd9498b..60cf33aeac 100644 --- a/dom/security/nsCSPContext.cpp +++ b/dom/security/nsCSPContext.cpp @@ -469,7 +469,7 @@ nsCSPContext::GetAllowsEval(bool* outShouldReportViolation, *outAllowsEval = true; for (uint32_t i = 0; i < mPolicies.Length(); i++) { - if (!mPolicies[i]->allows(nsIContentPolicy::TYPE_SCRIPT, + if (!mPolicies[i]->allows(SCRIPT_SRC_DIRECTIVE, CSP_UNSAFE_EVAL, EmptyString(), false)) { @@ -486,7 +486,7 @@ nsCSPContext::GetAllowsEval(bool* outShouldReportViolation, // Helper function to report inline violations void -nsCSPContext::reportInlineViolation(nsContentPolicyType aContentType, +nsCSPContext::reportInlineViolation(CSPDirective aDirective, const nsAString& aNonce, const nsAString& aContent, const nsAString& aViolatedDirective, @@ -499,12 +499,14 @@ nsCSPContext::reportInlineViolation(nsContentPolicyType aContentType, // let's report the hash error; no need to report the unsafe-inline error // anymore. if (!aNonce.IsEmpty()) { - observerSubject = (aContentType == nsIContentPolicy::TYPE_SCRIPT) + observerSubject = (aDirective == SCRIPT_SRC_ELEM_DIRECTIVE || + aDirective == SCRIPT_SRC_ATTR_DIRECTIVE) ? NS_LITERAL_STRING(SCRIPT_NONCE_VIOLATION_OBSERVER_TOPIC) : NS_LITERAL_STRING(STYLE_NONCE_VIOLATION_OBSERVER_TOPIC); } else { - observerSubject = (aContentType == nsIContentPolicy::TYPE_SCRIPT) + observerSubject = (aDirective == SCRIPT_SRC_ELEM_DIRECTIVE || + aDirective == SCRIPT_SRC_ATTR_DIRECTIVE) ? NS_LITERAL_STRING(SCRIPT_HASH_VIOLATION_OBSERVER_TOPIC) : NS_LITERAL_STRING(STYLE_HASH_VIOLATION_OBSERVER_TOPIC); } @@ -555,7 +557,7 @@ nsCSPContext::reportInlineViolation(nsContentPolicyType aContentType, } NS_IMETHODIMP -nsCSPContext::GetAllowsInline(nsContentPolicyType aContentType, +nsCSPContext::GetAllowsInline(CSPDirective aDirective, const nsAString& aNonce, bool aParserCreated, const nsAString& aContent, @@ -565,20 +567,19 @@ nsCSPContext::GetAllowsInline(nsContentPolicyType aContentType, { *outAllowsInline = true; - MOZ_ASSERT(aContentType == nsContentUtils::InternalContentPolicyTypeToExternal(aContentType), - "We should only see external content policy types here."); - - if (aContentType != nsIContentPolicy::TYPE_SCRIPT && - aContentType != nsIContentPolicy::TYPE_STYLESHEET) { - MOZ_ASSERT(false, "can only allow inline for script or style"); + if (aDirective != SCRIPT_SRC_ELEM_DIRECTIVE && + aDirective != SCRIPT_SRC_ATTR_DIRECTIVE && + aDirective != STYLE_SRC_ELEM_DIRECTIVE && + aDirective != STYLE_SRC_ATTR_DIRECTIVE) { + MOZ_ASSERT(false, "can only allow inline for (script/style)-src-(attr/elem) or style"); return NS_OK; } // always iterate all policies, otherwise we might not send out all reports for (uint32_t i = 0; i < mPolicies.Length(); i++) { bool allowed = - mPolicies[i]->allows(aContentType, CSP_UNSAFE_INLINE, EmptyString(), aParserCreated) || - mPolicies[i]->allows(aContentType, CSP_NONCE, aNonce, aParserCreated); + mPolicies[i]->allows(aDirective, CSP_UNSAFE_INLINE, EmptyString(), aParserCreated) || + mPolicies[i]->allows(aDirective, CSP_NONCE, aNonce, aParserCreated); // If the inlined script or style is allowed by either unsafe-inline or the // nonce, go ahead and shortcut this loop. @@ -589,7 +590,7 @@ nsCSPContext::GetAllowsInline(nsContentPolicyType aContentType, // Check if the csp-hash matches against the hash of the script. // If we don't have any content to check, block the script. if (!aContent.IsEmpty()) { - allowed = mPolicies[i]->allows(aContentType, CSP_HASH, aContent, aParserCreated); + allowed = mPolicies[i]->allows(aDirective, CSP_HASH, aContent, aParserCreated); } if (!allowed) { @@ -599,8 +600,8 @@ nsCSPContext::GetAllowsInline(nsContentPolicyType aContentType, *outAllowsInline = false; } nsAutoString violatedDirective; - mPolicies[i]->getDirectiveStringForContentType(aContentType, violatedDirective); - reportInlineViolation(aContentType, + mPolicies[i]->getDirectiveStringForContentType(aDirective, violatedDirective); + reportInlineViolation(aDirective, aNonce, aContent, violatedDirective, @@ -641,17 +642,17 @@ nsCSPContext::GetAllowsInline(nsContentPolicyType aContentType, * GetAllowsInline() and do not call this macro, hence we can pass 'false' * as the argument _aParserCreated_ to allows(). */ -#define CASE_CHECK_AND_REPORT(violationType, contentPolicyType, nonceOrHash, \ +#define CASE_CHECK_AND_REPORT(violationType, directive, nonceOrHash, \ keyword, observerTopic) \ case nsIContentSecurityPolicy::VIOLATION_TYPE_ ## violationType : \ PR_BEGIN_MACRO \ - if (!mPolicies[p]->allows(nsIContentPolicy::TYPE_ ## contentPolicyType, \ - keyword, nonceOrHash, false)) \ - { \ + static_assert(directive##_SRC_DIRECTIVE == SCRIPT_SRC_DIRECTIVE || \ + directive##_SRC_DIRECTIVE == STYLE_SRC_DIRECTIVE); \ + if (!mPolicies[p]->allows(directive##_SRC_DIRECTIVE, keyword, nonceOrHash, \ + false)) { \ nsAutoString violatedDirective; \ mPolicies[p]->getDirectiveStringForContentType( \ - nsIContentPolicy::TYPE_ ## contentPolicyType, \ - violatedDirective); \ + directive##_SRC_DIRECTIVE, violatedDirective); \ this->AsyncReportViolation(selfISupports, nullptr, violatedDirective, p, \ NS_LITERAL_STRING(observerTopic), aSourceFile,\ aScriptSample, aLineNum, aColumnNum); \ @@ -711,19 +712,19 @@ nsCSPContext::LogViolationDetails(uint16_t aViolationType, switch (aViolationType) { CASE_CHECK_AND_REPORT(EVAL, SCRIPT, NS_LITERAL_STRING(""), CSP_UNSAFE_EVAL, EVAL_VIOLATION_OBSERVER_TOPIC); - CASE_CHECK_AND_REPORT(INLINE_STYLE, STYLESHEET, NS_LITERAL_STRING(""), + CASE_CHECK_AND_REPORT(INLINE_STYLE, STYLE, NS_LITERAL_STRING(""), CSP_UNSAFE_INLINE, INLINE_STYLE_VIOLATION_OBSERVER_TOPIC); CASE_CHECK_AND_REPORT(INLINE_SCRIPT, SCRIPT, NS_LITERAL_STRING(""), CSP_UNSAFE_INLINE, INLINE_SCRIPT_VIOLATION_OBSERVER_TOPIC); CASE_CHECK_AND_REPORT(NONCE_SCRIPT, SCRIPT, aNonce, CSP_UNSAFE_INLINE, SCRIPT_NONCE_VIOLATION_OBSERVER_TOPIC); - CASE_CHECK_AND_REPORT(NONCE_STYLE, STYLESHEET, aNonce, + CASE_CHECK_AND_REPORT(NONCE_STYLE, STYLE, aNonce, CSP_UNSAFE_INLINE, STYLE_NONCE_VIOLATION_OBSERVER_TOPIC); CASE_CHECK_AND_REPORT(HASH_SCRIPT, SCRIPT, aContent, CSP_UNSAFE_INLINE, SCRIPT_HASH_VIOLATION_OBSERVER_TOPIC); - CASE_CHECK_AND_REPORT(HASH_STYLE, STYLESHEET, aContent, + CASE_CHECK_AND_REPORT(HASH_STYLE, STYLE, aContent, CSP_UNSAFE_INLINE, STYLE_HASH_VIOLATION_OBSERVER_TOPIC); - CASE_CHECK_AND_REPORT(REQUIRE_SRI_FOR_STYLE, STYLESHEET, NS_LITERAL_STRING(""), + CASE_CHECK_AND_REPORT(REQUIRE_SRI_FOR_STYLE, STYLE, NS_LITERAL_STRING(""), CSP_REQUIRE_SRI_FOR, REQUIRE_SRI_STYLE_VIOLATION_OBSERVER_TOPIC); CASE_CHECK_AND_REPORT(REQUIRE_SRI_FOR_SCRIPT, SCRIPT, NS_LITERAL_STRING(""), CSP_REQUIRE_SRI_FOR, REQUIRE_SRI_SCRIPT_VIOLATION_OBSERVER_TOPIC); diff --git a/dom/security/nsCSPContext.h b/dom/security/nsCSPContext.h index 272e4c733a..85c0615c47 100644 --- a/dom/security/nsCSPContext.h +++ b/dom/security/nsCSPContext.h @@ -133,7 +133,7 @@ class nsCSPContext : public nsIContentSecurityPolicy bool aParserCreated); // helper to report inline script/style violations - void reportInlineViolation(nsContentPolicyType aContentType, + void reportInlineViolation(CSPDirective aDirective, const nsAString& aNonce, const nsAString& aContent, const nsAString& aViolatedDirective, diff --git a/dom/security/nsCSPParser.cpp b/dom/security/nsCSPParser.cpp index 1ec8194513..85bee16527 100644 --- a/dom/security/nsCSPParser.cpp +++ b/dom/security/nsCSPParser.cpp @@ -108,12 +108,14 @@ nsCSPParser::nsCSPParser(cspTokens& aTokens, : mCurChar(nullptr) , mEndChar(nullptr) , mHasHashOrNonce(false) + , mHasAnyUnsafeEval(false) , mStrictDynamic(false) , mUnsafeInlineKeywordSrc(nullptr) , mChildSrc(nullptr) , mFrameSrc(nullptr) , mWorkerSrc(nullptr) , mScriptSrc(nullptr) + , mStyleSrc(nullptr) , mParsingFrameAncestorsDir(false) , mTokens(aTokens) , mSelfURI(aSelfURI) @@ -497,7 +499,9 @@ nsCSPParser::keywordSource() if (!sStrictDynamicEnabled) { return nullptr; } - if (!CSP_IsDirective(mCurDir[0], nsIContentSecurityPolicy::SCRIPT_SRC_DIRECTIVE)) { + if (!CSP_IsDirective(mCurDir[0], nsIContentSecurityPolicy::SCRIPT_SRC_DIRECTIVE) && + !CSP_IsDirective(mCurDir[0], nsIContentSecurityPolicy::SCRIPT_SRC_ELEM_DIRECTIVE) && + !CSP_IsDirective(mCurDir[0], nsIContentSecurityPolicy::SCRIPT_SRC_ATTR_DIRECTIVE)) { // Todo: Enforce 'strict-dynamic' within default-src; see Bug 1313937 const char16_t* params[] = { u"strict-dynamic" }; logWarningErrorToConsole(nsIScriptError::warningFlag, "ignoringStrictDynamic", @@ -534,6 +538,7 @@ nsCSPParser::keywordSource() if (doc) { doc->SetHasUnsafeEvalCSP(true); } + mHasAnyUnsafeEval = true; return new nsCSPKeywordSrc(CSP_KeywordToEnum(mCurToken)); } return nullptr; @@ -1077,11 +1082,19 @@ nsCSPParser::directiveName() } // if we have a script-src, cache it as a fallback for worker-src - // in case child-src is not present + // in case child-src is not present. It is also used as a fallback for + // script-src-elem and script-src-attr. if (CSP_IsDirective(mCurToken, nsIContentSecurityPolicy::SCRIPT_SRC_DIRECTIVE)) { mScriptSrc = new nsCSPScriptSrcDirective(CSP_StringToCSPDirective(mCurToken)); return mScriptSrc; } + + // If we have a style-src, cache it as a fallback for style-src-elem and + // style-src-attr. + if (CSP_IsDirective(mCurToken, nsIContentSecurityPolicy::STYLE_SRC_DIRECTIVE)) { + mStyleSrc = new nsCSPStyleSrcDirective(CSP_StringToCSPDirective(mCurToken)); + return mStyleSrc; + } if (CSP_IsDirective(mCurToken, nsIContentSecurityPolicy::REQUIRE_SRI_FOR)) { return new nsRequireSRIForDirective(CSP_StringToCSPDirective(mCurToken)); @@ -1181,6 +1194,7 @@ nsCSPParser::directive() // make sure to reset cache variables when trying to invalidate unsafe-inline; // unsafe-inline might not only appear in script-src, but also in default-src mHasHashOrNonce = false; + mHasAnyUnsafeEval = false; mStrictDynamic = false; mUnsafeInlineKeywordSrc = nullptr; @@ -1200,8 +1214,12 @@ nsCSPParser::directive() // If policy contains 'strict-dynamic' invalidate all srcs within script-src. if (mStrictDynamic) { - MOZ_ASSERT(cspDir->equals(nsIContentSecurityPolicy::SCRIPT_SRC_DIRECTIVE), - "strict-dynamic only allowed within script-src"); + MOZ_ASSERT( + cspDir->equals(nsIContentSecurityPolicy::SCRIPT_SRC_DIRECTIVE) || + cspDir->equals( + nsIContentSecurityPolicy::SCRIPT_SRC_ELEM_DIRECTIVE) || + cspDir->equals(nsIContentSecurityPolicy::SCRIPT_SRC_ATTR_DIRECTIVE), + "strict-dynamic only allowed within script-src(-elem|attr)"); for (uint32_t i = 0; i < srcs.Length(); i++) { // Please note that nsCSPNonceSrc as well as nsCSPHashSrc overwrite invalidate(), // so it's fine to just call invalidate() on all srcs. Please also note that @@ -1220,8 +1238,8 @@ nsCSPParser::directive() !StringBeginsWith(NS_ConvertUTF16toUTF8(srcStr), NS_LITERAL_CSTRING("'nonce-")) && !StringBeginsWith(NS_ConvertUTF16toUTF8(srcStr), NS_LITERAL_CSTRING("'sha"))) { - const char16_t* params[] = { srcStr.get() }; - logWarningErrorToConsole(nsIScriptError::warningFlag, "ignoringSrcForStrictDynamic", + const char16_t* params[] = { srcStr.get(), mCurDir[0].get() }; + logWarningErrorToConsole(nsIScriptError::warningFlag, "ignoringScriptSrcForStrictDynamic", params, ArrayLength(params)); } } @@ -1235,11 +1253,22 @@ nsCSPParser::directive() } else if (mHasHashOrNonce && mUnsafeInlineKeywordSrc && (cspDir->equals(nsIContentSecurityPolicy::SCRIPT_SRC_DIRECTIVE) || + cspDir->equals(nsIContentSecurityPolicy::SCRIPT_SRC_ELEM_DIRECTIVE) || + cspDir->equals(nsIContentSecurityPolicy::SCRIPT_SRC_ATTR_DIRECTIVE) || cspDir->equals(nsIContentSecurityPolicy::STYLE_SRC_DIRECTIVE))) { mUnsafeInlineKeywordSrc->invalidate(); - // log to the console that unsafe-inline will be ignored - const char16_t* params[] = { u"'unsafe-inline'" }; - logWarningErrorToConsole(nsIScriptError::warningFlag, "ignoringSrcWithinScriptStyleSrc", + // log to the console that unsafe-inline will be ignored. + const char16_t* params[] = { u"'unsafe-inline'", mCurDir[0].get() }; + logWarningErrorToConsole(nsIScriptError::warningFlag, "ignoringSrcWithinNonceOrHashDirective", + params, ArrayLength(params)); + } + + if (mHasAnyUnsafeEval && + (cspDir->equals(nsIContentSecurityPolicy::SCRIPT_SRC_ELEM_DIRECTIVE) || + cspDir->equals(nsIContentSecurityPolicy::SCRIPT_SRC_ATTR_DIRECTIVE))) { + // Log to the console that (wasm-)unsafe-eval will be ignored. + const char16_t* params[] = { mCurDir[0].get() }; + logWarningErrorToConsole(nsIScriptError::warningFlag, "ignoringUnsafeEval", params, ArrayLength(params)); } @@ -1265,13 +1294,13 @@ nsCSPParser::policy() if (mChildSrc) { if (!mFrameSrc) { - // if frame-src is specified explicitly for that policy than child-src should - // not restrict frames; if not, than child-src needs to restrict frames. + // if frame-src is specified explicitly for that policy, then child-src should + // not restrict frames; if not, then child-src needs to restrict frames. mChildSrc->setRestrictFrames(); } if (!mWorkerSrc) { - // if worker-src is specified explicitly for that policy than child-src should - // not restrict workers; if not, than child-src needs to restrict workers. + // if worker-src is specified explicitly for that policy, then child-src should + // not restrict workers; if not, then child-src needs to restrict workers. mChildSrc->setRestrictWorkers(); } } @@ -1281,6 +1310,30 @@ nsCSPParser::policy() mScriptSrc->setRestrictWorkers(); } + // If script-src is specified and script-src-elem is not specified, then + // script-src has to govern script requests and script blocks. + if (mScriptSrc && !mPolicy->hasDirective(nsIContentSecurityPolicy::SCRIPT_SRC_ELEM_DIRECTIVE)) { + mScriptSrc->setRestrictScriptElem(); + } + + // If script-src is specified and script-src-attr is not specified, then + // script-src has to govern script attr (event handlers). + if (mScriptSrc && !mPolicy->hasDirective(nsIContentSecurityPolicy::SCRIPT_SRC_ATTR_DIRECTIVE)) { + 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-src-attr is not specified, then + // style-src serves as a fallback. + if (mStyleSrc && !mPolicy->hasDirective(nsIContentSecurityPolicy::STYLE_SRC_ATTR_DIRECTIVE)) { + mStyleSrc->setRestrictStyleAttr(); + } + return mPolicy; } diff --git a/dom/security/nsCSPParser.h b/dom/security/nsCSPParser.h index 03ef2bb41e..56d1c39fcc 100644 --- a/dom/security/nsCSPParser.h +++ b/dom/security/nsCSPParser.h @@ -239,8 +239,9 @@ class nsCSPParser { // helpers to allow invalidation of srcs within script-src and style-src // if either 'strict-dynamic' or at least a hash or nonce is present. - bool mHasHashOrNonce; // false, if no hash or nonce is defined - bool mStrictDynamic; // false, if 'strict-dynamic' is not defined + bool mHasHashOrNonce; // false, if no hash or nonce is defined + bool mHasAnyUnsafeEval; // false, if no (wasm-)unsafe-eval keyword is used. + bool mStrictDynamic; // false, if 'strict-dynamic' is not defined nsCSPKeywordSrc* mUnsafeInlineKeywordSrc; // null, otherwise invlidate() // cache variables for child-src, frame-src and worker-src handling; @@ -254,6 +255,7 @@ class nsCSPParser { nsCSPDirective* mFrameSrc; nsCSPDirective* mWorkerSrc; nsCSPScriptSrcDirective* mScriptSrc; + nsCSPStyleSrcDirective* mStyleSrc; // cache variable to let nsCSPHostSrc know that it's within // the frame-ancestors directive. diff --git a/dom/security/nsCSPUtils.cpp b/dom/security/nsCSPUtils.cpp index cb04db315d..70418e70c3 100644 --- a/dom/security/nsCSPUtils.cpp +++ b/dom/security/nsCSPUtils.cpp @@ -199,6 +199,10 @@ CSP_LogLocalizedStr(const char16_t* aName, } /* ===== Helpers ============================ */ +// This implements +// https://w3c.github.io/webappsec-csp/#effective-directive-for-a-request. +// However the spec doesn't currently cover all request destinations, which +// we roughly represent using nsContentPolicyType. CSPDirective CSP_ContentTypeToDirective(nsContentPolicyType aType) { @@ -213,10 +217,14 @@ CSP_ContentTypeToDirective(nsContentPolicyType aType) case nsIContentPolicy::TYPE_INTERNAL_SCRIPT: case nsIContentPolicy::TYPE_INTERNAL_SCRIPT_PRELOAD: case nsIContentPolicy::TYPE_INTERNAL_WORKER_IMPORT_SCRIPTS: - return nsIContentSecurityPolicy::SCRIPT_SRC_DIRECTIVE; + // (https://github.com/w3c/webappsec-csp/issues/554) + // Some of these types are not explicitly defined in the spec. + // + // Chrome seems to use script-src-elem for worklet! + return nsIContentSecurityPolicy::SCRIPT_SRC_ELEM_DIRECTIVE; case nsIContentPolicy::TYPE_STYLESHEET: - return nsIContentSecurityPolicy::STYLE_SRC_DIRECTIVE; + return nsIContentSecurityPolicy::STYLE_SRC_ELEM_DIRECTIVE; case nsIContentPolicy::TYPE_FONT: return nsIContentSecurityPolicy::FONT_SRC_DIRECTIVE; @@ -1217,6 +1225,16 @@ nsCSPDirective::toDomCSPStruct(mozilla::dom::CSP& outCSP) const outCSP.mWorker_src.Value() = mozilla::Move(srcs); return; + case nsIContentSecurityPolicy::SCRIPT_SRC_ELEM_DIRECTIVE: + outCSP.mScript_src_elem.Construct(); + outCSP.mScript_src_elem.Value() = mozilla::Move(srcs); + return; + + case nsIContentSecurityPolicy::SCRIPT_SRC_ATTR_DIRECTIVE: + outCSP.mScript_src_attr.Construct(); + outCSP.mScript_src_attr.Value() = mozilla::Move(srcs); + return; + // REFERRER_DIRECTIVE and REQUIRE_SRI_FOR are handled in nsCSPPolicy::toDomCSPStruct() default: @@ -1225,16 +1243,6 @@ nsCSPDirective::toDomCSPStruct(mozilla::dom::CSP& outCSP) const } -bool -nsCSPDirective::restrictsContentType(nsContentPolicyType aContentType) const -{ - // make sure we do not check for the default src before any other sources - if (isDefaultDirective()) { - return false; - } - return mDirective == CSP_ContentTypeToDirective(aContentType); -} - void nsCSPDirective::getReportURIs(nsTArray &outReportURIs) const { @@ -1284,19 +1292,6 @@ nsCSPChildSrcDirective::~nsCSPChildSrcDirective() { } -bool nsCSPChildSrcDirective::restrictsContentType(nsContentPolicyType aContentType) const -{ - if (aContentType == nsIContentPolicy::TYPE_SUBDOCUMENT) { - return mRestrictFrames; - } - if (aContentType == nsIContentPolicy::TYPE_INTERNAL_WORKER || - aContentType == nsIContentPolicy::TYPE_INTERNAL_SHARED_WORKER || - aContentType == nsIContentPolicy::TYPE_INTERNAL_SERVICE_WORKER) { - return mRestrictWorkers; - } - return false; -} - bool nsCSPChildSrcDirective::equals(CSPDirective aDirective) const { if (aDirective == nsIContentSecurityPolicy::FRAME_SRC_DIRECTIVE) { @@ -1313,28 +1308,44 @@ bool nsCSPChildSrcDirective::equals(CSPDirective aDirective) const nsCSPScriptSrcDirective::nsCSPScriptSrcDirective(CSPDirective aDirective) : nsCSPDirective(aDirective) , mRestrictWorkers(false) + , mRestrictScriptElem(false) + , mRestrictScriptAttr(false) { } -nsCSPScriptSrcDirective::~nsCSPScriptSrcDirective() -{ -} +nsCSPScriptSrcDirective::~nsCSPScriptSrcDirective() = default; -bool nsCSPScriptSrcDirective::restrictsContentType(nsContentPolicyType aContentType) const -{ - if (aContentType == nsIContentPolicy::TYPE_INTERNAL_WORKER || - aContentType == nsIContentPolicy::TYPE_INTERNAL_SHARED_WORKER || - aContentType == nsIContentPolicy::TYPE_INTERNAL_SERVICE_WORKER) { - return mRestrictWorkers; - } - return mDirective == CSP_ContentTypeToDirective(aContentType); -} - -bool nsCSPScriptSrcDirective::equals(CSPDirective aDirective) const -{ +bool nsCSPScriptSrcDirective::equals(CSPDirective aDirective) const { if (aDirective == nsIContentSecurityPolicy::WORKER_SRC_DIRECTIVE) { return mRestrictWorkers; } + if (aDirective == nsIContentSecurityPolicy::SCRIPT_SRC_ELEM_DIRECTIVE) { + return mRestrictScriptElem; + } + if (aDirective == nsIContentSecurityPolicy::SCRIPT_SRC_ATTR_DIRECTIVE) { + return mRestrictScriptAttr; + } + return (mDirective == aDirective); +} + +/* =============== nsCSPStyleSrcDirective ============= */ + +nsCSPStyleSrcDirective::nsCSPStyleSrcDirective(CSPDirective aDirective) + : nsCSPDirective(aDirective) + , mRestrictStyleElem(false) + , mRestrictStyleAttr(false) +{ +} + +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); } @@ -1464,15 +1475,6 @@ nsCSPPolicy::~nsCSPPolicy() } } -bool -nsCSPPolicy::permits(CSPDirective aDir, - nsIURI* aUri, - bool aSpecific) const -{ - nsString outp; - return this->permits(aDir, aUri, EmptyString(), false, aSpecific, false, outp); -} - bool nsCSPPolicy::permits(CSPDirective aDir, nsIURI* aUri, @@ -1526,7 +1528,7 @@ nsCSPPolicy::permits(CSPDirective aDir, } bool -nsCSPPolicy::allows(nsContentPolicyType aContentType, +nsCSPPolicy::allows(CSPDirective aDirective, enum CSPKeyword aKeyword, const nsAString& aHashOrNonce, bool aParserCreated) const @@ -1538,15 +1540,16 @@ nsCSPPolicy::allows(nsContentPolicyType aContentType, // Try to find a matching directive for (uint32_t i = 0; i < mDirectives.Length(); i++) { - if (mDirectives[i]->restrictsContentType(aContentType)) { + if (mDirectives[i]->isDefaultDirective()) { + defaultDir = mDirectives[i]; + continue; + } + if (mDirectives[i]->equals(aDirective)) { if (mDirectives[i]->allows(aKeyword, aHashOrNonce, aParserCreated)) { return true; } return false; } - if (mDirectives[i]->isDefaultDirective()) { - defaultDir = mDirectives[i]; - } } // {nonce,hash}-source should not consult default-src: @@ -1573,13 +1576,6 @@ nsCSPPolicy::allows(nsContentPolicyType aContentType, return true; } -bool -nsCSPPolicy::allows(nsContentPolicyType aContentType, - enum CSPKeyword aKeyword) const -{ - return allows(aContentType, aKeyword, NS_LITERAL_STRING(""), false); -} - void nsCSPPolicy::toString(nsAString& outStr) const { @@ -1634,17 +1630,18 @@ nsCSPPolicy::hasDirective(CSPDirective aDir) const * for the ::permits() function family. */ void -nsCSPPolicy::getDirectiveStringForContentType(nsContentPolicyType aContentType, +nsCSPPolicy::getDirectiveStringForContentType(CSPDirective aDirective, nsAString& outDirective) const { nsCSPDirective* defaultDir = nullptr; for (uint32_t i = 0; i < mDirectives.Length(); i++) { - if (mDirectives[i]->restrictsContentType(aContentType)) { - mDirectives[i]->getDirName(outDirective); - return; - } if (mDirectives[i]->isDefaultDirective()) { defaultDir = mDirectives[i]; + continue; + } + if (mDirectives[i]->equals(aDirective)) { + mDirectives[i]->getDirName(outDirective); + return; } } // if we haven't found a matching directive yet, diff --git a/dom/security/nsCSPUtils.h b/dom/security/nsCSPUtils.h index 9b9ff46c90..4c0af92620 100644 --- a/dom/security/nsCSPUtils.h +++ b/dom/security/nsCSPUtils.h @@ -141,7 +141,11 @@ static const char* CSPStrDirectives[] = { "block-all-mixed-content", // BLOCK_ALL_MIXED_CONTENT "require-sri-for", // REQUIRE_SRI_FOR "sandbox", // SANDBOX_DIRECTIVE - "worker-src" // WORKER_SRC_DIRECTIVE + "worker-src", // WORKER_SRC_DIRECTIVE + "script-src-elem", // SCRIPT_SRC_ELEM_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) @@ -483,8 +487,6 @@ class nsCSPDirective { virtual void addSrcs(const nsTArray& aSrcs) { mSrcs = aSrcs; } - virtual bool restrictsContentType(nsContentPolicyType aContentType) const; - inline bool isDefaultDirective() const { return mDirective == nsIContentSecurityPolicy::DEFAULT_SRC_DIRECTIVE; } @@ -520,8 +522,6 @@ class nsCSPChildSrcDirective : public nsCSPDirective { void setRestrictWorkers() { mRestrictWorkers = true; } - virtual bool restrictsContentType(nsContentPolicyType aContentType) const; - virtual bool equals(CSPDirective aDirective) const; private: @@ -541,15 +541,37 @@ class nsCSPScriptSrcDirective : public nsCSPDirective { explicit nsCSPScriptSrcDirective(CSPDirective aDirective); virtual ~nsCSPScriptSrcDirective(); - void setRestrictWorkers() - { mRestrictWorkers = true; } - - virtual bool restrictsContentType(nsContentPolicyType aContentType) const; + void setRestrictWorkers() { mRestrictWorkers = true; } + void setRestrictScriptElem() { mRestrictScriptElem = true; } + void setRestrictScriptAttr() { mRestrictScriptAttr = true; } virtual bool equals(CSPDirective aDirective) const; private: bool mRestrictWorkers; + bool mRestrictScriptElem; + bool mRestrictScriptAttr; +}; + +/* =============== nsCSPStyleSrcDirective ============= */ + +/* + * In CSP 3, style-src is used as a fallback for style-src-elem and + * style-src-attr in case they aren't defined. + */ +class nsCSPStyleSrcDirective : public nsCSPDirective { + public: + explicit nsCSPStyleSrcDirective(CSPDirective aDirective); + virtual ~nsCSPStyleSrcDirective(); + + void setRestrictStyleElem() { mRestrictStyleElem = true; } + void setRestrictStyleAttr() { mRestrictStyleAttr = true; } + + virtual bool equals(CSPDirective aDirective) const; + + private: + bool mRestrictStyleElem; + bool mRestrictStyleAttr; }; /* =============== nsBlockAllMixedContentDirective === */ @@ -668,15 +690,10 @@ class nsCSPPolicy { bool aSpecific, bool aParserCreated, nsAString& outViolatedDirective) const; - bool permits(CSPDirective aDir, - nsIURI* aUri, - bool aSpecific) const; - bool allows(nsContentPolicyType aContentType, + bool allows(CSPDirective aDirective, enum CSPKeyword aKeyword, const nsAString& aHashOrNonce, bool aParserCreated) const; - bool allows(nsContentPolicyType aContentType, - enum CSPKeyword aKeyword) const; void toString(nsAString& outStr) const; void toDomCSPStruct(mozilla::dom::CSP& outCSP) const; @@ -708,7 +725,7 @@ class nsCSPPolicy { void getReportURIs(nsTArray &outReportURIs) const; - void getDirectiveStringForContentType(nsContentPolicyType aContentType, + void getDirectiveStringForContentType(CSPDirective aDirective, nsAString& outDirective) const; void getDirectiveAsString(CSPDirective aDir, nsAString& outDirective) const; diff --git a/dom/security/test/csp/test_report_for_import.html b/dom/security/test/csp/test_report_for_import.html index be112d51c0..c8ced5a244 100644 --- a/dom/security/test/csp/test_report_for_import.html +++ b/dom/security/test/csp/test_report_for_import.html @@ -50,7 +50,7 @@ function checkResults(reportStr) { "http://mochi.test:8888/tests/dom/security/test/csp/test_report_for_import.html", "Incorrect referrer"); is(cspReport["violated-directive"], - "style-src http://mochi.test:8888", + "style-src-elem http://mochi.test:8888", "Incorrect violated-directive"); is(cspReport["original-policy"], "style-src http://mochi.test:8888; report-uri " + diff --git a/dom/security/test/unit/test_csp_reports.js b/dom/security/test/unit/test_csp_reports.js index 6c88fb1e10..54dc470b4a 100644 --- a/dom/security/test/unit/test_csp_reports.js +++ b/dom/security/test/unit/test_csp_reports.js @@ -215,7 +215,7 @@ function run_test() { function(csp) { var uri = NetUtil // shouldLoad creates and sends out the report here. - csp.shouldLoad(Ci.nsIContentPolicy.TYPE_SCRIPT, + csp.shouldLoad(Ci.nsIContentSecurityPolicy.SCRIPT_SRC_ELEM_DIRECTIVE, NetUtil.newURI(selfSpec + "#bar"), null, null, null, null); }); @@ -224,7 +224,7 @@ function run_test() { makeTest(8, {"blocked-uri": "ftp://blocked.test"}, false, function(csp) { // shouldLoad creates and sends out the report here. - csp.shouldLoad(Ci.nsIContentPolicy.TYPE_SCRIPT, + csp.shouldLoad(Ci.nsIContentSecurityPolicy.SCRIPT_SRC_ELEM_DIRECTIVE, NetUtil.newURI("ftp://blocked.test/profile.png"), null, null, null, null); }); diff --git a/dom/webidl/CSPDictionaries.webidl b/dom/webidl/CSPDictionaries.webidl index f8de1c9ad8..0c024fe61f 100644 --- a/dom/webidl/CSPDictionaries.webidl +++ b/dom/webidl/CSPDictionaries.webidl @@ -20,7 +20,7 @@ dictionary CSP { sequence connect-src; sequence report-uri; sequence frame-ancestors; - // sequence reflected-xss; // not supported in Firefox + // sequence reflected-xss; // not supported in UXP sequence base-uri; sequence form-action; sequence referrer; @@ -31,6 +31,8 @@ dictionary CSP { sequence require-sri-for; sequence sandbox; sequence worker-src; + sequence script-src-elem; + sequence script-src-attr; }; dictionary CSPPolicies { diff --git a/gfx/thebes/gfxFont.cpp b/gfx/thebes/gfxFont.cpp index 3857c4cd38..383cc19042 100644 --- a/gfx/thebes/gfxFont.cpp +++ b/gfx/thebes/gfxFont.cpp @@ -591,8 +591,16 @@ gfxFontShaper::GetRoundOffsetsToPixels(DrawTarget* aDrawTarget, } // Sometimes hint metrics gets set for us, most notably for printing. +#ifdef MOZ_TREE_CAIRO cairo_hint_metrics_t hint_metrics = cairo_scaled_font_get_hint_metrics(scaled_font); +#else + cairo_font_options_t* font_options = cairo_font_options_create(); + cairo_scaled_font_get_font_options(scaled_font, font_options); + cairo_hint_metrics_t hint_metrics = + cairo_font_options_get_hint_metrics(font_options); + cairo_font_options_destroy(font_options); +#endif switch (hint_metrics) { case CAIRO_HINT_METRICS_OFF: diff --git a/layout/style/nsStyleUtil.cpp b/layout/style/nsStyleUtil.cpp index 274f5140f2..1f5de4fb83 100644 --- a/layout/style/nsStyleUtil.cpp +++ b/layout/style/nsStyleUtil.cpp @@ -767,14 +767,16 @@ nsStyleUtil::CSPAllowsInlineStyle(nsIContent* aContent, return true; } + CSPDirective directive = nsIContentSecurityPolicy::STYLE_SRC_ATTR_DIRECTIVE; // query the nonce nsAutoString nonce; if (aContent) { + directive = nsIContentSecurityPolicy::STYLE_SRC_ELEM_DIRECTIVE; aContent->GetAttr(kNameSpaceID_None, nsGkAtoms::nonce, nonce); } bool allowInlineStyle = true; - rv = csp->GetAllowsInline(nsIContentPolicy::TYPE_STYLESHEET, + rv = csp->GetAllowsInline(directive, nonce, false, // aParserCreated only applies to scripts aStyleText, aLineNumber, aColumnNumber,