Bug 1594234 manifest v3 content security validation improvements r=robwu,geckoview-reviewers,agi

This patch adds CSP validation for manifest v3 changes when parsing the addon manifest.

Differential Revision: https://phabricator.services.mozilla.com/D100720
This commit is contained in:
Shane Caraveo 2021-01-19 19:42:41 +00:00
parent 4a14410028
commit 98c9307c72
11 changed files with 425 additions and 57 deletions

View File

@ -68,11 +68,6 @@ pref("extensions.startupScanScopes", 0);
pref("extensions.geckoProfiler.acceptedExtensionIds", "geckoprofiler@mozilla.com,quantum-foxfooding@mozilla.com,raptor@mozilla.org");
// Add-on content security policies.
pref("extensions.webextensions.base-content-security-policy", "script-src 'self' https://* moz-extension: blob: filesystem: 'unsafe-eval' 'unsafe-inline'; object-src 'self' https://* moz-extension: blob: filesystem:;");
pref("extensions.webextensions.base-content-security-policy.v3", "script-src 'self'; object-src 'self'; style-src 'self'; worker-src 'self';");
pref("extensions.webextensions.default-content-security-policy", "script-src 'self'; object-src 'self';");
pref("extensions.webextensions.remote", true);
pref("extensions.webextensions.background-delayed-startup", true);

View File

@ -75,10 +75,21 @@ interface nsIAddonPolicyService : nsISupports
[scriptable, uuid(7a4fe60b-9131-45f5-83f3-dc63b5d71a5d)]
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.
*/
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);
/**
* Checks a custom content security policy string, to ensure that it meets
* minimum security requirements. Returns null for valid policies, or a
* string describing the error for invalid policies.
*/
AString validateAddonCSP(in AString aPolicyString);
AString validateAddonCSP(in AString aPolicyString, in unsigned long aPermittedPolicy);
};

View File

@ -196,11 +196,6 @@ pref("extensions.installDistroAddons", false);
pref("extensions.webextPermissionPrompts", true);
pref("extensions.webextOptionalPermissionPrompts", true);
// Add-on content security policies.
pref("extensions.webextensions.base-content-security-policy", "script-src 'self' https://* moz-extension: blob: filesystem: 'unsafe-eval' 'unsafe-inline'; object-src 'self' https://* moz-extension: blob: filesystem:;");
pref("extensions.webextensions.base-content-security-policy.v3", "script-src 'self'; object-src 'self'; style-src 'self'; worker-src 'self';");
pref("extensions.webextensions.default-content-security-policy", "script-src 'self'; object-src 'self';");
pref("extensions.webextensions.background-delayed-startup", true);
pref("extensions.experiments.enabled", false);

View File

