Bug 1931288 - Implement EnsureCSPDoesNotBlockStringCompilation as per CSP spec. r=smaug,tschuster

See https://w3c.github.io/webappsec-csp/#can-compile-strings

Differential Revision: https://phabricator.services.mozilla.com/D229624
This commit is contained in:
Frédéric Wang
2024-12-13 08:54:53 +00:00
parent 0f0df595a9
commit 8a29748fb2
14 changed files with 231 additions and 293 deletions

View File

@@ -475,6 +475,21 @@ bool nsScriptSecurityManager::ContentSecurityPolicyPermitsJSAction(
nsCOMPtr<nsIPrincipal> subjectPrincipal = nsContentUtils::SubjectPrincipal();
if (aKind == JS::RuntimeCode::JS) {
ErrorResult error;
bool areArgumentsTrusted = TrustedTypeUtils::
AreArgumentsTrustedForEnsureCSPDoesNotBlockStringCompilation(
cx, aCodeString, aCompilationType, aParameterStrings, aBodyString,
aParameterArgs, aBodyArg, error);
if (error.MaybeSetPendingException(cx)) {
return false;
}
if (!areArgumentsTrusted) {
*aOutCanCompileStrings = false;
return true;
}
}
// Check if Eval is allowed per firefox hardening policy
bool contextForbidsEval =
(subjectPrincipal->IsSystemPrincipal() || XRE_IsE10sParentProcess());

View File

@@ -88,7 +88,7 @@ class nsScriptSecurityManager final : public nsIScriptSecurityManager {
virtual ~nsScriptSecurityManager();
// Decides, based on CSP, whether or not eval() and stuff can be executed.
static bool ContentSecurityPolicyPermitsJSAction(
MOZ_CAN_RUN_SCRIPT static bool ContentSecurityPolicyPermitsJSAction(
JSContext* aCx, JS::RuntimeCode aKind, JS::Handle<JSString*> aCodeString,
JS::CompilationType aCompilationType,
JS::Handle<JS::StackGCVector<JSString*>> aParameterStrings,

View File

@@ -492,6 +492,15 @@ GetTrustedTypesCompliantStringForTrustedHTML(const nsAString& aInput,
&aInput, aSink, aSinkGroup, aNode, aResultHolder, aError);
}
MOZ_CAN_RUN_SCRIPT const nsAString*
GetTrustedTypesCompliantStringForTrustedScript(
const nsAString& aInput, const nsAString& aSink,
const nsAString& aSinkGroup, nsIGlobalObject& aGlobalObject,
Maybe<nsAutoString>& aResultHolder, ErrorResult& aError) {
return GetTrustedTypesCompliantString<TrustedScript>(
&aInput, aSink, aSinkGroup, aGlobalObject, aResultHolder, aError);
}
bool GetTrustedTypeDataForAttribute(const nsAtom* aElementName,
int32_t aElementNamespaceID,
nsAtom* aAttributeName,
@@ -613,4 +622,138 @@ bool HostGetCodeForEval(JSContext* aCx, JS::Handle<JSObject*> aCode,
return true;
}
bool AreArgumentsTrustedForEnsureCSPDoesNotBlockStringCompilation(
JSContext* aCx, JS::Handle<JSString*> aCodeString,
JS::CompilationType aCompilationType,
JS::Handle<JS::StackGCVector<JSString*>> aParameterStrings,
JS::Handle<JSString*> aBodyString,
JS::Handle<JS::StackGCVector<JS::Value>> aParameterArgs,
JS::Handle<JS::Value> aBodyArg, ErrorResult& aError) {
// EnsureCSPDoesNotBlockStringCompilation is essentially HTML's implementation
// of HostEnsureCanCompileStrings, so we only consider the cases described in
// the Dynamic Code Brand Checks spec. The algorithm is also supposed to be
// called for "TIMER" too but in that case it does not execute the specific
// part implemented in the present method (step 2).
// https://html.spec.whatwg.org/multipage/webappapis.html#hostensurecancompilestrings(realm,-parameterstrings,-bodystring,-codestring,-compilationtype,-parameterargs,-bodyarg)
// https://tc39.es/proposal-dynamic-code-brand-checks/#sec-hostensurecancompilestrings
// https://html.spec.whatwg.org/#timer-initialisation-steps
if (!StaticPrefs::dom_security_trusted_types_enabled() ||
aCompilationType == JS::CompilationType::Undefined) {
return true;
}
// https://html.spec.whatwg.org/multipage/webappapis.html#hostensurecancompilestrings(realm,-parameterstrings,-bodystring,-codestring,-compilationtype,-parameterargs,-bodyarg)
// https://w3c.github.io/webappsec-csp/#can-compile-strings
nsIGlobalObject* global = xpc::CurrentNativeGlobal(aCx);
if (!global) {
aError.Throw(NS_ERROR_NULL_POINTER);
return false;
}
// Exit early for some cases where GetTrustedTypesCompliantString
// would have no effect on aCodeString.
if (nsPIDOMWindowInner* piDOMWindowInner = global->GetAsInnerWindow()) {
const Document* extantDoc = piDOMWindowInner->GetExtantDoc();
if (extantDoc &&
!extantDoc->HasPolicyWithRequireTrustedTypesForDirective()) {
return true;
}
}
// Steps 2.2 - 2.4.
bool isTrusted = true;
auto isArgumentTrusted = [&aCx](JS::Handle<JS::Value> aValue,
JS::Handle<JSString*> aString,
ErrorResult& aError) {
if (!aValue.isObject()) {
return false;
}
JS::Rooted<JSObject*> object(aCx, &aValue.toObject());
TrustedScript* trustedScript;
if (NS_FAILED(UNWRAP_OBJECT(TrustedScript, &object, trustedScript))) {
return false;
}
nsAutoJSString jsString;
if (NS_WARN_IF(!jsString.init(aCx, aString))) {
aError.StealExceptionFromJSContext(aCx);
return false;
}
return jsString.Equals(trustedScript->mData);
};
if (aCompilationType == JS::CompilationType::DirectEval ||
aCompilationType == JS::CompilationType::IndirectEval) {
// The following assertions are guanranteed by the steps of PerformEval.
MOZ_ASSERT(aParameterArgs.empty());
MOZ_ASSERT(aParameterStrings.empty());
MOZ_ASSERT(aBodyString);
MOZ_ASSERT(aBodyArg.isString() || aBodyArg.isObject());
isTrusted = aBodyArg.isObject();
#ifdef DEBUG
bool trusted = isArgumentTrusted(aBodyArg, aBodyString, aError);
if (aError.Failed()) {
return false;
}
// The following assertion is guaranteed by the HTML implementation of
// HostGetCodeForEval.
MOZ_ASSERT(isTrusted == trusted);
#endif
} else {
MOZ_ASSERT(aCompilationType == JS::CompilationType::Function);
if (aBodyString) {
isTrusted = isArgumentTrusted(aBodyArg, aBodyString, aError);
if (aError.Failed()) {
return false;
}
}
if (isTrusted) {
MOZ_ASSERT(aParameterArgs.length() == aParameterStrings.length());
for (size_t index = 0; index < aParameterArgs.length(); index++) {
isTrusted = isArgumentTrusted(aParameterArgs[index],
aParameterStrings[index], aError);
if (aError.Failed()) {
return false;
}
if (!isTrusted) {
break;
}
}
}
}
// If successful, the steps below always ends up with sourceString ==
// codeString. Moreover if isTrusted == true, passing a new TrustedScript to
// GetTrustedTypesCompliantStringForTrustedScript would just return codeString
// immediately, so we can skip all these steps.
if (isTrusted) {
return true;
}
// Steps 2.5 - 2.6.
nsAutoJSString codeString;
if (NS_WARN_IF(!codeString.init(aCx, aCodeString))) {
aError.StealExceptionFromJSContext(aCx);
return false;
}
Maybe<nsAutoString> compliantStringHolder;
constexpr nsLiteralString evalSink = u"eval"_ns;
constexpr nsLiteralString functionSink = u"Function"_ns;
nsCOMPtr<nsIGlobalObject> pinnedGlobal = global;
const nsAString* compliantString =
dom::TrustedTypeUtils::GetTrustedTypesCompliantStringForTrustedScript(
codeString,
aCompilationType == JS::CompilationType::Function ? functionSink
: evalSink,
kTrustedTypesOnlySinkGroup, *pinnedGlobal, compliantStringHolder,
aError);
// Step 2.7-2.8.
// Callers will take care of throwing an EvalError when we return false.
if (aError.Failed()) {
aError.SuppressException();
return false;
}
return compliantString->Equals(codeString);
}
} // namespace mozilla::dom::TrustedTypeUtils

View File

@@ -105,6 +105,11 @@ GetTrustedTypesCompliantStringForTrustedHTML(const nsAString& aInput,
const nsINode& aNode,
Maybe<nsAutoString>& aResultHolder,
ErrorResult& aError);
MOZ_CAN_RUN_SCRIPT const nsAString*
GetTrustedTypesCompliantStringForTrustedScript(
const nsAString& aInput, const nsAString& aSink,
const nsAString& aSinkGroup, nsIGlobalObject& aGlobalObject,
Maybe<nsAutoString>& aResultHolder, ErrorResult& aError);
// https://w3c.github.io/trusted-types/dist/spec/#abstract-opdef-process-value-with-a-default-policy
template <typename ExpectedType>
@@ -131,6 +136,17 @@ MOZ_CAN_RUN_SCRIPT const nsAString* GetTrustedTypesCompliantAttributeValue(
bool HostGetCodeForEval(JSContext* aCx, JS::Handle<JSObject*> aCode,
JS::MutableHandle<JSString*> aOutCode);
// Implements steps 1 and 2 of EnsureCSPDoesNotBlockStringCompilation.
// See https://w3c.github.io/webappsec-csp/#can-compile-strings
MOZ_CAN_RUN_SCRIPT bool
AreArgumentsTrustedForEnsureCSPDoesNotBlockStringCompilation(
JSContext* aCx, JS::Handle<JSString*> aCodeString,
JS::CompilationType aCompilationType,
JS::Handle<JS::StackGCVector<JSString*>> aParameterStrings,
JS::Handle<JSString*> aBodyString,
JS::Handle<JS::StackGCVector<JS::Value>> aParameterArgs,
JS::Handle<JS::Value> aBodyArg, ErrorResult& aError);
} // namespace TrustedTypeUtils
} // namespace dom

View File

@@ -502,7 +502,7 @@ class LogViolationDetailsRunnable final : public WorkerMainThreadRunnable {
~LogViolationDetailsRunnable() = default;
};
bool ContentSecurityPolicyAllows(
MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION bool ContentSecurityPolicyAllows(
JSContext* aCx, JS::RuntimeCode aKind, JS::Handle<JSString*> aCodeString,
JS::CompilationType aCompilationType,
JS::Handle<JS::StackGCVector<JSString*>> aParameterStrings,
@@ -517,6 +517,19 @@ bool ContentSecurityPolicyAllows(
uint16_t violationType;
nsAutoJSString scriptSample;
if (aKind == JS::RuntimeCode::JS) {
ErrorResult error;
bool areArgumentsTrusted = TrustedTypeUtils::
AreArgumentsTrustedForEnsureCSPDoesNotBlockStringCompilation(
aCx, aCodeString, aCompilationType, aParameterStrings, aBodyString,
aParameterArgs, aBodyArg, error);
if (error.MaybeSetPendingException(aCx)) {
return false;
}
if (!areArgumentsTrusted) {
*aOutCanCompileStrings = false;
return true;
}
if (NS_WARN_IF(!scriptSample.init(aCx, aCodeString))) {
return false;
}

View File

@@ -1,54 +1,53 @@
[source-file.html]
expected:
if (os == "android") and fission: [ERROR, TIMEOUT]
TIMEOUT
if (os == "android") and fission: [ERROR]
[Basic HTTPS URL]
expected: TIMEOUT
expected: FAIL
[Basic HTTP URL]
expected: NOTRUN
expected: FAIL
[Basic WSS URL]
expected: NOTRUN
expected: FAIL
[Basic WS URL]
expected: NOTRUN
expected: FAIL
[Fragment]
expected: NOTRUN
expected: FAIL
[Query]
expected: NOTRUN
expected: FAIL
[Port]
expected: NOTRUN
expected: FAIL
[User:password]
expected: NOTRUN
expected: FAIL
[User]
expected: NOTRUN
expected: FAIL
[Invalid URL]
expected: NOTRUN
expected: FAIL
[file:]
expected: NOTRUN
expected: FAIL
[Custom protocol]
expected: NOTRUN
expected: FAIL
[about:blank]
expected: NOTRUN
expected: FAIL
[about:custom]
expected: NOTRUN
expected: FAIL
[data:]
expected: NOTRUN
expected: FAIL
[blob:]
expected: NOTRUN
expected: FAIL
[javascript:]
expected: NOTRUN
expected: FAIL

View File

@@ -1,18 +0,0 @@
[eval-csp-tt-default-policy-mutate.html]
[eval of string where default policy mutates value throws.]
expected: FAIL
[indirect eval of string where default policy mutates value throws.]
expected: FAIL
[Function constructor with string where default policy mutates value throws.]
expected: FAIL
[AsyncFunction constructor with string where default policy mutates value throws.]
expected: FAIL
[GeneratorFunction constructor with string where default policy mutates value throws.]
expected: FAIL
[AsyncGeneratorFunction constructor with string where default policy mutates value throws.]
expected: FAIL

View File

@@ -1,24 +0,0 @@
[eval-csp-tt-no-default-policy.html]
[eval of string fails.]
expected: FAIL
[indirect eval of string fails.]
expected: FAIL
[Function constructor of string fails.]
expected: FAIL
[Function constructor of all strings fails.]
expected: FAIL
[Function constructor of string and TrustedScript fails.]
expected: FAIL
[AsyncFunction constructor of string fails.]
expected: FAIL
[GeneratorFunction constructor of string fails.]
expected: FAIL
[AsyncGeneratorFunction constructor of string fails.]
expected: FAIL

View File

@@ -1,24 +0,0 @@
[eval-function-constructor-untrusted-arguments-and-applying-default-policy.html]
[plain string at index 0 (default policy modifying the function text).]
expected: FAIL
[plain string at index 1 (default policy modifying the function text).]
expected: FAIL
[plain string at index 2 (default policy modifying the function text).]
expected: FAIL
[plain string at index 3 (default policy modifying the function text).]
expected: FAIL
[TrustedScript with forged toString() at index 0 (default policy modifying the function text).]
expected: FAIL
[TrustedScript with forged toString() at index 1 (default policy modifying the function text).]
expected: FAIL
[TrustedScript with forged toString() at index 2 (default policy modifying the function text).]
expected: FAIL
[TrustedScript with forged toString() at index 3 (default policy modifying the function text).]
expected: FAIL

View File

@@ -1,192 +0,0 @@
[eval-function-constructor.html]
[Function constructor with mixed plain and trusted strings, mask #0]
expected: FAIL
[Function constructor with mixed plain and trusted strings, mask #1]
expected: FAIL
[Function constructor with mixed plain and trusted strings, mask #2]
expected: FAIL
[Function constructor with mixed plain and trusted strings, mask #3]
expected: FAIL
[Function constructor with mixed plain and trusted strings, mask #4]
expected: FAIL
[Function constructor with mixed plain and trusted strings, mask #5]
expected: FAIL
[Function constructor with mixed plain and trusted strings, mask #6]
expected: FAIL
[Function constructor with mixed plain and trusted strings, mask #7]
expected: FAIL
[Function constructor with mixed plain and trusted strings, mask #8]
expected: FAIL
[Function constructor with mixed plain and trusted strings, mask #9]
expected: FAIL
[Function constructor with mixed plain and trusted strings, mask #10]
expected: FAIL
[Function constructor with mixed plain and trusted strings, mask #11]
expected: FAIL
[Function constructor with mixed plain and trusted strings, mask #12]
expected: FAIL
[Function constructor with mixed plain and trusted strings, mask #13]
expected: FAIL
[Function constructor with mixed plain and trusted strings, mask #14]
expected: FAIL
[Function constructor with trusted strings, and a forged toString() for the one at index 0]
expected: FAIL
[Function constructor with trusted strings, and a forged toString() for the one at index 1]
expected: FAIL
[Function constructor with trusted strings, and a forged toString() for the one at index 2]
expected: FAIL
[Function constructor with trusted strings, and a forged toString() for the one at index 3]
expected: FAIL
[AsyncFunction constructor with mixed plain and trusted strings, mask #0]
expected: FAIL
[GeneratorFunction constructor with mixed plain and trusted strings, mask #0]
expected: FAIL
[AsyncGeneratorFunction constructor with mixed plain and trusted strings, mask #0]
expected: FAIL
[AsyncFunction constructor with mixed plain and trusted strings, mask #1]
expected: FAIL
[GeneratorFunction constructor with mixed plain and trusted strings, mask #1]
expected: FAIL
[AsyncGeneratorFunction constructor with mixed plain and trusted strings, mask #1]
expected: FAIL
[AsyncFunction constructor with mixed plain and trusted strings, mask #2]
expected: FAIL
[GeneratorFunction constructor with mixed plain and trusted strings, mask #2]
expected: FAIL
[AsyncGeneratorFunction constructor with mixed plain and trusted strings, mask #2]
expected: FAIL
[AsyncFunction constructor with mixed plain and trusted strings, mask #3]
expected: FAIL
[GeneratorFunction constructor with mixed plain and trusted strings, mask #3]
expected: FAIL
[AsyncGeneratorFunction constructor with mixed plain and trusted strings, mask #3]
expected: FAIL
[AsyncFunction constructor with mixed plain and trusted strings, mask #4]
expected: FAIL
[GeneratorFunction constructor with mixed plain and trusted strings, mask #4]
expected: FAIL
[AsyncGeneratorFunction constructor with mixed plain and trusted strings, mask #4]
expected: FAIL
[AsyncFunction constructor with mixed plain and trusted strings, mask #5]
expected: FAIL
[GeneratorFunction constructor with mixed plain and trusted strings, mask #5]
expected: FAIL
[AsyncGeneratorFunction constructor with mixed plain and trusted strings, mask #5]
expected: FAIL
[AsyncFunction constructor with mixed plain and trusted strings, mask #6]
expected: FAIL
[GeneratorFunction constructor with mixed plain and trusted strings, mask #6]
expected: FAIL
[AsyncGeneratorFunction constructor with mixed plain and trusted strings, mask #6]
expected: FAIL
[AsyncFunction constructor with mixed plain and trusted strings, mask #7]
expected: FAIL
[GeneratorFunction constructor with mixed plain and trusted strings, mask #7]
expected: FAIL
[AsyncGeneratorFunction constructor with mixed plain and trusted strings, mask #7]
expected: FAIL
[AsyncFunction constructor with mixed plain and trusted strings, mask #8]
expected: FAIL
[GeneratorFunction constructor with mixed plain and trusted strings, mask #8]
expected: FAIL
[AsyncGeneratorFunction constructor with mixed plain and trusted strings, mask #8]
expected: FAIL
[AsyncFunction constructor with mixed plain and trusted strings, mask #9]
expected: FAIL
[GeneratorFunction constructor with mixed plain and trusted strings, mask #9]
expected: FAIL
[AsyncGeneratorFunction constructor with mixed plain and trusted strings, mask #9]
expected: FAIL
[AsyncFunction constructor with mixed plain and trusted strings, mask #10]
expected: FAIL
[GeneratorFunction constructor with mixed plain and trusted strings, mask #10]
expected: FAIL
[AsyncGeneratorFunction constructor with mixed plain and trusted strings, mask #10]
expected: FAIL
[AsyncFunction constructor with mixed plain and trusted strings, mask #11]
expected: FAIL
[GeneratorFunction constructor with mixed plain and trusted strings, mask #11]
expected: FAIL
[AsyncGeneratorFunction constructor with mixed plain and trusted strings, mask #11]
expected: FAIL
[AsyncFunction constructor with mixed plain and trusted strings, mask #12]
expected: FAIL
[GeneratorFunction constructor with mixed plain and trusted strings, mask #12]
expected: FAIL
[AsyncGeneratorFunction constructor with mixed plain and trusted strings, mask #12]
expected: FAIL
[AsyncFunction constructor with mixed plain and trusted strings, mask #13]
expected: FAIL
[GeneratorFunction constructor with mixed plain and trusted strings, mask #13]
expected: FAIL
[AsyncGeneratorFunction constructor with mixed plain and trusted strings, mask #13]
expected: FAIL
[AsyncFunction constructor with mixed plain and trusted strings, mask #14]
expected: FAIL
[GeneratorFunction constructor with mixed plain and trusted strings, mask #14]
expected: FAIL
[AsyncGeneratorFunction constructor with mixed plain and trusted strings, mask #14]
expected: FAIL

View File

@@ -1,9 +0,0 @@
[eval-with-permissive-csp.html]
[eval with plain string with Trusted Types and permissive CSP throws (no type).]
expected: FAIL
[indirect eval with plain string with Trusted Types and permissive CSP throws (no type).]
expected: FAIL
[Function constructor with plain string with Trusted Types and permissive CSP throws (no type).]
expected: FAIL

View File

@@ -1,10 +1,10 @@
[trusted-types-eval-reporting.html]
expected: TIMEOUT
[Trusted Type violation report: evaluating a string.]
expected: FAIL
expected: TIMEOUT
[Trusted Type violation report: evaluating a Trusted Script.]
expected: TIMEOUT
expected: NOTRUN
[Trusted Type violation report: default policy transforms the script before CSP checks runs.]
expected: NOTRUN

View File

@@ -1,3 +0,0 @@
[tt-block-eval.html]
[eval blocks if the default policy rejects a value.]
expected: FAIL

View File

@@ -0,0 +1,22 @@
<!DOCTYPE html>
<html>
<head>
<link rel="help" href="https://w3c.github.io/webappsec-csp/#can-compile-strings">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<meta http-equiv="Content-Security-Policy" content="require-trusted-types-for 'script'">
</head>
<body>
<script>
let policy = trustedTypes.createPolicy("p", {
createScript: s => { throw Error('createScript exception'); }
});
test(t => {
assert_throws_js(EvalError, _ => eval("1+2"));
}, `EvalError thrown if the callback of the default policy throws an error (eval).`);
test(t => {
assert_throws_js(EvalError, _ => new Function("return 3;"));
}, `EvalError thrown if the callback of the default policy throws an error (new Function).`);
</script>
</body>
</html>