Bug 1740263 - Continue to allow WASM by default in Webextensions v2. r=mixedpuppy,robwu

Differential Revision: https://phabricator.services.mozilla.com/D142953
This commit is contained in:
Tom Schuster 2022-05-19 14:13:51 +00:00
parent 3319a4e36b
commit cf003c21f0
12 changed files with 248 additions and 25 deletions

View File

@ -20,6 +20,11 @@ interface nsIAddonPolicyService : nsISupports
*/
readonly attribute AString defaultCSP;
/**
* Same as above, but used for extensions using manifest v3.
*/
readonly attribute AString defaultCSPV3;
/**
* Returns the base content security policy which applies to all extension resources.
*/
@ -79,13 +84,14 @@ interface nsIAddonContentPolicy : nsISupports
/* options to pass to validateAddonCSP
*
* Manifest V2 uses CSP_ALLOW_ANY.
* In Manifest V3, extension_pages would use CSP_ALLOW_LOCALHOST and
* sandbox would use CSP_ALLOW_EVAL.
* In Manifest V3, extension_pages would use CSP_ALLOW_LOCALHOST|CSP_ALLOW_WASM
* and sandbox would use CSP_ALLOW_EVAL.
*/
const unsigned long CSP_ALLOW_ANY = 0xFFFF;
const unsigned long CSP_ALLOW_LOCALHOST = (1<<0);
const unsigned long CSP_ALLOW_EVAL = (1<<1);
const unsigned long CSP_ALLOW_REMOTE = (1<<2);
const unsigned long CSP_ALLOW_WASM = (1<<3);
/**
* Checks a custom content security policy string, to ensure that it meets

View File

@ -3679,9 +3679,10 @@ pref("extensions.webcompat-reporter.newIssueEndpoint", "https://webcompat.com/is
#endif
// Add-on content security policies.
pref("extensions.webextensions.base-content-security-policy", "script-src 'self' https://* http://localhost:* http://127.0.0.1:* moz-extension: blob: filesystem: 'unsafe-eval' 'unsafe-inline'; object-src 'self' moz-extension: blob: filesystem:;");
pref("extensions.webextensions.base-content-security-policy.v3", "script-src 'self' http://localhost:* http://127.0.0.1:*; object-src 'self';");
pref("extensions.webextensions.default-content-security-policy", "script-src 'self'; object-src 'self';");
pref("extensions.webextensions.base-content-security-policy", "script-src 'self' https://* http://localhost:* http://127.0.0.1:* moz-extension: blob: filesystem: 'unsafe-eval' 'wasm-unsafe-eval' 'unsafe-inline'; object-src 'self' moz-extension: blob: filesystem:;");
pref("extensions.webextensions.base-content-security-policy.v3", "script-src 'self' 'wasm-unsafe-eval' http://localhost:* http://127.0.0.1:*; object-src 'self';");
pref("extensions.webextensions.default-content-security-policy", "script-src 'self' 'wasm-unsafe-eval'; object-src 'self';");
pref("extensions.webextensions.default-content-security-policy.v3", "script-src 'self'; object-src 'self';");
pref("network.buffer.cache.count", 24);

View File

@ -47,7 +47,12 @@ using dom::Promise;
#define DEFAULT_CSP_PREF \
"extensions.webextensions.default-content-security-policy"
#define DEFAULT_DEFAULT_CSP "script-src 'self'; object-src 'self';"
#define DEFAULT_DEFAULT_CSP \
"script-src 'self' 'wasm-unsafe-eval'; object-src 'self';"
#define DEFAULT_CSP_PREF_V3 \
"extensions.webextensions.default-content-security-policy.v3"
#define DEFAULT_DEFAULT_CSP_V3 "script-src 'self'; object-src 'self';"
#define OBS_TOPIC_PRELOAD_SCRIPT "web-extension-preload-content-script"
#define OBS_TOPIC_LOAD_SCRIPT "web-extension-load-content-script"
@ -89,6 +94,7 @@ ExtensionPolicyService::ExtensionPolicyService() {
MOZ_RELEASE_ASSERT(mObs);
mDefaultCSP.SetIsVoid(true);
mDefaultCSPV3.SetIsVoid(true);
RegisterObservers();
}
@ -213,6 +219,7 @@ void ExtensionPolicyService::RegisterObservers() {
}
Preferences::AddStrongObserver(this, DEFAULT_CSP_PREF);
Preferences::AddStrongObserver(this, DEFAULT_CSP_PREF_V3);
}
void ExtensionPolicyService::UnregisterObservers() {
@ -223,6 +230,7 @@ void ExtensionPolicyService::UnregisterObservers() {
}
Preferences::RemoveObserver(this, DEFAULT_CSP_PREF);
Preferences::RemoveObserver(this, DEFAULT_CSP_PREF_V3);
}
nsresult ExtensionPolicyService::Observe(nsISupports* aSubject,
@ -244,6 +252,8 @@ nsresult ExtensionPolicyService::Observe(nsISupports* aSubject,
const char* pref = converted.get();
if (!strcmp(pref, DEFAULT_CSP_PREF)) {
mDefaultCSP.SetIsVoid(true);
} else if (!strcmp(pref, DEFAULT_CSP_PREF_V3)) {
mDefaultCSPV3.SetIsVoid(true);
}
}
return NS_OK;
@ -521,6 +531,19 @@ nsresult ExtensionPolicyService::GetDefaultCSP(nsAString& aDefaultCSP) {
return NS_OK;
}
nsresult ExtensionPolicyService::GetDefaultCSPV3(nsAString& aDefaultCSP) {
if (mDefaultCSPV3.IsVoid()) {
nsresult rv = Preferences::GetString(DEFAULT_CSP_PREF_V3, mDefaultCSPV3);
if (NS_FAILED(rv)) {
mDefaultCSPV3.AssignLiteral(DEFAULT_DEFAULT_CSP_V3);
}
mDefaultCSPV3.SetIsVoid(false);
}
aDefaultCSP.Assign(mDefaultCSPV3);
return NS_OK;
}
nsresult ExtensionPolicyService::GetBaseCSP(const nsAString& aAddonId,
nsAString& aResult) {
if (WebExtensionPolicy* policy = GetByID(aAddonId)) {

View File

@ -120,6 +120,7 @@ class ExtensionPolicyService final : public nsIAddonPolicyService,
nsCOMPtr<nsIObserverService> mObs;
nsString mDefaultCSP;
nsString mDefaultCSPV3;
};
} // namespace mozilla

View File

@ -1141,13 +1141,14 @@ const FORMATS = {
},
contentSecurityPolicy(string, context) {
// Manifest V3 extension_pages allows localhost. When sandbox is
// Manifest V3 extension_pages allows localhost and WASM. When sandbox is
// implemented, or any other V3 or later directive, the flags
// logic will need to be updated.
let flags =
context.manifestVersion < 3
? Ci.nsIAddonContentPolicy.CSP_ALLOW_ANY
: Ci.nsIAddonContentPolicy.CSP_ALLOW_LOCALHOST;
: Ci.nsIAddonContentPolicy.CSP_ALLOW_LOCALHOST |
Ci.nsIAddonContentPolicy.CSP_ALLOW_WASM;
let error = contentPolicyService.validateAddonCSP(string, flags);
if (error != null) {
// The CSP validation error is not reported as part of the "choices" error message,

View File

@ -43,15 +43,15 @@ static const char kBackgroundPageHTMLEnd[] =
#define BASE_CSP_PREF_V2 "extensions.webextensions.base-content-security-policy"
#define DEFAULT_BASE_CSP_V2 \
"script-src 'self' https://* moz-extension: blob: filesystem: " \
"'unsafe-eval' 'unsafe-inline'; " \
"object-src 'self' https://* moz-extension: blob: filesystem:;"
"script-src 'self' https://* http://localhost:* http://127.0.0.1:* " \
"moz-extension: blob: filesystem: 'unsafe-eval' 'wasm-unsafe-eval' " \
"'unsafe-inline'; object-src 'self' moz-extension: blob: filesystem:;"
#define BASE_CSP_PREF_V3 \
"extensions.webextensions.base-content-security-policy.v3"
#define DEFAULT_BASE_CSP_V3 \
"script-src 'self'; object-src 'self'; " \
"style-src 'self'; worker-src 'self';"
"script-src 'self' 'wasm-unsafe-eval' http://localhost:* " \
"http://127.0.0.1:*; object-src 'self';"
static const char kRestrictedDomainPref[] =
"extensions.webextensions.restrictedDomains";
@ -196,7 +196,11 @@ WebExtensionPolicy::WebExtensionPolicy(GlobalObject& aGlobal,
InitializeBaseCSP();
if (mExtensionPageCSP.IsVoid()) {
if (mManifestVersion < 3) {
EPS().GetDefaultCSP(mExtensionPageCSP);
} else {
EPS().GetDefaultCSPV3(mExtensionPageCSP);
}
}
mWebAccessibleResources.SetCapacity(aInit.mWebAccessibleResources.Length());
@ -254,6 +258,7 @@ void WebExtensionPolicy::InitializeBaseCSP() {
}
return;
}
// Version 3 or higher.
nsresult rv = Preferences::GetString(BASE_CSP_PREF_V3, mBaseCSP);
if (NS_FAILED(rv)) {

View File

@ -75,7 +75,7 @@ add_task(async function test_policy_csp() {
policyData: {
manifestVersion: 3,
},
expectedPolicy: aps.defaultCSP,
expectedPolicy: aps.defaultCSPV3,
},
{
name: "manifest 3 version set, custom extensionPage policy",
@ -143,7 +143,7 @@ add_task(async function test_extension_csp() {
{
name: "manifest_v2 allows https protocol",
manifest: {
manifest_version: 3,
manifest_version: 2,
content_security_policy: {
extension_pages: `script-src 'self' https://*; object-src 'self'`,
},
@ -153,13 +153,23 @@ add_task(async function test_extension_csp() {
{
name: "manifest_v2 allows unsafe-eval",
manifest: {
manifest_version: 3,
manifest_version: 2,
content_security_policy: {
extension_pages: `script-src 'self' 'unsafe-eval'; object-src 'self'`,
},
},
expectedPolicy: aps.defaultCSP,
},
{
name: "manifest_v2 allows wasm-unsafe-eval",
manifest: {
manifest_version: 2,
content_security_policy: {
extension_pages: `script-src 'self' 'wasm-unsafe-eval'; object-src 'self'`,
},
},
expectedPolicy: aps.defaultCSP,
},
{
name: "manifest_v3 invalid csp results in default csp used",
manifest: {
@ -168,7 +178,7 @@ add_task(async function test_extension_csp() {
extension_pages: `script-src 'none'`,
},
},
expectedPolicy: aps.defaultCSP,
expectedPolicy: aps.defaultCSPV3,
},
{
name: "manifest_v3 forbidden protocol results in default csp used",
@ -178,7 +188,7 @@ add_task(async function test_extension_csp() {
extension_pages: `script-src 'self' https://*; object-src 'self'`,
},
},
expectedPolicy: aps.defaultCSP,
expectedPolicy: aps.defaultCSPV3,
},
{
name: "manifest_v3 forbidden eval results in default csp used",
@ -188,7 +198,7 @@ add_task(async function test_extension_csp() {
extension_pages: `script-src 'self' 'unsafe-eval'; object-src 'self'`,
},
},
expectedPolicy: aps.defaultCSP,
expectedPolicy: aps.defaultCSPV3,
},
{
name: "manifest_v3 allows localhost",
@ -210,6 +220,16 @@ add_task(async function test_extension_csp() {
},
expectedPolicy: `script-src 'self' https://127.0.0.1; object-src 'self'`,
},
{
name: "manifest_v3 allows wasm-unsafe-eval",
manifest: {
manifest_version: 3,
content_security_policy: {
extension_pages: `script-src 'self' 'wasm-unsafe-eval'; object-src 'self'`,
},
},
expectedPolicy: `script-src 'self' 'wasm-unsafe-eval'; object-src 'self'`,
},
{
name: "manifest_v2 csp",
manifest: {
@ -230,7 +250,7 @@ add_task(async function test_extension_csp() {
manifest: {
manifest_version: 3,
},
expectedPolicy: aps.defaultCSP,
expectedPolicy: aps.defaultCSPV3,
},
{
name: "manifest_v3 syntax used",

View File

@ -42,6 +42,25 @@ add_task(async function test_csp_validator_flags() {
"eval allowed"
);
checkPolicy(
"default-src 'self'; script-src 'self' 'wasm-unsafe-eval'",
0,
"\u2018script-src\u2019 directive contains a forbidden 'wasm-unsafe-eval' keyword",
"wasm disallowed"
);
checkPolicy(
"default-src 'self'; script-src 'self' 'wasm-unsafe-eval'",
flags.CSP_ALLOW_WASM,
null,
"wasm allowed"
);
checkPolicy(
"default-src 'self'; script-src 'self' 'unsafe-eval' 'wasm-unsafe-eval'",
flags.CSP_ALLOW_EVAL,
null,
"wasm and eval allowed"
);
checkPolicy(
"default-src 'self'; script-src 'self' https://example.com",
0,

View File

@ -17,10 +17,12 @@ server.registerPathHandler("/worker.js", (request, response) => {
});
const baseCSP = [];
// Keep in sync with extensions.webextensions.base-content-security-policy
baseCSP[2] = {
"object-src": ["blob:", "filesystem:", "moz-extension:", "'self'"],
"script-src": [
"'unsafe-eval'",
"'wasm-unsafe-eval'",
"'unsafe-inline'",
"blob:",
"filesystem:",
@ -31,10 +33,21 @@ baseCSP[2] = {
"'self'",
],
};
// Keep in sync with extensions.webextensions.base-content-security-policy.v3
baseCSP[3] = {
"object-src": ["'self'"],
"script-src": ["http://localhost:*", "http://127.0.0.1:*", "'self'"],
"worker-src": ["http://localhost:*", "http://127.0.0.1:*", "'self'"],
"script-src": [
"http://localhost:*",
"http://127.0.0.1:*",
"'self'",
"'wasm-unsafe-eval'",
],
"worker-src": [
"http://localhost:*",
"http://127.0.0.1:*",
"'self'",
"'wasm-unsafe-eval'",
],
};
/**
@ -54,6 +67,10 @@ async function testPolicy(manifest_version = 2, customCSP = null) {
"script-src": ["'self'"],
};
if (manifest_version < 3) {
addonCSP["script-src"].push("'wasm-unsafe-eval'");
}
let content_security_policy = null;
if (customCSP) {
@ -259,4 +276,10 @@ add_task(async function testCSP() {
"script-src": `'self'`,
"worker-src": `'self'`,
});
await testPolicy(3, {
"object-src": "'none'",
"script-src": `'self' 'wasm-unsafe-eval'`,
"worker-src": `'self' 'wasm-unsafe-eval'`,
});
});

View File

@ -0,0 +1,117 @@
/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
"use strict";
add_task(async function test_wasm_v2() {
let extension = ExtensionTestUtils.loadExtension({
background() {
let wasm = new WebAssembly.Module(
new Uint8Array([0, 0x61, 0x73, 0x6d, 0x1, 0, 0, 0])
);
browser.test.assertEq(wasm.toString(), "[object WebAssembly.Module]");
browser.test.notifyPass("finished");
},
manifest: {
manifest_version: 2,
},
});
await extension.startup();
await extension.awaitFinish("finished");
await extension.unload();
});
add_task(async function test_wasm_v2_explicit() {
let extension = ExtensionTestUtils.loadExtension({
background() {
let wasm = new WebAssembly.Module(
new Uint8Array([0, 0x61, 0x73, 0x6d, 0x1, 0, 0, 0])
);
browser.test.assertEq(wasm.toString(), "[object WebAssembly.Module]");
browser.test.notifyPass("finished");
},
manifest: {
manifest_version: 2,
content_security_policy: `object-src; script-src 'self' 'wasm-unsafe-eval'`,
},
});
await extension.startup();
await extension.awaitFinish("finished");
await extension.unload();
});
add_task(async function test_wasm_v2_blocked() {
let extension = ExtensionTestUtils.loadExtension({
background() {
browser.test.assertThrows(
() => {
new WebAssembly.Module(
new Uint8Array([0, 0x61, 0x73, 0x6d, 0x1, 0, 0, 0])
);
},
error => error instanceof WebAssembly.CompileError,
"WASM should be blocked"
);
browser.test.notifyPass("finished");
},
manifest: {
manifest_version: 2,
content_security_policy: `object-src; script-src 'self'`,
},
});
await extension.startup();
await extension.awaitFinish("finished");
await extension.unload();
});
add_task(async function test_wasm_v3() {
Services.prefs.setBoolPref("extensions.manifestV3.enabled", true);
let extension = ExtensionTestUtils.loadExtension({
background() {
browser.test.assertThrows(
() => {
new WebAssembly.Module(
new Uint8Array([0, 0x61, 0x73, 0x6d, 0x1, 0, 0, 0])
);
},
error => error instanceof WebAssembly.CompileError,
"WASM should be blocked"
);
browser.test.notifyPass("finished");
},
manifest: {
manifest_version: 3,
},
});
await extension.startup();
await extension.awaitFinish("finished");
await extension.unload();
Services.prefs.clearUserPref("extensions.manifestV3.enabled");
});
add_task(async function test_wasm_v3_allowed() {
Services.prefs.setBoolPref("extensions.manifestV3.enabled", true);
let extension = ExtensionTestUtils.loadExtension({
background() {
let wasm = new WebAssembly.Module(
new Uint8Array([0, 0x61, 0x73, 0x6d, 0x1, 0, 0, 0])
);
browser.test.assertEq(wasm.toString(), "[object WebAssembly.Module]");
browser.test.notifyPass("finished");
},
manifest: {
manifest_version: 3,
content_security_policy: {
extension_pages: `script-src 'self' 'wasm-unsafe-eval'; object-src 'self'`,
},
},
});
await extension.startup();
await extension.awaitFinish("finished");
await extension.unload();
Services.prefs.clearUserPref("extensions.manifestV3.enabled");
});

View File

@ -269,6 +269,7 @@ skip-if =
os == "linux" && fission # Bug 1762638
[test_ext_userScripts_telemetry.js]
skip-if = os == "android" # Bug 1700482
[test_ext_wasm.js]
[test_ext_webRequest_auth.js]
skip-if = os == "android" && debug
[test_ext_webRequest_cached.js]

View File

@ -265,6 +265,12 @@ class CSPValidator final : public nsCSPSrcVisitor {
case CSP_NONE:
case CSP_SELF:
return true;
case CSP_WASM_UNSAFE_EVAL:
if (mPermittedPolicy & nsIAddonContentPolicy::CSP_ALLOW_WASM) {
return true;
}
// fall through to also check CSP_ALLOW_EVAL
[[fallthrough]];
case CSP_UNSAFE_EVAL:
if (mPermittedPolicy & nsIAddonContentPolicy::CSP_ALLOW_EVAL) {
return true;