diff options
author | wolfbeast <mcwerewolf@gmail.com> | 2018-03-03 11:21:43 +0100 |
---|---|---|
committer | wolfbeast <mcwerewolf@gmail.com> | 2018-03-03 11:22:15 +0100 |
commit | c3039dadd95f5487e84311a9719604fa901aacd7 (patch) | |
tree | 3168b0b2d41184b89f894821e25ca258d88d6af4 /dom | |
parent | 8891f99913d9054c363c0266cf4ee9718cbf474e (diff) | |
download | uxp-c3039dadd95f5487e84311a9719604fa901aacd7.tar.gz |
Add support for CSP v3 "worker-src" directive
Diffstat (limited to 'dom')
23 files changed, 469 insertions, 47 deletions
diff --git a/dom/interfaces/security/nsIContentSecurityPolicy.idl b/dom/interfaces/security/nsIContentSecurityPolicy.idl index 51ca46f2a8..da4297f338 100644 --- a/dom/interfaces/security/nsIContentSecurityPolicy.idl +++ b/dom/interfaces/security/nsIContentSecurityPolicy.idl @@ -61,6 +61,7 @@ interface nsIContentSecurityPolicy : nsISerializable const unsigned short BLOCK_ALL_MIXED_CONTENT = 19; const unsigned short REQUIRE_SRI_FOR = 20; const unsigned short SANDBOX_DIRECTIVE = 21; + const unsigned short WORKER_SRC_DIRECTIVE = 22; /** * Accessor method for a read-only string version of the policy at a given diff --git a/dom/locales/en-US/chrome/security/csp.properties b/dom/locales/en-US/chrome/security/csp.properties index 4124ef8aac..4c4054cee8 100644 --- a/dom/locales/en-US/chrome/security/csp.properties +++ b/dom/locales/en-US/chrome/security/csp.properties @@ -112,9 +112,10 @@ couldntParsePort = Couldn’t parse port in %1$S # LOCALIZATION NOTE (duplicateDirective): # %1$S is the name of the duplicate directive duplicateDirective = Duplicate %1$S directives detected. All but the first instance will be ignored. -# LOCALIZATION NOTE (deprecatedDirective): -# %1$S is the name of the deprecated directive, %2$S is the name of the replacement. -deprecatedDirective = Directive ‘%1$S’ has been deprecated. Please use directive ‘%2$S’ instead. +# LOCALIZATION NOTE (deprecatedChildSrcDirective): +# %1$S is the value of the deprecated directive. +# Do not localize: worker-src, frame-src +deprecatedChildSrcDirective = Directive ‘%1$S’ has been deprecated. Please use directive ‘worker-src’ to control workers, or directive ‘frame-src’ to control frames respectively. # LOCALIZATION NOTE (couldntParseInvalidSandboxFlag): # %1$S is the option that could not be understood couldntParseInvalidSandboxFlag = Couldn’t parse invalid sandbox flag ‘%1$S’ diff --git a/dom/security/nsCSPParser.cpp b/dom/security/nsCSPParser.cpp index 86aa4e0018..a0eba69188 100644 --- a/dom/security/nsCSPParser.cpp +++ b/dom/security/nsCSPParser.cpp @@ -136,6 +136,8 @@ nsCSPParser::nsCSPParser(cspTokens& aTokens, , mUnsafeInlineKeywordSrc(nullptr) , mChildSrc(nullptr) , mFrameSrc(nullptr) + , mWorkerSrc(nullptr) + , mScriptSrc(nullptr) , mParsingFrameAncestorsDir(false) , mTokens(aTokens) , mSelfURI(aSelfURI) @@ -1099,21 +1101,37 @@ nsCSPParser::directiveName() return new nsUpgradeInsecureDirective(CSP_StringToCSPDirective(mCurToken)); } - // child-src has it's own class to handle frame-src if necessary + // child-src by itself is deprecatd but will be enforced + // * for workers (if worker-src is not explicitly specified) + // * for frames (if frame-src is not explicitly specified) if (CSP_IsDirective(mCurToken, nsIContentSecurityPolicy::CHILD_SRC_DIRECTIVE)) { + const char16_t* params[] = { mCurToken.get() }; + logWarningErrorToConsole(nsIScriptError::warningFlag, + "deprecatedChildSrcDirective", + params, ArrayLength(params)); mChildSrc = new nsCSPChildSrcDirective(CSP_StringToCSPDirective(mCurToken)); return mChildSrc; } - // if we have a frame-src, cache it so we can decide whether to use child-src + // if we have a frame-src, cache it so we can discard child-src for frames if (CSP_IsDirective(mCurToken, nsIContentSecurityPolicy::FRAME_SRC_DIRECTIVE)) { - const char16_t* params[] = { mCurToken.get(), NS_LITERAL_STRING("child-src").get() }; - logWarningErrorToConsole(nsIScriptError::warningFlag, "deprecatedDirective", - params, ArrayLength(params)); mFrameSrc = new nsCSPDirective(CSP_StringToCSPDirective(mCurToken)); return mFrameSrc; } + // if we have a worker-src, cache it so we can discard child-src for workers + if (CSP_IsDirective(mCurToken, nsIContentSecurityPolicy::WORKER_SRC_DIRECTIVE)) { + mWorkerSrc = new nsCSPDirective(CSP_StringToCSPDirective(mCurToken)); + return mWorkerSrc; + } + + // if we have a script-src, cache it as a fallback for worker-src + // in case child-src is not present + if (CSP_IsDirective(mCurToken, nsIContentSecurityPolicy::SCRIPT_SRC_DIRECTIVE)) { + mScriptSrc = new nsCSPScriptSrcDirective(CSP_StringToCSPDirective(mCurToken)); + return mScriptSrc; + } + if (CSP_IsDirective(mCurToken, nsIContentSecurityPolicy::REQUIRE_SRI_FOR)) { return new nsRequireSRIForDirective(CSP_StringToCSPDirective(mCurToken)); } @@ -1290,9 +1308,22 @@ nsCSPParser::policy() directive(); } - if (mChildSrc && !mFrameSrc) { - // if we have a child-src, it handles frame-src too, unless frame-src is set - mChildSrc->setHandleFrameSrc(); + if (mChildSrc) { + if (!mFrameSrc) { + // if frame-src is specified explicitly for that policy than child-src should + // not restrict frames; if not, than child-src needs to restrict frames. + mChildSrc->setRestrictFrames(); + } + if (!mWorkerSrc) { + // if worker-src is specified explicitly for that policy than child-src should + // not restrict workers; if not, than child-src needs to restrict workers. + mChildSrc->setRestrictWorkers(); + } + } + // if script-src is specified, but not worker-src and also no child-src, then + // script-src has to govern workers. + if (mScriptSrc && !mWorkerSrc && !mChildSrc) { + mScriptSrc->setRestrictWorkers(); } return mPolicy; diff --git a/dom/security/nsCSPParser.h b/dom/security/nsCSPParser.h index 1bfc56c65a..d500a1c18d 100644 --- a/dom/security/nsCSPParser.h +++ b/dom/security/nsCSPParser.h @@ -243,14 +243,17 @@ class nsCSPParser { bool mStrictDynamic; // false, if 'strict-dynamic' is not defined nsCSPKeywordSrc* mUnsafeInlineKeywordSrc; // null, otherwise invlidate() - // cache variables for child-src and frame-src directive handling. - // frame-src is deprecated in favor of child-src, however if we - // see a frame-src directive, it takes precedence for frames and iframes. - // At the end of parsing, if we have a child-src directive, we need to - // decide whether it will handle frames, or if there is a frame-src we - // should honor instead. - nsCSPChildSrcDirective* mChildSrc; - nsCSPDirective* mFrameSrc; + // cache variables for child-src, frame-src and worker-src handling; + // in CSP 3 child-src is deprecated. For backwards compatibility + // child-src needs to restrict: + // (*) frames, in case frame-src is not expicitly specified + // (*) workers, in case worker-src is not expicitly specified + // If neither worker-src, nor child-src is present, then script-src + // needs to govern workers. + nsCSPChildSrcDirective* mChildSrc; + nsCSPDirective* mFrameSrc; + nsCSPDirective* mWorkerSrc; + nsCSPScriptSrcDirective* mScriptSrc; // cache variable to let nsCSPHostSrc know that it's within // the frame-ancestors directive. diff --git a/dom/security/nsCSPUtils.cpp b/dom/security/nsCSPUtils.cpp index a5f683b018..49832f8f4a 100644 --- a/dom/security/nsCSPUtils.cpp +++ b/dom/security/nsCSPUtils.cpp @@ -230,7 +230,7 @@ CSP_ContentTypeToDirective(nsContentPolicyType aType) case nsIContentPolicy::TYPE_INTERNAL_WORKER: case nsIContentPolicy::TYPE_INTERNAL_SHARED_WORKER: case nsIContentPolicy::TYPE_INTERNAL_SERVICE_WORKER: - return nsIContentSecurityPolicy::CHILD_SRC_DIRECTIVE; + return nsIContentSecurityPolicy::WORKER_SRC_DIRECTIVE; case nsIContentPolicy::TYPE_SUBDOCUMENT: return nsIContentSecurityPolicy::FRAME_SRC_DIRECTIVE; @@ -1184,6 +1184,11 @@ nsCSPDirective::toDomCSPStruct(mozilla::dom::CSP& outCSP) const outCSP.mSandbox.Value() = mozilla::Move(srcs); return; + case nsIContentSecurityPolicy::WORKER_SRC_DIRECTIVE: + outCSP.mWorker_src.Construct(); + outCSP.mWorker_src.Value() = mozilla::Move(srcs); + return; + // REFERRER_DIRECTIVE and REQUIRE_SRI_FOR are handled in nsCSPPolicy::toDomCSPStruct() default: @@ -1236,7 +1241,8 @@ bool nsCSPDirective::equals(CSPDirective aDirective) const nsCSPChildSrcDirective::nsCSPChildSrcDirective(CSPDirective aDirective) : nsCSPDirective(aDirective) - , mHandleFrameSrc(false) + , mRestrictFrames(false) + , mRestrictWorkers(false) { } @@ -1244,30 +1250,58 @@ nsCSPChildSrcDirective::~nsCSPChildSrcDirective() { } -void nsCSPChildSrcDirective::setHandleFrameSrc() -{ - mHandleFrameSrc = true; -} - bool nsCSPChildSrcDirective::restrictsContentType(nsContentPolicyType aContentType) const { if (aContentType == nsIContentPolicy::TYPE_SUBDOCUMENT) { - return mHandleFrameSrc; + return mRestrictFrames; } - - return (aContentType == nsIContentPolicy::TYPE_INTERNAL_WORKER - || aContentType == nsIContentPolicy::TYPE_INTERNAL_SHARED_WORKER - || aContentType == nsIContentPolicy::TYPE_INTERNAL_SERVICE_WORKER - ); + if (aContentType == nsIContentPolicy::TYPE_INTERNAL_WORKER || + aContentType == nsIContentPolicy::TYPE_INTERNAL_SHARED_WORKER || + aContentType == nsIContentPolicy::TYPE_INTERNAL_SERVICE_WORKER) { + return mRestrictWorkers; + } + return false; } bool nsCSPChildSrcDirective::equals(CSPDirective aDirective) const { if (aDirective == nsIContentSecurityPolicy::FRAME_SRC_DIRECTIVE) { - return mHandleFrameSrc; + return mRestrictFrames; + } + if (aDirective == nsIContentSecurityPolicy::WORKER_SRC_DIRECTIVE) { + return mRestrictWorkers; } + return (mDirective == aDirective); +} - return (aDirective == nsIContentSecurityPolicy::CHILD_SRC_DIRECTIVE); +/* =============== nsCSPScriptSrcDirective ============= */ + +nsCSPScriptSrcDirective::nsCSPScriptSrcDirective(CSPDirective aDirective) + : nsCSPDirective(aDirective) + , mRestrictWorkers(false) +{ +} + +nsCSPScriptSrcDirective::~nsCSPScriptSrcDirective() +{ +} + +bool nsCSPScriptSrcDirective::restrictsContentType(nsContentPolicyType aContentType) const +{ + if (aContentType == nsIContentPolicy::TYPE_INTERNAL_WORKER || + aContentType == nsIContentPolicy::TYPE_INTERNAL_SHARED_WORKER || + aContentType == nsIContentPolicy::TYPE_INTERNAL_SERVICE_WORKER) { + return mRestrictWorkers; + } + return mDirective == CSP_ContentTypeToDirective(aContentType); +} + +bool nsCSPScriptSrcDirective::equals(CSPDirective aDirective) const +{ + if (aDirective == nsIContentSecurityPolicy::WORKER_SRC_DIRECTIVE) { + return mRestrictWorkers; + } + return (mDirective == aDirective); } /* =============== nsBlockAllMixedContentDirective ============= */ diff --git a/dom/security/nsCSPUtils.h b/dom/security/nsCSPUtils.h index cfbe832568..91096712a7 100644 --- a/dom/security/nsCSPUtils.h +++ b/dom/security/nsCSPUtils.h @@ -93,7 +93,8 @@ static const char* CSPStrDirectives[] = { "child-src", // CHILD_SRC_DIRECTIVE "block-all-mixed-content", // BLOCK_ALL_MIXED_CONTENT "require-sri-for", // REQUIRE_SRI_FOR - "sandbox" // SANDBOX_DIRECTIVE + "sandbox", // SANDBOX_DIRECTIVE + "worker-src" // WORKER_SRC_DIRECTIVE }; inline const char* CSP_CSPDirectiveToString(CSPDirective aDir) @@ -445,7 +446,7 @@ class nsCSPDirective { bool visitSrcs(nsCSPSrcVisitor* aVisitor) const; - private: + protected: CSPDirective mDirective; nsTArray<nsCSPBaseSrc*> mSrcs; }; @@ -453,26 +454,52 @@ class nsCSPDirective { /* =============== nsCSPChildSrcDirective ============= */ /* - * In CSP 2, the child-src directive covers both workers and - * subdocuments (i.e., frames and iframes). Workers were removed - * from script-src, but frames can be controlled by either child-src - * or frame-src directives, so child-src needs to know whether it should - * also restrict frames. When both are present the frame-src directive - * takes precedent. + * In CSP 3 child-src is deprecated. For backwards compatibility + * child-src needs to restrict: + * (*) frames, in case frame-src is not expicitly specified + * (*) workers, in case worker-src is not expicitly specified */ class nsCSPChildSrcDirective : public nsCSPDirective { public: explicit nsCSPChildSrcDirective(CSPDirective aDirective); virtual ~nsCSPChildSrcDirective(); - void setHandleFrameSrc(); + void setRestrictFrames() + { mRestrictFrames = true; } + + void setRestrictWorkers() + { mRestrictWorkers = true; } + + virtual bool restrictsContentType(nsContentPolicyType aContentType) const; + + virtual bool equals(CSPDirective aDirective) const; + + private: + bool mRestrictFrames; + bool mRestrictWorkers; +}; + +/* =============== nsCSPScriptSrcDirective ============= */ + +/* + * In CSP 3 worker-src restricts workers, for backwards compatibily + * script-src has to restrict workers as the ultimate fallback if + * neither worker-src nor child-src is present in a CSP. + */ +class nsCSPScriptSrcDirective : public nsCSPDirective { + public: + explicit nsCSPScriptSrcDirective(CSPDirective aDirective); + virtual ~nsCSPScriptSrcDirective(); + + void setRestrictWorkers() + { mRestrictWorkers = true; } virtual bool restrictsContentType(nsContentPolicyType aContentType) const; virtual bool equals(CSPDirective aDirective) const; private: - bool mHandleFrameSrc; + bool mRestrictWorkers; }; /* =============== nsBlockAllMixedContentDirective === */ diff --git a/dom/security/test/csp/file_frame_src.js b/dom/security/test/csp/file_frame_src.js new file mode 100644 index 0000000000..8e81f07438 --- /dev/null +++ b/dom/security/test/csp/file_frame_src.js @@ -0,0 +1,14 @@ +let testframe = document.getElementById("testframe"); +testframe.onload = function() { + parent.postMessage({ + result: "frame-allowed", + href: document.location.href, + }, "*"); +} +testframe.onerror = function() { + parent.postMessage({ + result: "frame-blocked", + href: document.location.href, + }, "*"); +} +testframe.src = "file_frame_src_inner.html" diff --git a/dom/security/test/csp/file_frame_src_child_governs.html b/dom/security/test/csp/file_frame_src_child_governs.html new file mode 100644 index 0000000000..a51cb75be2 --- /dev/null +++ b/dom/security/test/csp/file_frame_src_child_governs.html @@ -0,0 +1,10 @@ +<html> +<head> + <meta charset="utf-8"> + <meta http-equiv="Content-Security-Policy" content="child-src https://example.com">"; +</head> +<body> +<iframe id="testframe"></iframe> +<script type="text/javascript" src="file_frame_src.js"></script> +</body> +</html> diff --git a/dom/security/test/csp/file_frame_src_frame_governs.html b/dom/security/test/csp/file_frame_src_frame_governs.html new file mode 100644 index 0000000000..2c5d5857f2 --- /dev/null +++ b/dom/security/test/csp/file_frame_src_frame_governs.html @@ -0,0 +1,10 @@ +<html> +<head> + <meta charset="utf-8"> + <meta http-equiv="Content-Security-Policy" content="frame-src https://example.com; child-src 'none'">"; +</head> +<body> +<iframe id="testframe"></iframe> +<script type="text/javascript" src="file_frame_src.js"></script> +</body> +</html> diff --git a/dom/security/test/csp/file_frame_src_inner.html b/dom/security/test/csp/file_frame_src_inner.html new file mode 100644 index 0000000000..4a2fc6095a --- /dev/null +++ b/dom/security/test/csp/file_frame_src_inner.html @@ -0,0 +1,5 @@ +<html> +<body> +dummy iframe +</body> +</html> diff --git a/dom/security/test/csp/file_spawn_service_worker.js b/dom/security/test/csp/file_spawn_service_worker.js new file mode 100644 index 0000000000..b262fa10a3 --- /dev/null +++ b/dom/security/test/csp/file_spawn_service_worker.js @@ -0,0 +1 @@ +// dummy file diff --git a/dom/security/test/csp/file_spawn_shared_worker.js b/dom/security/test/csp/file_spawn_shared_worker.js new file mode 100644 index 0000000000..00063bc5ce --- /dev/null +++ b/dom/security/test/csp/file_spawn_shared_worker.js @@ -0,0 +1,7 @@ +onconnect = function(e) { + var port = e.ports[0]; + port.addEventListener("message", function(e) { + port.postMessage("shared worker is executing"); + }); + port.start(); +} diff --git a/dom/security/test/csp/file_spawn_worker.js b/dom/security/test/csp/file_spawn_worker.js new file mode 100644 index 0000000000..acde7408c1 --- /dev/null +++ b/dom/security/test/csp/file_spawn_worker.js @@ -0,0 +1 @@ +postMessage("worker is executing"); diff --git a/dom/security/test/csp/file_worker_src.js b/dom/security/test/csp/file_worker_src.js new file mode 100644 index 0000000000..ad3ade6a65 --- /dev/null +++ b/dom/security/test/csp/file_worker_src.js @@ -0,0 +1,52 @@ + +let myWorker = new Worker("file_spawn_worker.js"); +myWorker.onmessage = function(event) { + parent.postMessage({ + result: "worker-allowed", + href: document.location.href, + }, "*"); +} +myWorker.onerror = function(event) { + parent.postMessage({ + result: "worker-blocked", + href: document.location.href, + }, "*"); +} + +// -------------------------------------------- + +var mySharedWorker = new SharedWorker('file_spawn_shared_worker.js'); +mySharedWorker.port.onmessage = function(ev) { + parent.postMessage({ + result: "shared-worker-allowed", + href: document.location.href, + }, "*"); +} +mySharedWorker.onerror = function(evt) { + evt.preventDefault(); + parent.postMessage({ + result: "shared-worker-blocked", + href: document.location.href, + }, "*"); +} +mySharedWorker.port.start(); +mySharedWorker.port.postMessage('foo'); + +// -------------------------------------------- + +navigator.serviceWorker.register('file_spawn_service_worker.js') +.then(function(reg) { + // registration worked + reg.unregister().then(function() { + parent.postMessage({ + result: "service-worker-allowed", + href: document.location.href, + }, "*"); + }); +}).catch(function(error) { + // registration failed + parent.postMessage({ + result: "service-worker-blocked", + href: document.location.href, + }, "*"); +}); diff --git a/dom/security/test/csp/file_worker_src_child_governs.html b/dom/security/test/csp/file_worker_src_child_governs.html new file mode 100644 index 0000000000..ca8a683aac --- /dev/null +++ b/dom/security/test/csp/file_worker_src_child_governs.html @@ -0,0 +1,9 @@ +<html> +<head> + <meta charset="utf-8"> + <meta http-equiv="Content-Security-Policy" content="child-src https://example.com; script-src 'nonce-foo'">"; +</head> +<body> +<script type="text/javascript" src="file_worker_src.js" nonce="foo"></script> +</body> +</html> diff --git a/dom/security/test/csp/file_worker_src_script_governs.html b/dom/security/test/csp/file_worker_src_script_governs.html new file mode 100644 index 0000000000..0385fee57c --- /dev/null +++ b/dom/security/test/csp/file_worker_src_script_governs.html @@ -0,0 +1,9 @@ +<html> +<head> + <meta charset="utf-8"> + <meta http-equiv="Content-Security-Policy" content="script-src 'nonce-foo' https://example.com">"; +</head> +<body> +<script type="text/javascript" src="file_worker_src.js" nonce="foo"></script> +</body> +</html> diff --git a/dom/security/test/csp/file_worker_src_worker_governs.html b/dom/security/test/csp/file_worker_src_worker_governs.html new file mode 100644 index 0000000000..93c8f61225 --- /dev/null +++ b/dom/security/test/csp/file_worker_src_worker_governs.html @@ -0,0 +1,9 @@ +<html> +<head> + <meta charset="utf-8"> + <meta http-equiv="Content-Security-Policy" content="worker-src https://example.com; child-src 'none'; script-src 'nonce-foo'">"; +</head> +<body> +<script type="text/javascript" src="file_worker_src.js" nonce="foo"></script> +</body> +</html> diff --git a/dom/security/test/csp/mochitest.ini b/dom/security/test/csp/mochitest.ini index 2102cbe706..ca5c2c6eab 100644 --- a/dom/security/test/csp/mochitest.ini +++ b/dom/security/test/csp/mochitest.ini @@ -316,3 +316,18 @@ support-files = [test_punycode_host_src.html] [test_websocket_self.html] skip-if = toolkit == 'android' +[test_worker_src.html] +support-files = + file_worker_src_worker_governs.html + file_worker_src_child_governs.html + file_worker_src_script_governs.html + file_worker_src.js + file_spawn_worker.js + file_spawn_shared_worker.js + file_spawn_service_worker.js +[test_frame_src.html] +support-files = + file_frame_src_frame_governs.html + file_frame_src_child_governs.html + file_frame_src.js + file_frame_src_inner.html diff --git a/dom/security/test/csp/test_child-src_worker.html b/dom/security/test/csp/test_child-src_worker.html index 7dcbd03f66..ea9e7b28e4 100644 --- a/dom/security/test/csp/test_child-src_worker.html +++ b/dom/security/test/csp/test_child-src_worker.html @@ -83,19 +83,19 @@ id: "script-src-worker", file: WORKER_TEST_FILE, result : "blocked", - policy : "default-src 'none'; script-src 'self' 'unsafe-inline'" + policy : "default-src 'none'; script-src https://www.example.org 'unsafe-inline'" }, 'script-src-service_worker': { id: "script-src-service_worker", file: SERVICE_WORKER_TEST_FILE, result : "blocked", - policy : "default-src 'none'; script-src 'self' 'unsafe-inline'" + policy : "default-src 'none'; script-src https://www.example.org 'unsafe-inline'" }, 'script-src-self-shared_worker': { id: "script-src-self-shared_worker", file: SHARED_WORKER_TEST_FILE, result : "blocked", - policy : "default-src 'none'; script-src 'self' 'unsafe-inline'" + policy : "default-src 'none'; script-src https://www.example.org 'unsafe-inline'" }, }; diff --git a/dom/security/test/csp/test_frame_src.html b/dom/security/test/csp/test_frame_src.html new file mode 100644 index 0000000000..07de90cfaf --- /dev/null +++ b/dom/security/test/csp/test_frame_src.html @@ -0,0 +1,84 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Bug 1302667 - Test frame-src</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<iframe style="width:100%;" id="testframe"></iframe> + +<script class="testbody" type="text/javascript"> + +SimpleTest.waitForExplicitFinish(); + +/* Description of the test: + * We load a page inlcuding a frame a CSP of: + * >> frame-src https://example.com; child-src 'none' + * and make sure that frame-src governs frames correctly. In addition, + * we make sure that child-src is discarded in case frame-src is specified. + */ + +const ORIGIN_1 = "https://example.com/tests/dom/security/test/csp/"; +const ORIGIN_2 = "https://test1.example.com/tests/dom/security/test/csp/"; + +let TESTS = [ + // frame-src tests + ORIGIN_1 + "file_frame_src_frame_governs.html", + ORIGIN_2 + "file_frame_src_frame_governs.html", + // child-src tests + ORIGIN_1 + "file_frame_src_child_governs.html", + ORIGIN_2 + "file_frame_src_child_governs.html", +]; + +let testIndex = 0; + +function checkFinish() { + if (testIndex >= TESTS.length) { + window.removeEventListener("message", receiveMessage); + SimpleTest.finish(); + return; + } + runNextTest(); +} + +window.addEventListener("message", receiveMessage); +function receiveMessage(event) { + let href = event.data.href; + let result = event.data.result; + + if (href.startsWith("https://example.com")) { + if (result == "frame-allowed") { + ok(true, "allowing frame from https://example.com (" + result + ")"); + } + else { + ok(false, "blocking frame from https://example.com (" + result + ")"); + } + } + else if (href.startsWith("https://test1.example.com")) { + if (result == "frame-blocked") { + ok(true, "blocking frame from https://test1.example.com (" + result + ")"); + } + else { + ok(false, "allowing frame from https://test1.example.com (" + result + ")"); + } + } + else { + // sanity check, we should never enter that branch, bust just in case... + ok(false, "unexpected result: " + result); + } + checkFinish(); +} + +function runNextTest() { + document.getElementById("testframe").src = TESTS[testIndex]; + testIndex++; +} + +// fire up the tests +runNextTest(); + +</script> +</body> +</html> diff --git a/dom/security/test/csp/test_worker_src.html b/dom/security/test/csp/test_worker_src.html new file mode 100644 index 0000000000..3f2b44c9f0 --- /dev/null +++ b/dom/security/test/csp/test_worker_src.html @@ -0,0 +1,94 @@ +<!DOCTYPE HTML> +<html> +<head> + <meta charset="utf-8"> + <title>Bug 1302667 - Test worker-src</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<iframe style="width:100%;" id="worker-testframe"></iframe> +<iframe style="width:100%;" id="child-testframe"></iframe> +<iframe style="width:100%;" id="script-testframe"></iframe> + +<script class="testbody" type="text/javascript"> + +SimpleTest.waitForExplicitFinish(); + +/* Description of the test: + * We load a page inlcuding a worker, a shared worker as well as a + * service worker with a CSP of: + * >> worker-src https://example.com; child-src 'none'; script-src 'nonce-foo' + * and make sure that worker-src governs these three kinds of workers correctly. + * In addition, we make sure that child-src as well as script-src is discarded + * in case worker-src is specified. Ideally we would use "script-src 'none'" but + * we have to whitelist the actual script that spawns the workers, hence the nonce. + */ + +let testRuns = 0; +let messageCounter = 0; +let numberSubTests = 9; // 3 workers * 3 frames = 9 + +function checkFinish() { + messageCounter = 0; + if (testRuns == 0) { + testRuns++; + runTests("https://test1.example.com/tests/dom/security/test/csp/") + return; + } + window.removeEventListener("message", receiveMessage); + SimpleTest.finish(); +} + +window.addEventListener("message", receiveMessage); +function receiveMessage(event) { + let href = event.data.href; + let result = event.data.result; + + if (href.startsWith("https://example.com")) { + if (result == "worker-allowed" || + result == "shared-worker-allowed" || + result == "service-worker-allowed") { + ok(true, "allowing worker from https://example.com (" + result + ")"); + } + else { + ok(false, "blocking worker from https://example.com (" + result + ")"); + } + } + else if (href.startsWith("https://test1.example.com")) { + if (result == "worker-blocked" || + result == "shared-worker-blocked" || + result == "service-worker-blocked") { + ok(true, "blocking worker from https://test1.example.com (" + result + ")"); + } + else { + ok(false, "allowing worker from https://test1.example.com (" + result + ")"); + } + } + else { + // sanity check, we should never enter that branch, bust just in case... + ok(false, "unexpected result: " + result); + } + messageCounter++; + if (messageCounter < numberSubTests) { + return; + } + checkFinish(); +} + +function runTests(aPath) { + document.getElementById("worker-testframe").src = aPath + "file_worker_src_worker_governs.html"; + document.getElementById("child-testframe").src = aPath + "file_worker_src_child_governs.html"; + document.getElementById("script-testframe").src = aPath + "file_worker_src_script_governs.html"; +} + +SpecialPowers.pushPrefEnv({"set": [ + ["dom.serviceWorkers.enabled", true], + ["dom.serviceWorkers.testing.enabled", true], +]}, function() { + runTests("https://example.com/tests/dom/security/test/csp/"); +}); + +</script> +</body> +</html> diff --git a/dom/security/test/gtest/TestCSPParser.cpp b/dom/security/test/gtest/TestCSPParser.cpp index 8d168d81cb..893e02db5e 100644 --- a/dom/security/test/gtest/TestCSPParser.cpp +++ b/dom/security/test/gtest/TestCSPParser.cpp @@ -240,6 +240,10 @@ TEST(CSPParser, Directives) "script-src 'nonce-foo' 'strict-dynamic' 'unsafe-inline' https:" }, { "default-src 'sha256-siVR8' 'strict-dynamic' 'unsafe-inline' https: ", "default-src 'sha256-siVR8' 'unsafe-inline' https:" }, + { "worker-src https://example.com", + "worker-src https://example.com" }, + { "worker-src http://worker.com; frame-src http://frame.com; child-src http://child.com", + "worker-src http://worker.com; frame-src http://frame.com; child-src http://child.com" }, }; uint32_t policyCount = sizeof(policies) / sizeof(PolicyTest); diff --git a/dom/webidl/CSPDictionaries.webidl b/dom/webidl/CSPDictionaries.webidl index 54008f13ab..f8de1c9ad8 100644 --- a/dom/webidl/CSPDictionaries.webidl +++ b/dom/webidl/CSPDictionaries.webidl @@ -30,6 +30,7 @@ dictionary CSP { sequence<DOMString> block-all-mixed-content; sequence<DOMString> require-sri-for; sequence<DOMString> sandbox; + sequence<DOMString> worker-src; }; dictionary CSPPolicies { |