mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-23 12:51:06 +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
|
// Get the CSP for addon sandboxes. If the principal is expanded and has a
|
||||||
// csp, we're probably in luck.
|
// csp, we're probably in luck.
|
||||||
auto* basePrin = BasePrincipal::Cast(subjectPrincipal);
|
auto* basePrin = BasePrincipal::Cast(subjectPrincipal);
|
||||||
// ContentScriptAddonPolicy means it is also an expanded principal, thus
|
// TODO bug 1548468: Move CSP off ExpandedPrincipal.
|
||||||
// this is in a sandbox used as a content script.
|
if (basePrin->Is<ExpandedPrincipal>()) {
|
||||||
if (basePrin->ContentScriptAddonPolicy()) {
|
|
||||||
basePrin->As<ExpandedPrincipal>()->GetCsp(getter_AddRefs(csp));
|
basePrin->As<ExpandedPrincipal>()->GetCsp(getter_AddRefs(csp));
|
||||||
}
|
}
|
||||||
// don't do anything unless there's a 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
|
* 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
|
* parameter, in order to ensure that the script is cleaned up at the
|
||||||
* same time as the content itself.
|
* 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
|
* - sandboxName: {String} Identifies the sandbox in about:memory. This
|
||||||
* property is optional, but very useful for tracking memory usage. A
|
* property is optional, but very useful for tracking memory usage. A
|
||||||
* recommended value for this property is an absolute path to the script
|
* 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);
|
return Define(cx, obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
nsresult SetSandboxCSP(nsISupports* prinOrSop, const nsAString& cspString) {
|
||||||
* 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) {
|
|
||||||
nsCOMPtr<nsIPrincipal> principal = do_QueryInterface(prinOrSop);
|
nsCOMPtr<nsIPrincipal> principal = do_QueryInterface(prinOrSop);
|
||||||
if (!principal) {
|
if (!principal) {
|
||||||
return NS_OK;
|
return NS_ERROR_INVALID_ARG;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto* basePrin = BasePrincipal::Cast(principal);
|
auto* basePrin = BasePrincipal::Cast(principal);
|
||||||
// We only get an addonPolicy if the principal is an
|
if (!basePrin->Is<ExpandedPrincipal>()) {
|
||||||
// expanded principal with an extension principal in it.
|
return NS_ERROR_INVALID_ARG;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
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;
|
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
|
#ifdef MOZ_DEBUG
|
||||||
// Bug 1548468: Move CSP off ExpandedPrincipal
|
// Bug 1548468: Move CSP off ExpandedPrincipal
|
||||||
expanded->GetCsp(getter_AddRefs(csp));
|
expanded->GetCsp(getter_AddRefs(csp));
|
||||||
@ -1196,7 +1189,7 @@ nsresult ApplyAddonContentScriptCSP(nsISupports* prinOrSop) {
|
|||||||
nsAutoString parsedPolicyStr;
|
nsAutoString parsedPolicyStr;
|
||||||
for (uint32_t i = 0; i < count; i++) {
|
for (uint32_t i = 0; i < count; i++) {
|
||||||
csp->GetPolicyString(i, parsedPolicyStr);
|
csp->GetPolicyString(i, parsedPolicyStr);
|
||||||
MOZ_ASSERT(!parsedPolicyStr.Equals(baseCSP));
|
MOZ_ASSERT(!parsedPolicyStr.Equals(cspString));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1217,7 +1210,7 @@ nsresult ApplyAddonContentScriptCSP(nsISupports* prinOrSop) {
|
|||||||
MOZ_TRY(
|
MOZ_TRY(
|
||||||
csp->SetRequestContextWithPrincipal(clonedPrincipal, selfURI, ""_ns, 0));
|
csp->SetRequestContextWithPrincipal(clonedPrincipal, selfURI, ""_ns, 0));
|
||||||
|
|
||||||
MOZ_TRY(csp->AppendPolicy(baseCSP, false, false));
|
MOZ_TRY(csp->AppendPolicy(cspString, false, false));
|
||||||
|
|
||||||
expanded->SetCsp(csp);
|
expanded->SetCsp(csp);
|
||||||
return NS_OK;
|
return NS_OK;
|
||||||
@ -1798,6 +1791,33 @@ bool OptionsBase::ParseString(const char* name, nsString& prop) {
|
|||||||
return true;
|
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.
|
* Helper that tries to get jsid property from the options object.
|
||||||
*/
|
*/
|
||||||
@ -1883,6 +1903,8 @@ bool SandboxOptions::Parse() {
|
|||||||
ParseBoolean("isWebExtensionContentScript",
|
ParseBoolean("isWebExtensionContentScript",
|
||||||
&isWebExtensionContentScript) &&
|
&isWebExtensionContentScript) &&
|
||||||
ParseBoolean("forceSecureContext", &forceSecureContext) &&
|
ParseBoolean("forceSecureContext", &forceSecureContext) &&
|
||||||
|
ParseOptionalString("sandboxContentSecurityPolicy",
|
||||||
|
sandboxContentSecurityPolicy) &&
|
||||||
ParseString("sandboxName", sandboxName) &&
|
ParseString("sandboxName", sandboxName) &&
|
||||||
ParseObject("sameZoneAs", &sameZoneAs) &&
|
ParseObject("sameZoneAs", &sameZoneAs) &&
|
||||||
ParseBoolean("freshCompartment", &freshCompartment) &&
|
ParseBoolean("freshCompartment", &freshCompartment) &&
|
||||||
@ -1994,8 +2016,6 @@ nsresult nsXPCComponents_utils_Sandbox::CallOrConstruct(
|
|||||||
} else {
|
} else {
|
||||||
ok = GetExpandedPrincipal(cx, obj, options, getter_AddRefs(expanded));
|
ok = GetExpandedPrincipal(cx, obj, options, getter_AddRefs(expanded));
|
||||||
prinOrSop = expanded;
|
prinOrSop = expanded;
|
||||||
// If this is an addon content script we need to apply the csp.
|
|
||||||
MOZ_TRY(ApplyAddonContentScriptCSP(prinOrSop));
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ok = GetPrincipalOrSOP(cx, obj, getter_AddRefs(prinOrSop));
|
ok = GetPrincipalOrSOP(cx, obj, getter_AddRefs(prinOrSop));
|
||||||
@ -2010,6 +2030,21 @@ nsresult nsXPCComponents_utils_Sandbox::CallOrConstruct(
|
|||||||
return ThrowAndFail(NS_ERROR_INVALID_ARG, cx, _retval);
|
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))) {
|
if (NS_FAILED(AssembleSandboxMemoryReporterName(cx, options.sandboxName))) {
|
||||||
return ThrowAndFail(NS_ERROR_INVALID_ARG, cx, _retval);
|
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 ParseJSString(const char* name, JS::MutableHandleString prop);
|
||||||
bool ParseString(const char* name, nsCString& prop);
|
bool ParseString(const char* name, nsCString& prop);
|
||||||
bool ParseString(const char* name, nsString& 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 ParseId(const char* name, JS::MutableHandleId id);
|
||||||
bool ParseUInt32(const char* name, uint32_t* prop);
|
bool ParseUInt32(const char* name, uint32_t* prop);
|
||||||
|
|
||||||
@ -2306,6 +2307,7 @@ class MOZ_STACK_CLASS SandboxOptions : public OptionsBase {
|
|||||||
bool wantExportHelpers;
|
bool wantExportHelpers;
|
||||||
bool isWebExtensionContentScript;
|
bool isWebExtensionContentScript;
|
||||||
JS::RootedObject proto;
|
JS::RootedObject proto;
|
||||||
|
mozilla::Maybe<nsString> sandboxContentSecurityPolicy;
|
||||||
nsCString sandboxName;
|
nsCString sandboxName;
|
||||||
JS::RootedObject sameZoneAs;
|
JS::RootedObject sameZoneAs;
|
||||||
bool forceSecureContext;
|
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_atob.js"]
|
||||||
|
|
||||||
|
["test_sandbox_csp.js"]
|
||||||
|
|
||||||
["test_sandbox_metadata.js"]
|
["test_sandbox_metadata.js"]
|
||||||
|
|
||||||
["test_sandbox_name.js"]
|
["test_sandbox_name.js"]
|
||||||
|
@ -958,6 +958,7 @@ class ContentScriptContextChild extends BaseContext {
|
|||||||
|
|
||||||
let isMV2 = extension.manifestVersion == 2;
|
let isMV2 = extension.manifestVersion == 2;
|
||||||
let wantGlobalProperties;
|
let wantGlobalProperties;
|
||||||
|
let sandboxContentSecurityPolicy;
|
||||||
if (isMV2) {
|
if (isMV2) {
|
||||||
// In MV2, fetch/XHR support cross-origin requests.
|
// In MV2, fetch/XHR support cross-origin requests.
|
||||||
// WebSocket was also included to avoid CSP effects (bug 1676024).
|
// WebSocket was also included to avoid CSP effects (bug 1676024).
|
||||||
@ -965,11 +966,16 @@ class ContentScriptContextChild extends BaseContext {
|
|||||||
} else {
|
} else {
|
||||||
// In MV3, fetch/XHR have the same capabilities as the web page.
|
// In MV3, fetch/XHR have the same capabilities as the web page.
|
||||||
wantGlobalProperties = [];
|
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, {
|
this.sandbox = Cu.Sandbox(principal, {
|
||||||
metadata,
|
metadata,
|
||||||
sandboxName: `Content Script ${extension.policy.debugName}`,
|
sandboxName: `Content Script ${extension.policy.debugName}`,
|
||||||
sandboxPrototype: contentWindow,
|
sandboxPrototype: contentWindow,
|
||||||
|
sandboxContentSecurityPolicy,
|
||||||
sameZoneAs: contentWindow,
|
sameZoneAs: contentWindow,
|
||||||
wantXrays: true,
|
wantXrays: true,
|
||||||
isWebExtensionContentScript: true,
|
isWebExtensionContentScript: true,
|
||||||
|
Loading…
Reference in New Issue
Block a user