@ -3907,6 +3907,12 @@ pref("extensions.webcompat-reporter.newIssueEndpoint", "https://webcompat.com/is
pref("extensions.webcompat-reporter.enabled", false);
#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("network.buffer.cache.count", 24);
pref("network.buffer.cache.size", 32768);

View File

@ -883,6 +883,7 @@ class ExtensionData {
this.manifestWarning(error);
},
preprocessors: {},
manifestVersion: this.manifest.manifest_version,
};
if (this.fluentL10n || this.localeData) {

View File

@ -388,7 +388,7 @@ class Context {
}
}
let props = ["preprocessors", "isChromeCompat"];
let props = ["preprocessors", "isChromeCompat", "manifestVersion"];
for (let prop of props) {
if (prop in params) {
if (prop in this && typeof this[prop] == "object") {
@ -1099,7 +1099,14 @@ const FORMATS = {
},
contentSecurityPolicy(string, context) {
let error = contentPolicyService.validateAddonCSP(string);
// Manifest V3 extension_pages allows localhost. 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;
let error = contentPolicyService.validateAddonCSP(string, flags);
if (error != null) {
// The CSP validation error is not reported as part of the "choices" error message,
// we log the CSP validation error explicitly here to make it easier for the addon developers

View File

@ -140,6 +140,26 @@ add_task(async function test_extension_csp() {
},
expectedPolicy: aps.defaultCSP,
},
{
name: "manifest_v2 allows https protocol",
manifest: {
manifest_version: 3,
content_security_policy: {
extension_pages: `script-src 'self' https://*; object-src 'self'`,
},
},
expectedPolicy: aps.defaultCSP,
},
{
name: "manifest_v2 allows unsafe-eval",
manifest: {
manifest_version: 3,
content_security_policy: {
extension_pages: `script-src 'self' 'unsafe-eval'; object-src 'self'`,
},
},
expectedPolicy: aps.defaultCSP,
},
{
name: "manifest_v3 invalid csp results in default csp used",
manifest: {
@ -150,6 +170,46 @@ add_task(async function test_extension_csp() {
},
expectedPolicy: aps.defaultCSP,
},
{
name: "manifest_v3 forbidden protocol results in default csp used",
manifest: {
manifest_version: 3,
content_security_policy: {
extension_pages: `script-src 'self' https://*; object-src 'self'`,
},
},
expectedPolicy: aps.defaultCSP,
},
{
name: "manifest_v3 forbidden eval results in default csp used",
manifest: {
manifest_version: 3,
content_security_policy: {
extension_pages: `script-src 'self' 'unsafe-eval'; object-src 'self'`,
},
},
expectedPolicy: aps.defaultCSP,
},
{
name: "manifest_v3 allows localhost",
manifest: {
manifest_version: 3,
content_security_policy: {
extension_pages: `script-src 'self' https://localhost; object-src 'self'`,
},
},
expectedPolicy: `script-src 'self' https://localhost; object-src 'self'`,
},
{
name: "manifest_v3 allows 127.0.0.1",
manifest: {
manifest_version: 3,
content_security_policy: {
extension_pages: `script-src 'self' https://127.0.0.1; object-src 'self'`,
},
},
expectedPolicy: `script-src 'self' https://127.0.0.1; object-src 'self'`,
},
{
name: "manifest_v2 csp",
manifest: {

View File

@ -6,11 +6,64 @@ const cps = Cc["@mozilla.org/addons/content-policy;1"].getService(
Ci.nsIAddonContentPolicy
);
add_task(async function test_csp_validator_flags() {
let checkPolicy = (policy, flags, expectedResult, message = null) => {
info(`Checking policy: ${policy}`);
let result = cps.validateAddonCSP(policy, flags);
equal(result, expectedResult);
};
let flags = Ci.nsIAddonContentPolicy;
checkPolicy(
"default-src 'self'; script-src 'self' http://localhost",
0,
"\u2018script-src\u2019 directive contains a forbidden http: protocol source",
"localhost disallowed"
);
checkPolicy(
"default-src 'self'; script-src 'self' http://localhost",
flags.CSP_ALLOW_LOCALHOST,
null,
"localhost allowed"
);
checkPolicy(
"default-src 'self'; script-src 'self' 'unsafe-eval'",
0,
"\u2018script-src\u2019 directive contains a forbidden 'unsafe-eval' keyword",
"eval disallowed"
);
checkPolicy(
"default-src 'self'; script-src 'self' 'unsafe-eval'",
flags.CSP_ALLOW_EVAL,
null,
"eval allowed"
);
checkPolicy(
"default-src 'self'; script-src 'self' https://example.com",
0,
"\u2018script-src\u2019 directive contains a forbidden https: protocol source",
"remote disallowed"
);
checkPolicy(
"default-src 'self'; script-src 'self' https://example.com",
flags.CSP_ALLOW_REMOTE,
null,
"remote allowed"
);
});
add_task(async function test_csp_validator() {
let checkPolicy = (policy, expectedResult, message = null) => {
info(`Checking policy: ${policy}`);
let result = cps.validateAddonCSP(policy);
let result = cps.validateAddonCSP(
policy,
Ci.nsIAddonContentPolicy.CSP_ALLOW_ANY
);
equal(result, expectedResult);
};
@ -82,6 +135,16 @@ add_task(async function test_csp_validator() {
"\u2018script-src\u2019 directive contains a forbidden 'unsafe-inline' keyword"
);
// Localhost is always valid
for (let src of [
"http://localhost",
"https://localhost",
"http://127.0.0.1",
"https://127.0.0.1",
]) {
checkPolicy(`script-src 'self' ${src}; object-src 'none';`, null);
}
let directives = ["script-src", "object-src"];
for (let [directive, other] of [directives, directives.slice().reverse()]) {
@ -92,17 +155,135 @@ add_task(async function test_csp_validator() {
);
}
checkPolicy(
`${directive} 'self' https:; ${other} 'self';`,
`https: protocol requires a host in \u2018${directive}\u2019 directives`
);
for (let protocol of ["http", "https"]) {
checkPolicy(
`${directive} 'self' ${protocol}:; ${other} 'self';`,
`${protocol}: protocol requires a host in \u2018${directive}\u2019 directives`
);
}
checkPolicy(
`${directive} 'self' http://example.com; ${other} 'self';`,
`\u2018${directive}\u2019 directive contains a forbidden http: protocol source`
);
for (let protocol of ["http", "ftp", "meh"]) {
for (let protocol of ["ftp", "meh"]) {
checkPolicy(
`${directive} 'self' ${protocol}:; ${other} 'self';`,
`\u2018${directive}\u2019 directive contains a forbidden ${protocol}: protocol source`
);
}
checkPolicy(
`${directive} 'self' 'nonce-01234'; ${other} 'self';`,
`\u2018${directive}\u2019 directive contains a forbidden 'nonce-*' keyword`
);
}
});
add_task(async function test_csp_validator_extension_pages() {
let checkPolicy = (policy, expectedResult, message = null) => {
info(`Checking policy: ${policy}`);
let result = cps.validateAddonCSP(
policy,
Ci.nsIAddonContentPolicy.CSP_ALLOW_LOCALHOST
);
equal(result, expectedResult);
};
checkPolicy("script-src 'self'; object-src 'self';", null);
checkPolicy("script-src 'self'; object-src 'self'; worker-src 'none'", null);
checkPolicy("script-src 'self'; object-src 'none'; worker-src 'self'", null);
let hash =
"'sha256-NjZhMDQ1YjQ1MjEwMmM1OWQ4NDBlYzA5N2Q1OWQ5NDY3ZTEzYTNmMzRmNjQ5NGU1MzlmZmQzMmMxYmIzNWYxOCAgLQo='";
checkPolicy(
`script-src 'self' moz-extension://09abcdef blob: filesystem: ${hash}; ` +
`object-src 'self' moz-extension://09abcdef blob: filesystem: ${hash}`,
null
);
for (let policy of ["", "object-src 'none';", "worker-src 'none';"]) {
checkPolicy(
policy,
"Policy is missing a required \u2018script-src\u2019 directive"
);
}
checkPolicy(
"default-src 'self'",
null,
"A valid default-src should count as a valid script-src or object-src"
);
for (let directive of ["script-src", "object-src", "worker-src"]) {
checkPolicy(
`default-src 'self'; ${directive} 'self'`,
null,
`A valid default-src should count as a valid ${directive}`
);
checkPolicy(
`default-src 'self'; ${directive} http://example.com`,
`\u2018${directive}\u2019 directive contains a forbidden http: protocol source`,
`A valid default-src should not allow an invalid ${directive} directive`
);
}
checkPolicy(
"script-src 'self';",
"Policy is missing a required \u2018object-src\u2019 directive"
);
checkPolicy(
"script-src 'none'; object-src 'none'",
"\u2018script-src\u2019 must include the source 'self'"
);
checkPolicy("script-src 'self'; object-src 'none';", null);
checkPolicy(
"script-src 'self' 'unsafe-inline'; object-src 'self';",
"\u2018script-src\u2019 directive contains a forbidden 'unsafe-inline' keyword"
);
checkPolicy(
"script-src 'self' 'unsafe-eval'; object-src 'self';",
"\u2018script-src\u2019 directive contains a forbidden 'unsafe-eval' keyword"
);
// Localhost is always valid
for (let src of [
"http://localhost",
"https://localhost",
"http://127.0.0.1",
"https://127.0.0.1",
]) {
checkPolicy(`script-src 'self' ${src}; object-src 'none';`, null);
}
let directives = ["script-src", "object-src"];
for (let [directive, other] of [directives, directives.slice().reverse()]) {
for (let protocol of ["http", "https"]) {
checkPolicy(
`${directive} 'self' ${protocol}:; ${other} 'self';`,
`${protocol}: protocol requires a host in \u2018${directive}\u2019 directives`
);
}
checkPolicy(
`${directive} 'self' https://example.com; ${other} 'self';`,
`\u2018${directive}\u2019 directive contains a forbidden https: protocol source`
);
checkPolicy(
`${directive} 'self' http://example.com; ${other} 'self';`,
`\u2018${directive}\u2019 directive contains a forbidden http: protocol source`
);
for (let protocol of ["ftp", "meh"]) {
checkPolicy(
`${directive} 'self' ${protocol}:; ${other} 'self';`,
`\u2018${directive}\u2019 directive contains a forbidden ${protocol}: protocol source`

View File

@ -1,5 +1,7 @@
"use strict";
Services.prefs.setBoolPref("extensions.manifestV3.enabled", true);
const server = createHttpServer({ hosts: ["example.com"] });
server.registerPathHandler("/dummy", (request, response) => {
@ -8,36 +10,45 @@ server.registerPathHandler("/dummy", (request, response) => {
response.write("<!DOCTYPE html><html></html>");
});
server.registerPathHandler("/worker.js", (request, response) => {
response.setStatusLine(request.httpVersion, 200, "OK");
response.setHeader("Content-Type", "application/javascript", false);
response.write("let x = true;");
});
const baseCSP = [];
baseCSP[2] = {
"object-src": ["blob:", "filesystem:", "moz-extension:", "'self'"],
"script-src": [
"'unsafe-eval'",
"'unsafe-inline'",
"blob:",
"filesystem:",
"http://localhost:*",
"http://127.0.0.1:*",
"https://*",
"moz-extension:",
"'self'",
],
};
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'"],
};
/**
* Tests that content security policies for an add-on are actually applied to *
* documents that belong to it. This tests both the base policies and add-on
* specific policies, and ensures that the parsed policies applied to the
* document's principal match what was specified in the policy string.
*
* @param {number} [manifest_version]
* @param {object} [customCSP]
*/
async function testPolicy(customCSP = null) {
async function testPolicy(manifest_version = 2, customCSP = null) {
let baseURL;
let baseCSP = {
"object-src": [
"blob:",
"filesystem:",
"https://*",
"moz-extension:",
"'self'",
],
"script-src": [
"'unsafe-eval'",
"'unsafe-inline'",
"blob:",
"filesystem:",
"https://*",
"moz-extension:",
"'self'",
],
};
let addonCSP = {
"object-src": ["'self'"],
"script-src": ["'self'"],
@ -56,8 +67,13 @@ async function testPolicy(customCSP = null) {
}
function checkSource(name, policy, expected) {
// fallback to script-src when comparing worker-src if policy does not include worker-src
let policySrc =
name != "worker-src" || policy[name]
? policy[name]
: policy["script-src"];
equal(
JSON.stringify(policy[name].sort()),
JSON.stringify(policySrc.sort()),
JSON.stringify(expected[name].sort()),
`Expected value for ${name}`
);
@ -67,16 +83,19 @@ async function testPolicy(customCSP = null) {
let policies = csp["csp-policies"];
info(`Base policy for ${location}`);
let base = baseCSP[manifest_version];
equal(policies[0]["report-only"], false, "Policy is not report-only");
checkSource("object-src", policies[0], baseCSP);
checkSource("script-src", policies[0], baseCSP);
for (let key in base) {
checkSource(key, policies[0], base);
}
info(`Add-on policy for ${location}`);
equal(policies[1]["report-only"], false, "Policy is not report-only");
checkSource("object-src", policies[1], addonCSP);
checkSource("script-src", policies[1], addonCSP);
for (let key in addonCSP) {
checkSource(key, policies[1], addonCSP);
}
}
function background() {
@ -90,6 +109,25 @@ async function testPolicy(customCSP = null) {
function tabScript() {
browser.test.sendMessage("tab-csp", window.getCSP());
const worker = new Worker("worker.js");
worker.onmessage = event => {
browser.test.sendMessage("worker-csp", event.data);
};
worker.postMessage({});
}
function testWorker(port) {
this.onmessage = () => {
try {
// eslint-disable-next-line no-undef
importScripts(`http://127.0.0.1:${port}/worker.js`);
postMessage({ loaded: true });
} catch (e) {
postMessage({ loaded: false });
}
};
}
let extension = ExtensionTestUtils.loadExtension({
@ -102,9 +140,11 @@ async function testPolicy(customCSP = null) {
"tab.js": tabScript,
"content.html": `<html><head><meta charset="utf-8"></head></html>`,
"worker.js": `(${testWorker})(${server.identity.primaryPort})`,
},
manifest: {
manifest_version,
content_security_policy,
web_accessible_resources: ["content.html", "tab.html"],
@ -169,6 +209,10 @@ async function testPolicy(customCSP = null) {
checkCSP(contentCSP, "content frame");
let workerCSP = await extension.awaitMessage("worker-csp");
// TODO BUG 1685627: This test should fail if localhost is not in the csp.
ok(workerCSP.loaded, "worker loaded");
await contentPage.close();
await tabPage.close();
@ -178,18 +222,30 @@ async function testPolicy(customCSP = null) {
}
add_task(async function testCSP() {
await testPolicy(null);
await testPolicy(2, null);
let hash =
"'sha256-NjZhMDQ1YjQ1MjEwMmM1OWQ4NDBlYzA5N2Q1OWQ5NDY3ZTEzYTNmMzRmNjQ5NGU1MzlmZmQzMmMxYmIzNWYxOCAgLQo='";
await testPolicy({
await testPolicy(2, {
"object-src": "'self' https://*.example.com",
"script-src": `'self' https://*.example.com 'unsafe-eval' ${hash}`,
});
await testPolicy({
await testPolicy(2, {
"object-src": "'none'",
"script-src": `'self'`,
});
await testPolicy(3, {
"object-src": "'self' http://localhost",
"script-src": `'self' http://localhost:123 ${hash}`,
"worker-src": `'self' http://127.0.0.1:*`,
});
await testPolicy(3, {
"object-src": "'none'",
"script-src": `'self'`,
"worker-src": `'self'`,
});
});

View File

@ -48,11 +48,16 @@ add_task(async function test_manifest_csp_v3() {
},
});
equal(normalized.error, undefined, "Should not have an error");
equal(normalized.errors.length, 0, "Should not have warnings");
Assert.deepEqual(
normalized.errors,
[
"Error processing content_security_policy.extension_pages: script-src directive contains a forbidden 'unsafe-eval' keyword",
],
"Should have the expected warning"
);
equal(
normalized.value.content_security_policy.extension_pages,
"script-src 'self' 'unsafe-eval'; object-src 'none'",
null,
"Should have the expected policy string"
);

View File

@ -149,7 +149,8 @@ AddonContentPolicy::ShouldProcess(nsIURI* aContentLocation,
static const char* allowedSchemes[] = {"blob", "filesystem", nullptr};
static const char* allowedHostSchemes[] = {"https", "moz-extension", nullptr};
static const char* allowedHostSchemes[] = {"http", "https", "moz-extension",
nullptr};
/**
* Validates a CSP directive to ensure that it is sufficiently stringent.
@ -167,13 +168,21 @@ static const char* allowedHostSchemes[] = {"https", "moz-extension", nullptr};
*
* - No keyword sources are allowed other than 'none', 'self', 'unsafe-eval',
* and hash sources.
*
* Manifest V3 limits CSP for extension_pages, the script-src, object-src, and
* worker-src directives may only be the following:
* - self
* - none
* - Any localhost source, (http://localhost, http://127.0.0.1, or any port
* on those domains)
*/
class CSPValidator final : public nsCSPSrcVisitor {
public:
CSPValidator(nsAString& aURL, CSPDirective aDirective,
bool aDirectiveRequired = true)
bool aDirectiveRequired = true, uint32_t aPermittedPolicy = 0)
: mURL(aURL),
mDirective(CSP_CSPDirectiveToString(aDirective)),
mPermittedPolicy(aPermittedPolicy),
mFoundSelf(false) {
// Start with the default error message for a missing directive, since no
// visitors will be called if the directive isn't present.
@ -206,7 +215,24 @@ class CSPValidator final : public nsCSPSrcVisitor {
src.getScheme(scheme);
src.getHost(host);
if (scheme.LowerCaseEqualsLiteral("http")) {
// Allow localhost on http
if (mPermittedPolicy & nsIAddonContentPolicy::CSP_ALLOW_LOCALHOST &&
HostIsLocal(host)) {
return true;
}
FormatError("csp.error.illegal-protocol", scheme);
return false;
}
if (scheme.LowerCaseEqualsLiteral("https")) {
if (mPermittedPolicy & nsIAddonContentPolicy::CSP_ALLOW_LOCALHOST &&
HostIsLocal(host)) {
return true;
}
if (!(mPermittedPolicy & nsIAddonContentPolicy::CSP_ALLOW_REMOTE)) {
FormatError("csp.error.illegal-protocol", scheme);
return false;
}
if (!HostIsAllowed(host)) {
FormatError("csp.error.illegal-host-wildcard", scheme);
return false;
@ -237,9 +263,13 @@ class CSPValidator final : public nsCSPSrcVisitor {
switch (src.getKeyword()) {
case CSP_NONE:
case CSP_SELF:
case CSP_UNSAFE_EVAL:
return true;
case CSP_UNSAFE_EVAL:
if (mPermittedPolicy & nsIAddonContentPolicy::CSP_ALLOW_EVAL) {
return true;
}
// fall through and produce an illegal-keyword error.
[[fallthrough]];
default:
FormatError(
"csp.error.illegal-keyword",
@ -272,6 +302,9 @@ class CSPValidator final : public nsCSPSrcVisitor {
private:
// Validators
bool HostIsLocal(nsAString& host) {
return host.EqualsLiteral("localhost") || host.EqualsLiteral("127.0.0.1");
}
bool HostIsAllowed(nsAString& host) {
if (host.First() == '*') {
@ -342,6 +375,7 @@ class CSPValidator final : public nsCSPSrcVisitor {
NS_ConvertASCIItoUTF16 mDirective;
nsString mError;
uint32_t mPermittedPolicy;
bool mFoundSelf;
};
@ -356,6 +390,7 @@ class CSPValidator final : public nsCSPSrcVisitor {
*/
NS_IMETHODIMP
AddonContentPolicy::ValidateAddonCSP(const nsAString& aPolicyString,
uint32_t aPermittedPolicy,
nsAString& aResult) {
nsresult rv;
@ -393,12 +428,14 @@ AddonContentPolicy::ValidateAddonCSP(const nsAString& aPolicyString,
const nsCSPPolicy* policy = csp->GetPolicy(0);
if (!policy) {
CSPValidator validator(url, nsIContentSecurityPolicy::SCRIPT_SRC_DIRECTIVE);
CSPValidator validator(url, nsIContentSecurityPolicy::SCRIPT_SRC_DIRECTIVE,
true, aPermittedPolicy);
aResult.Assign(validator.GetError());
return NS_OK;
}
bool haveValidDefaultSrc = false;
bool hasValidScriptSrc = false;
{
CSPDirective directive = nsIContentSecurityPolicy::DEFAULT_SRC_DIRECTIVE;
CSPValidator validator(url, directive);
@ -409,7 +446,8 @@ AddonContentPolicy::ValidateAddonCSP(const nsAString& aPolicyString,
aResult.SetIsVoid(true);
{
CSPDirective directive = nsIContentSecurityPolicy::SCRIPT_SRC_DIRECTIVE;
CSPValidator validator(url, directive, !haveValidDefaultSrc);
CSPValidator validator(url, directive, !haveValidDefaultSrc,
aPermittedPolicy);
if (!policy->visitDirectiveSrcs(directive, &validator)) {
aResult.Assign(validator.GetError());
@ -417,11 +455,24 @@ AddonContentPolicy::ValidateAddonCSP(const nsAString& aPolicyString,
validator.FormatError("csp.error.missing-source", u"'self'"_ns);
aResult.Assign(validator.GetError());
}
hasValidScriptSrc = true;
}
if (aResult.IsVoid()) {
CSPDirective directive = nsIContentSecurityPolicy::OBJECT_SRC_DIRECTIVE;
CSPValidator validator(url, directive, !haveValidDefaultSrc);
CSPValidator validator(url, directive, !haveValidDefaultSrc,
aPermittedPolicy);
if (!policy->visitDirectiveSrcs(directive, &validator)) {
aResult.Assign(validator.GetError());
}
}
if (aResult.IsVoid()) {
CSPDirective directive = nsIContentSecurityPolicy::WORKER_SRC_DIRECTIVE;
CSPValidator validator(url, directive,
!haveValidDefaultSrc && !hasValidScriptSrc,
aPermittedPolicy);
if (!policy->visitDirectiveSrcs(directive, &validator)) {
aResult.Assign(validator.GetError());