mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-24 05:11:16 +00:00
Bug 1797070 - CSP: Add a basic implementation of unsafe-hashes behind a flag. r=freddyb
Differential Revision: https://phabricator.services.mozilla.com/D160046
This commit is contained in:
parent
a24dca9645
commit
b680625ab0
@ -1044,6 +1044,7 @@ nsresult EventListenerManager::SetEventHandler(nsAtom* aName,
|
||||
bool allowsInlineScript = true;
|
||||
rv = csp->GetAllowsInline(
|
||||
nsIContentSecurityPolicy::SCRIPT_SRC_ATTR_DIRECTIVE,
|
||||
true, // aHasUnsafeHash
|
||||
u""_ns, // aNonce
|
||||
true, // aParserCreated (true because attribute event handler)
|
||||
aElement,
|
||||
|
@ -125,7 +125,10 @@ interface nsIContentSecurityPolicy : nsISerializable
|
||||
|
||||
/*
|
||||
* Whether this policy allows inline script or style.
|
||||
* @param aContentPolicyType Either TYPE_SCRIPT or TYPE_STYLESHEET
|
||||
* @param aContentPolicyType Either SCRIPT_SRC_(ELEM|ATTR)_DIRECTIVE or
|
||||
* STYLE_SRC_(ELEM|ATTR)_DIRECTIVE.
|
||||
* @param aHasUnsafeHash Only hash this when the 'unsafe-hashes' directive is
|
||||
* also specified.
|
||||
* @param aNonce The nonce string to check against the policy
|
||||
* @param aParserCreated If the script element was created by the HTML Parser
|
||||
* @param aTriggeringElement The script element of the inline resource to
|
||||
@ -142,6 +145,7 @@ interface nsIContentSecurityPolicy : nsISerializable
|
||||
* (block the rules if false).
|
||||
*/
|
||||
boolean getAllowsInline(in nsIContentSecurityPolicy_CSPDirective aDirective,
|
||||
in bool aHasUnsafeHash,
|
||||
in AString aNonce,
|
||||
in boolean aParserCreated,
|
||||
in Element aTriggeringElement,
|
||||
|
@ -132,23 +132,36 @@ static nsIScriptGlobalObject* GetGlobalObject(nsIChannel* aChannel) {
|
||||
}
|
||||
|
||||
static bool AllowedByCSP(nsIContentSecurityPolicy* aCSP,
|
||||
const nsAString& aContentOfPseudoScript) {
|
||||
const nsACString& aJavaScriptURL) {
|
||||
if (!aCSP) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// javascript: is a "navigation" type, so script-src-elem applies.
|
||||
// https://w3c.github.io/webappsec-csp/#should-block-navigation-request
|
||||
// Step 3. If result is "Allowed", and if navigation request’s current URL’s
|
||||
// scheme is javascript:
|
||||
//
|
||||
// Step 3.1.1.2 If directive’s inline check returns "Allowed" when executed
|
||||
// upon null, "navigation" and navigation request’s current URL, skip to the
|
||||
// next directive.
|
||||
//
|
||||
// This means /type/ is "navigation" and /source string/ is the
|
||||
// "navigation request’s current URL".
|
||||
//
|
||||
// Per
|
||||
// https://w3c.github.io/webappsec-csp/#effective-directive-for-inline-check
|
||||
// type "navigation" maps to the effective directive script-src-elem.
|
||||
bool allowsInlineScript = true;
|
||||
nsresult rv =
|
||||
aCSP->GetAllowsInline(nsIContentSecurityPolicy::SCRIPT_SRC_ELEM_DIRECTIVE,
|
||||
u""_ns, // aNonce
|
||||
true, // aParserCreated
|
||||
nullptr, // aElement,
|
||||
nullptr, // nsICSPEventListener
|
||||
aContentOfPseudoScript, // aContent
|
||||
0, // aLineNumber
|
||||
0, // aColumnNumber
|
||||
true, // aHasUnsafeHash
|
||||
u""_ns, // aNonce
|
||||
true, // aParserCreated
|
||||
nullptr, // aElement,
|
||||
nullptr, // nsICSPEventListener
|
||||
NS_ConvertASCIItoUTF16(aJavaScriptURL), // aContent
|
||||
0, // aLineNumber
|
||||
0, // aColumnNumber
|
||||
&allowsInlineScript);
|
||||
|
||||
return (NS_SUCCEEDED(rv) && allowsInlineScript);
|
||||
@ -210,11 +223,7 @@ nsresult nsJSThunk::EvaluateScript(
|
||||
// once we have determined the target document.
|
||||
nsCOMPtr<nsIContentSecurityPolicy> csp = loadInfo->GetCspToInherit();
|
||||
|
||||
nsAutoCString script(mScript);
|
||||
// Unescape the script
|
||||
NS_UnescapeURL(script);
|
||||
|
||||
if (!AllowedByCSP(csp, NS_ConvertASCIItoUTF16(script))) {
|
||||
if (!AllowedByCSP(csp, mURL)) {
|
||||
return NS_ERROR_DOM_RETVAL_UNDEFINED;
|
||||
}
|
||||
|
||||
@ -264,7 +273,7 @@ nsresult nsJSThunk::EvaluateScript(
|
||||
// against if the triggering principal is system.
|
||||
if (targetDoc->NodePrincipal()->Subsumes(loadInfo->TriggeringPrincipal())) {
|
||||
nsCOMPtr<nsIContentSecurityPolicy> targetCSP = targetDoc->GetCsp();
|
||||
if (!AllowedByCSP(targetCSP, NS_ConvertASCIItoUTF16(script))) {
|
||||
if (!AllowedByCSP(targetCSP, mURL)) {
|
||||
return NS_ERROR_DOM_RETVAL_UNDEFINED;
|
||||
}
|
||||
}
|
||||
@ -305,6 +314,10 @@ nsresult nsJSThunk::EvaluateScript(
|
||||
return NS_ERROR_DOM_SECURITY_ERR;
|
||||
}
|
||||
|
||||
nsAutoCString script(mScript);
|
||||
// Unescape the script
|
||||
NS_UnescapeURL(script);
|
||||
|
||||
JS::Rooted<JS::Value> v(cx, JS::UndefinedValue());
|
||||
// Finally, we have everything needed to evaluate the expression.
|
||||
JS::CompileOptions options(cx);
|
||||
|
@ -801,8 +801,9 @@ static bool CSPAllowsInlineScript(nsIScriptElement* aElement,
|
||||
|
||||
bool allowInlineScript = false;
|
||||
nsresult rv = csp->GetAllowsInline(
|
||||
nsIContentSecurityPolicy::SCRIPT_SRC_ELEM_DIRECTIVE, nonce, parserCreated,
|
||||
scriptContent, nullptr /* nsICSPEventListener */, u""_ns,
|
||||
nsIContentSecurityPolicy::SCRIPT_SRC_ELEM_DIRECTIVE,
|
||||
false /* aHasUnsafeHash */, nonce, parserCreated, scriptContent,
|
||||
nullptr /* nsICSPEventListener */, u""_ns,
|
||||
aElement->GetScriptLineNumber(), aElement->GetScriptColumnNumber(),
|
||||
&allowInlineScript);
|
||||
return NS_SUCCEEDED(rv) && allowInlineScript;
|
||||
|
@ -572,8 +572,9 @@ void nsCSPContext::reportInlineViolation(
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsCSPContext::GetAllowsInline(CSPDirective aDirective, const nsAString& aNonce,
|
||||
bool aParserCreated, Element* aTriggeringElement,
|
||||
nsCSPContext::GetAllowsInline(CSPDirective aDirective, bool aHasUnsafeHash,
|
||||
const nsAString& aNonce, bool aParserCreated,
|
||||
Element* aTriggeringElement,
|
||||
nsICSPEventListener* aCSPEventListener,
|
||||
const nsAString& aContentOfPseudoScript,
|
||||
uint32_t aLineNumber, uint32_t aColumnNumber,
|
||||
@ -616,12 +617,20 @@ nsCSPContext::GetAllowsInline(CSPDirective aDirective, const nsAString& aNonce,
|
||||
element->GetScriptText(content);
|
||||
}
|
||||
}
|
||||
|
||||
if (content.IsEmpty()) {
|
||||
content = aContentOfPseudoScript;
|
||||
}
|
||||
allowed =
|
||||
mPolicies[i]->allows(aDirective, CSP_HASH, content, aParserCreated);
|
||||
|
||||
// Per https://w3c.github.io/webappsec-csp/#match-element-to-source-list
|
||||
// Step 5. If type is "script" or "style", or unsafe-hashes flag is true:
|
||||
//
|
||||
// aHasUnsafeHash is true for event handlers (type "script attribute"),
|
||||
// style= attributes (type "style attribute") and the javascript: protocol.
|
||||
if (!aHasUnsafeHash || mPolicies[i]->allows(aDirective, CSP_UNSAFE_HASHES,
|
||||
u""_ns, aParserCreated)) {
|
||||
allowed =
|
||||
mPolicies[i]->allows(aDirective, CSP_HASH, content, aParserCreated);
|
||||
}
|
||||
|
||||
if (!allowed) {
|
||||
// policy is violoated: deny the load unless policy is report only and
|
||||
|
@ -454,6 +454,11 @@ nsCSPBaseSrc* nsCSPParser::keywordSource() {
|
||||
return new nsCSPKeywordSrc(CSP_UTF16KeywordToEnum(mCurToken));
|
||||
}
|
||||
|
||||
if (StaticPrefs::security_csp_unsafe_hashes_enabled() &&
|
||||
CSP_IsKeyword(mCurToken, CSP_UNSAFE_HASHES)) {
|
||||
return new nsCSPKeywordSrc(CSP_UTF16KeywordToEnum(mCurToken));
|
||||
}
|
||||
|
||||
if (CSP_IsKeyword(mCurToken, CSP_UNSAFE_ALLOW_REDIRECTS)) {
|
||||
if (!CSP_IsDirective(mCurDir[0],
|
||||
nsIContentSecurityPolicy::NAVIGATE_TO_DIRECTIVE)) {
|
||||
@ -1070,10 +1075,13 @@ void nsCSPParser::directive() {
|
||||
nsAutoString srcStr;
|
||||
srcs[i]->toString(srcStr);
|
||||
// Even though we invalidate all of the srcs internally, we don't want to
|
||||
// log messages for the srcs: (1) strict-dynamic, (2) unsafe-inline, (3)
|
||||
// nonces, and (4) hashes
|
||||
// log messages for the srcs: 'strict-dynamic', 'unsafe-inline',
|
||||
// 'unsafe-hashes', nonces, and hashes, because those still apply even
|
||||
// with 'strict-dynamic'.
|
||||
// TODO the comment seems wrong 'unsafe-eval' vs 'unsafe-inline'.
|
||||
if (!srcStr.EqualsASCII(CSP_EnumToUTF8Keyword(CSP_STRICT_DYNAMIC)) &&
|
||||
!srcStr.EqualsASCII(CSP_EnumToUTF8Keyword(CSP_UNSAFE_EVAL)) &&
|
||||
!srcStr.EqualsASCII(CSP_EnumToUTF8Keyword(CSP_UNSAFE_HASHES)) &&
|
||||
!StringBeginsWith(
|
||||
srcStr, nsDependentString(CSP_EnumToUTF16Keyword(CSP_NONCE))) &&
|
||||
!StringBeginsWith(srcStr, u"'sha"_ns)) {
|
||||
|
@ -875,7 +875,7 @@ bool nsCSPKeywordSrc::allows(enum CSPKeyword aKeyword,
|
||||
"%s",
|
||||
CSP_EnumToUTF8Keyword(aKeyword),
|
||||
NS_ConvertUTF16toUTF8(aHashOrNonce).get(),
|
||||
mInvalidated ? "yes" : "false"));
|
||||
mInvalidated ? "true" : "false"));
|
||||
|
||||
if (mInvalidated) {
|
||||
// only 'self', 'report-sample' and 'unsafe-inline' are keywords that can be
|
||||
|
@ -116,6 +116,7 @@ inline CSPDirective CSP_StringToCSPDirective(const nsAString& aDir) {
|
||||
MACRO(CSP_SELF, "'self'") \
|
||||
MACRO(CSP_UNSAFE_INLINE, "'unsafe-inline'") \
|
||||
MACRO(CSP_UNSAFE_EVAL, "'unsafe-eval'") \
|
||||
MACRO(CSP_UNSAFE_HASHES, "'unsafe-hashes'") \
|
||||
MACRO(CSP_NONE, "'none'") \
|
||||
MACRO(CSP_NONCE, "'nonce-") \
|
||||
MACRO(CSP_REPORT_SAMPLE, "'report-sample'") \
|
||||
|
@ -129,6 +129,7 @@ function run_test() {
|
||||
let inlineOK = true;
|
||||
inlineOK = csp.getAllowsInline(
|
||||
Ci.nsIContentSecurityPolicy.SCRIPT_SRC_ELEM_DIRECTIVE,
|
||||
false, // aHasUnsafeHash
|
||||
"", // aNonce
|
||||
false, // aParserCreated
|
||||
null, // aTriggeringElement
|
||||
@ -206,6 +207,7 @@ function run_test() {
|
||||
let inlineOK = true;
|
||||
inlineOK = csp.getAllowsInline(
|
||||
Ci.nsIContentSecurityPolicy.SCRIPT_SRC_ELEM_DIRECTIVE,
|
||||
false, // aHasUnsafeHash
|
||||
"", // aNonce
|
||||
false, // aParserCreated
|
||||
null, // aTriggeringElement
|
||||
|
@ -306,12 +306,11 @@ bool nsStyleUtil::CSPAllowsInlineStyle(
|
||||
return true;
|
||||
}
|
||||
|
||||
nsIContentSecurityPolicy::CSPDirective directive =
|
||||
nsIContentSecurityPolicy::STYLE_SRC_ATTR_DIRECTIVE;
|
||||
bool isStyleElement = false;
|
||||
// query the nonce
|
||||
nsAutoString nonce;
|
||||
if (aElement && aElement->NodeInfo()->NameAtom() == nsGkAtoms::style) {
|
||||
directive = nsIContentSecurityPolicy::STYLE_SRC_ELEM_DIRECTIVE;
|
||||
isStyleElement = true;
|
||||
nsString* cspNonce =
|
||||
static_cast<nsString*>(aElement->GetProperty(nsGkAtoms::nonce));
|
||||
if (cspNonce) {
|
||||
@ -320,11 +319,13 @@ bool nsStyleUtil::CSPAllowsInlineStyle(
|
||||
}
|
||||
|
||||
bool allowInlineStyle = true;
|
||||
rv = csp->GetAllowsInline(directive, nonce,
|
||||
false, // aParserCreated only applies to scripts
|
||||
aElement, nullptr, // nsICSPEventListener
|
||||
aStyleText, aLineNumber, aColumnNumber,
|
||||
&allowInlineStyle);
|
||||
rv = csp->GetAllowsInline(
|
||||
isStyleElement ? nsIContentSecurityPolicy::STYLE_SRC_ELEM_DIRECTIVE
|
||||
: nsIContentSecurityPolicy::STYLE_SRC_ATTR_DIRECTIVE,
|
||||
!isStyleElement /* aHasUnsafeHash */, nonce,
|
||||
false, // aParserCreated only applies to scripts
|
||||
aElement, nullptr, // nsICSPEventListener
|
||||
aStyleText, aLineNumber, aColumnNumber, &allowInlineStyle);
|
||||
NS_ENSURE_SUCCESS(rv, false);
|
||||
|
||||
return allowInlineStyle;
|
||||
|
@ -12859,6 +12859,12 @@
|
||||
value: true
|
||||
mirror: always
|
||||
|
||||
# unsafe-hashes source keyword
|
||||
- name: security.csp.unsafe-hashes.enabled
|
||||
type: bool
|
||||
value: false
|
||||
mirror: always
|
||||
|
||||
# The script-src-attr and script-src-elem directive
|
||||
- name: security.csp.script-src-attr-elem.enabled
|
||||
type: bool
|
||||
|
@ -1 +1 @@
|
||||
prefs: [dom.targetBlankNoOpener.enabled:false]
|
||||
prefs: [dom.targetBlankNoOpener.enabled:false, security.csp.unsafe-hashes.enabled:true]
|
||||
|
@ -1,3 +0,0 @@
|
||||
[javascript_src_allowed-href.html]
|
||||
[javascript: navigation using <a href> should be allowed]
|
||||
expected: FAIL
|
@ -1,5 +0,0 @@
|
||||
[javascript_src_allowed-href_blank-script-src-elem.html]
|
||||
expected:
|
||||
if (os == "android") and fission: [OK, TIMEOUT]
|
||||
[javascript: navigation using <a href target=_blank> should be allowed]
|
||||
expected: FAIL
|
@ -1,5 +0,0 @@
|
||||
[javascript_src_allowed-href_blank.html]
|
||||
expected:
|
||||
if (os == "android") and fission: [OK, TIMEOUT]
|
||||
[javascript: navigation using <a href target=_blank> should be allowed]
|
||||
expected: FAIL
|
@ -1,5 +0,0 @@
|
||||
[javascript_src_allowed-window_location.html]
|
||||
expected:
|
||||
if (os == "android") and fission: [OK, TIMEOUT]
|
||||
[Test that the javascript: src is allowed to run]
|
||||
expected: FAIL
|
@ -1,5 +0,0 @@
|
||||
[javascript_src_allowed-window_open.html]
|
||||
expected:
|
||||
if (os == "android") and fission: [OK, TIMEOUT]
|
||||
[Test that the javascript: src is allowed to run]
|
||||
expected: FAIL
|
@ -1,5 +0,0 @@
|
||||
[script_event_handlers_denied_missing_unsafe_hashes.html]
|
||||
expected: TIMEOUT
|
||||
[Test that the inline event handler is not allowed to run]
|
||||
expected: NOTRUN
|
||||
|
@ -1,6 +0,0 @@
|
||||
implementation-status: backlog
|
||||
[style_attribute_denied_missing_unsafe_hashes.html]
|
||||
expected: TIMEOUT
|
||||
[Test that the inline style attribute is blocked]
|
||||
expected: NOTRUN
|
||||
|
@ -1,3 +1,4 @@
|
||||
[string-compilation-nonce-classic.html]
|
||||
prefs: [security.csp.unsafe-hashes.enabled:true]
|
||||
expected:
|
||||
if (os == "android") and fission: [OK, TIMEOUT]
|
||||
|
@ -1,3 +1,4 @@
|
||||
[string-compilation-nonce-module.html]
|
||||
prefs: [security.csp.unsafe-hashes.enabled:true]
|
||||
expected:
|
||||
if (os == "android") and fission: [OK, TIMEOUT]
|
||||
|
Loading…
Reference in New Issue
Block a user