mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-23 04:41:11 +00:00
Bug 1930749 - Add option to Cu.Sandbox to specify CSP r=mccr8
Differential Revision: https://phabricator.services.mozilla.com/D228711
This commit is contained in:
parent
4e69784010
commit
f41915445f
@ -497,9 +497,8 @@ bool nsScriptSecurityManager::ContentSecurityPolicyPermitsJSAction(
|
||||
// Get the CSP for addon sandboxes. If the principal is expanded and has a
|
||||
// csp, we're probably in luck.
|
||||
auto* basePrin = BasePrincipal::Cast(subjectPrincipal);
|
||||
// ContentScriptAddonPolicy means it is also an expanded principal, thus
|
||||
// this is in a sandbox used as a content script.
|
||||
if (basePrin->ContentScriptAddonPolicy()) {
|
||||
// TODO bug 1548468: Move CSP off ExpandedPrincipal.
|
||||
if (basePrin->Is<ExpandedPrincipal>()) {
|
||||
basePrin->As<ExpandedPrincipal>()->GetCsp(getter_AddRefs(csp));
|
||||
}
|
||||
// don't do anything unless there's a CSP
|
||||
|
@ -185,6 +185,13 @@ interface nsIXPCComponents_Utils : nsISupports
|
||||
* Content scripts should pass the window they're running in as this
|
||||
* parameter, in order to ensure that the script is cleaned up at the
|
||||
* same time as the content itself.
|
||||
* - sandboxContentSecurityPolicy: {String} The Content Security Policy
|
||||
* to apply in this sandbox. This can be used to restrict eval
|
||||
* (e.g. "script-src 'self'"). It does not apply to DOM methods
|
||||
* that were retrieved from objects outside the sandbox.
|
||||
* This is only implemented for Expanded Principals; if desired for
|
||||
* other principals, bug 1548468 must be resolved first.
|
||||
* When not specified, the default CSP is used (usually no CSP).
|
||||
* - sandboxName: {String} Identifies the sandbox in about:memory. This
|
||||
* property is optional, but very useful for tracking memory usage. A
|
||||
* recommended value for this property is an absolute path to the script
|
||||
|
@ -1149,42 +1149,35 @@ bool xpc::GlobalProperties::DefineInSandbox(JSContext* cx,
|
||||
return Define(cx, obj);
|
||||
}
|
||||
|
||||
/**
|
||||
* If enabled, apply the extension base CSP, then apply the
|
||||
* content script CSP which will either be a default or one
|
||||
* provided by the extension in its manifest.
|
||||
*/
|
||||
nsresult ApplyAddonContentScriptCSP(nsISupports* prinOrSop) {
|
||||
nsresult SetSandboxCSP(nsISupports* prinOrSop, const nsAString& cspString) {
|
||||
nsCOMPtr<nsIPrincipal> principal = do_QueryInterface(prinOrSop);
|
||||
if (!principal) {
|
||||
return NS_OK;
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
auto* basePrin = BasePrincipal::Cast(principal);
|
||||
// We only get an addonPolicy if the principal is an
|
||||
// expanded principal with an extension principal in it.
|
||||
auto* addonPolicy = basePrin->ContentScriptAddonPolicy();
|
||||
if (!addonPolicy) {
|
||||
return NS_OK;
|
||||
}
|
||||
// For backwards compatibility, content scripts have no CSP
|
||||
// in manifest v2. Only apply content script CSP to V3 or later.
|
||||
if (addonPolicy->ManifestVersion() < 3) {
|
||||
return NS_OK;
|
||||
if (!basePrin->Is<ExpandedPrincipal>()) {
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
auto* expanded = basePrin->As<ExpandedPrincipal>();
|
||||
|
||||
nsString url;
|
||||
MOZ_TRY_VAR(url, addonPolicy->GetURL(u""_ns));
|
||||
|
||||
nsCOMPtr<nsIURI> selfURI;
|
||||
MOZ_TRY(NS_NewURI(getter_AddRefs(selfURI), url));
|
||||
|
||||
const nsAString& baseCSP = addonPolicy->BaseCSP();
|
||||
|
||||
// If we got here, we're definitly an expanded principal.
|
||||
auto expanded = basePrin->As<ExpandedPrincipal>();
|
||||
nsCOMPtr<nsIContentSecurityPolicy> csp;
|
||||
|
||||
// The choice of self-uri (self-origin) to use in a Sandbox depends on the
|
||||
// use case. For now, we default to a non-existing URL because there is no
|
||||
// use case that requires 'self' to have a particular value. Consumers can
|
||||
// always specify the URL explicitly instead of 'self'. Besides, the CSP
|
||||
// enforcement in a Sandbox is barely implemented, except for eval()-like
|
||||
// execution.
|
||||
//
|
||||
// moz-extension:-resources are never blocked by CSP because the protocol is
|
||||
// registered with URI_IS_LOCAL_RESOURCE and subjectToCSP in nsCSPService.cpp
|
||||
// therefore allows the load. This matches the CSP spec, which explicitly
|
||||
// states that CSP should not interfere with addons. Because of this, we do
|
||||
// not need to set selfURI to the real moz-extension:-URL, even in cases
|
||||
// where we want to restrict all scripts except for moz-extension:-URLs.
|
||||
nsCOMPtr<nsIURI> selfURI;
|
||||
MOZ_TRY(NS_NewURI(getter_AddRefs(selfURI), "moz-extension://dummy"_ns));
|
||||
|
||||
#ifdef MOZ_DEBUG
|
||||
// Bug 1548468: Move CSP off ExpandedPrincipal
|
||||
expanded->GetCsp(getter_AddRefs(csp));
|
||||
@ -1196,7 +1189,7 @@ nsresult ApplyAddonContentScriptCSP(nsISupports* prinOrSop) {
|
||||
nsAutoString parsedPolicyStr;
|
||||
for (uint32_t i = 0; i < count; i++) {
|
||||
csp->GetPolicyString(i, parsedPolicyStr);
|
||||
MOZ_ASSERT(!parsedPolicyStr.Equals(baseCSP));
|
||||
MOZ_ASSERT(!parsedPolicyStr.Equals(cspString));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1217,7 +1210,7 @@ nsresult ApplyAddonContentScriptCSP(nsISupports* prinOrSop) {
|
||||
MOZ_TRY(
|
||||
csp->SetRequestContextWithPrincipal(clonedPrincipal, selfURI, ""_ns, 0));
|
||||
|
||||
MOZ_TRY(csp->AppendPolicy(baseCSP, false, false));
|
||||
MOZ_TRY(csp->AppendPolicy(cspString, false, false));
|
||||
|
||||
expanded->SetCsp(csp);
|
||||
return NS_OK;
|
||||
@ -1798,6 +1791,33 @@ bool OptionsBase::ParseString(const char* name, nsString& prop) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Helper that tries to get a string property from the options object.
|
||||
*/
|
||||
bool OptionsBase::ParseOptionalString(const char* name, Maybe<nsString>& prop) {
|
||||
RootedValue value(mCx);
|
||||
bool found;
|
||||
bool ok = ParseValue(name, &value, &found);
|
||||
NS_ENSURE_TRUE(ok, false);
|
||||
|
||||
if (!found || value.isUndefined()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!value.isString()) {
|
||||
JS_ReportErrorASCII(mCx, "Expected a string value for property %s", name);
|
||||
return false;
|
||||
}
|
||||
|
||||
nsAutoJSString strVal;
|
||||
if (!strVal.init(mCx, value.toString())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
prop = Some(strVal);
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Helper that tries to get jsid property from the options object.
|
||||
*/
|
||||
@ -1883,6 +1903,8 @@ bool SandboxOptions::Parse() {
|
||||
ParseBoolean("isWebExtensionContentScript",
|
||||
&isWebExtensionContentScript) &&
|
||||
ParseBoolean("forceSecureContext", &forceSecureContext) &&
|
||||
ParseOptionalString("sandboxContentSecurityPolicy",
|
||||
sandboxContentSecurityPolicy) &&
|
||||
ParseString("sandboxName", sandboxName) &&
|
||||
ParseObject("sameZoneAs", &sameZoneAs) &&
|
||||
ParseBoolean("freshCompartment", &freshCompartment) &&
|
||||
@ -1994,8 +2016,6 @@ nsresult nsXPCComponents_utils_Sandbox::CallOrConstruct(
|
||||
} else {
|
||||
ok = GetExpandedPrincipal(cx, obj, options, getter_AddRefs(expanded));
|
||||
prinOrSop = expanded;
|
||||
// If this is an addon content script we need to apply the csp.
|
||||
MOZ_TRY(ApplyAddonContentScriptCSP(prinOrSop));
|
||||
}
|
||||
} else {
|
||||
ok = GetPrincipalOrSOP(cx, obj, getter_AddRefs(prinOrSop));
|
||||
@ -2010,6 +2030,21 @@ nsresult nsXPCComponents_utils_Sandbox::CallOrConstruct(
|
||||
return ThrowAndFail(NS_ERROR_INVALID_ARG, cx, _retval);
|
||||
}
|
||||
|
||||
if (options.sandboxContentSecurityPolicy.isSome()) {
|
||||
if (!expanded) {
|
||||
// CSP is currently stored on ExpandedPrincipal. If CSP moves off
|
||||
// ExpandedPrincipal (bug 1548468), then we can drop/relax this check.
|
||||
JS_ReportErrorASCII(cx,
|
||||
"sandboxContentSecurityPolicy is currently only "
|
||||
"supported with ExpandedPrincipals");
|
||||
return ThrowAndFail(NS_ERROR_INVALID_ARG, cx, _retval);
|
||||
}
|
||||
rv = SetSandboxCSP(prinOrSop, options.sandboxContentSecurityPolicy.value());
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
|
||||
if (NS_FAILED(AssembleSandboxMemoryReporterName(cx, options.sandboxName))) {
|
||||
return ThrowAndFail(NS_ERROR_INVALID_ARG, cx, _retval);
|
||||
}
|
||||
|
@ -2269,6 +2269,7 @@ class MOZ_STACK_CLASS OptionsBase {
|
||||
bool ParseJSString(const char* name, JS::MutableHandleString prop);
|
||||
bool ParseString(const char* name, nsCString& prop);
|
||||
bool ParseString(const char* name, nsString& prop);
|
||||
bool ParseOptionalString(const char* name, mozilla::Maybe<nsString>& prop);
|
||||
bool ParseId(const char* name, JS::MutableHandleId id);
|
||||
bool ParseUInt32(const char* name, uint32_t* prop);
|
||||
|
||||
@ -2306,6 +2307,7 @@ class MOZ_STACK_CLASS SandboxOptions : public OptionsBase {
|
||||
bool wantExportHelpers;
|
||||
bool isWebExtensionContentScript;
|
||||
JS::RootedObject proto;
|
||||
mozilla::Maybe<nsString> sandboxContentSecurityPolicy;
|
||||
nsCString sandboxName;
|
||||
JS::RootedObject sameZoneAs;
|
||||
bool forceSecureContext;
|
||||
|
110
js/xpconnect/tests/unit/test_sandbox_csp.js
Normal file
110
js/xpconnect/tests/unit/test_sandbox_csp.js
Normal file
@ -0,0 +1,110 @@
|
||||
"use strict";
|
||||
|
||||
function isEvalAllowed(sandbox) {
|
||||
try {
|
||||
Cu.evalInSandbox("eval('1234')", sandbox);
|
||||
return true;
|
||||
} catch (e) {
|
||||
Assert.equal(e.message, "call to eval() blocked by CSP", "Eval error msg");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
add_task(function test_empty_csp() {
|
||||
let sand = Cu.Sandbox(["http://example.com/"], {
|
||||
sandboxContentSecurityPolicy: "",
|
||||
});
|
||||
Assert.ok(isEvalAllowed(sand), "eval() not blocked with empty CSP string");
|
||||
});
|
||||
|
||||
add_task(function test_undefined_csp() {
|
||||
let sand = Cu.Sandbox(["http://example.com/"], {
|
||||
sandboxContentSecurityPolicy: undefined,
|
||||
});
|
||||
Assert.ok(isEvalAllowed(sand), "eval() not blocked with undefined CSP");
|
||||
});
|
||||
|
||||
add_task(function test_malformed_csp() {
|
||||
let sand = Cu.Sandbox(["http://example.com/"], {
|
||||
sandboxContentSecurityPolicy: "This is not a valid CSP value",
|
||||
});
|
||||
Assert.ok(isEvalAllowed(sand), "eval() not blocked with undefined CSP");
|
||||
});
|
||||
|
||||
add_task(function test_allowed_by_sandboxContentSecurityPolicy() {
|
||||
let sand = Cu.Sandbox(["http://example.com/"], {
|
||||
sandboxContentSecurityPolicy: "script-src 'unsafe-eval';",
|
||||
});
|
||||
Assert.ok(isEvalAllowed(sand), "eval() allowed by 'unsafe-eval' CSP");
|
||||
});
|
||||
|
||||
add_task(function test_blocked_by_sandboxContentSecurityPolicy() {
|
||||
let sand = Cu.Sandbox(["http://example.com/"], {
|
||||
sandboxContentSecurityPolicy: "script-src 'none';",
|
||||
});
|
||||
|
||||
// Until bug 1548468 is fixed, CSP only works with an ExpandedPrincipal.
|
||||
Assert.ok(Cu.getObjectPrincipal(sand).isExpandedPrincipal, "Exp principal");
|
||||
|
||||
Assert.ok(!isEvalAllowed(sand), "eval() should be blocked by CSP");
|
||||
// sandbox.eval is also blocked: callers should use Cu.evalInSandbox instead.
|
||||
Assert.throws(
|
||||
() => sand.eval("123"),
|
||||
/EvalError: call to eval\(\) blocked by CSP/,
|
||||
"sandbox.eval() is also blocked by CSP"
|
||||
);
|
||||
});
|
||||
|
||||
add_task(function test_sandboxContentSecurityPolicy_on_content_principal() {
|
||||
Assert.throws(
|
||||
() => {
|
||||
Cu.Sandbox("http://example.com", {
|
||||
sandboxContentSecurityPolicy: "script-src 'none';",
|
||||
});
|
||||
},
|
||||
/Error: sandboxContentSecurityPolicy is currently only supported with ExpandedPrincipals/,
|
||||
// Until bug 1548468 is fixed, CSP only works with an ExpandedPrincipal.
|
||||
"sandboxContentSecurityPolicy does not work with content principal"
|
||||
);
|
||||
});
|
||||
|
||||
add_task(function test_sandboxContentSecurityPolicy_on_null_principal() {
|
||||
Assert.throws(
|
||||
() => {
|
||||
Cu.Sandbox(null, { sandboxContentSecurityPolicy: "script-src 'none';" });
|
||||
},
|
||||
/Error: sandboxContentSecurityPolicy is currently only supported with ExpandedPrincipals/,
|
||||
// Until bug 1548468 is fixed, CSP only works with an ExpandedPrincipal.
|
||||
"sandboxContentSecurityPolicy does not work with content principal"
|
||||
);
|
||||
});
|
||||
|
||||
add_task(function test_sandboxContentSecurityPolicy_on_content_principal() {
|
||||
Assert.throws(
|
||||
() => {
|
||||
Cu.Sandbox("http://example.com", {
|
||||
sandboxContentSecurityPolicy: "script-src 'none';",
|
||||
});
|
||||
},
|
||||
/Error: sandboxContentSecurityPolicy is currently only supported with ExpandedPrincipals/,
|
||||
// Until bug 1548468 is fixed, CSP only works with an ExpandedPrincipal.
|
||||
"sandboxContentSecurityPolicy does not work with content principal"
|
||||
);
|
||||
});
|
||||
|
||||
add_task(function test_sandboxContentSecurityPolicy_on_system_principal() {
|
||||
const systemPrincipal = Services.scriptSecurityManager.getSystemPrincipal();
|
||||
// Note: if we ever introduce support for CSP in non-Expanded principals,
|
||||
// then the test should set security.allow_eval_with_system_principal=true
|
||||
// to make sure that eval() is blocked because of CSP and not another reason.
|
||||
Assert.throws(
|
||||
() => {
|
||||
Cu.Sandbox(systemPrincipal, {
|
||||
sandboxContentSecurityPolicy: "script-src 'none';",
|
||||
});
|
||||
},
|
||||
/Error: sandboxContentSecurityPolicy is currently only supported with ExpandedPrincipals/,
|
||||
// Until bug 1548468 is fixed, CSP only works with an ExpandedPrincipal.
|
||||
"sandboxContentSecurityPolicy does not work with system principal"
|
||||
);
|
||||
});
|
@ -361,6 +361,8 @@ head = "head_ongc.js"
|
||||
|
||||
["test_sandbox_atob.js"]
|
||||
|
||||
["test_sandbox_csp.js"]
|
||||
|
||||
["test_sandbox_metadata.js"]
|
||||
|
||||
["test_sandbox_name.js"]
|
||||
|
@ -958,6 +958,7 @@ class ContentScriptContextChild extends BaseContext {
|
||||
|
||||
let isMV2 = extension.manifestVersion == 2;
|
||||
let wantGlobalProperties;
|
||||
let sandboxContentSecurityPolicy;
|
||||
if (isMV2) {
|
||||
// In MV2, fetch/XHR support cross-origin requests.
|
||||
// WebSocket was also included to avoid CSP effects (bug 1676024).
|
||||
@ -965,11 +966,16 @@ class ContentScriptContextChild extends BaseContext {
|
||||
} else {
|
||||
// In MV3, fetch/XHR have the same capabilities as the web page.
|
||||
wantGlobalProperties = [];
|
||||
// In MV3, the base CSP is enforced for content scripts. Overrides are
|
||||
// currently not supported, but this was considered at some point, see
|
||||
// https://bugzilla.mozilla.org/show_bug.cgi?id=1581611#c10
|
||||
sandboxContentSecurityPolicy = extension.policy.baseCSP;
|
||||
}
|
||||
this.sandbox = Cu.Sandbox(principal, {
|
||||
metadata,
|
||||
sandboxName: `Content Script ${extension.policy.debugName}`,
|
||||
sandboxPrototype: contentWindow,
|
||||
sandboxContentSecurityPolicy,
|
||||
sameZoneAs: contentWindow,
|
||||
wantXrays: true,
|
||||
isWebExtensionContentScript: true,
|
||||
|
Loading…
Reference in New Issue
Block a user