diff --git a/dom/interfaces/security/nsIContentSecurityPolicy.idl b/dom/interfaces/security/nsIContentSecurityPolicy.idl index a4e024cedcfd..08eb1007ccf9 100644 --- a/dom/interfaces/security/nsIContentSecurityPolicy.idl +++ b/dom/interfaces/security/nsIContentSecurityPolicy.idl @@ -63,6 +63,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 4124ef8aac9a..4c4054cee87f 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 45c9e9d94461..1f6de5e02cf4 100644 --- a/dom/security/nsCSPParser.cpp +++ b/dom/security/nsCSPParser.cpp @@ -134,6 +134,8 @@ nsCSPParser::nsCSPParser(cspTokens& aTokens, , mUnsafeInlineKeywordSrc(nullptr) , mChildSrc(nullptr) , mFrameSrc(nullptr) + , mWorkerSrc(nullptr) + , mScriptSrc(nullptr) , mParsingFrameAncestorsDir(false) , mTokens(aTokens) , mSelfURI(aSelfURI) @@ -1110,21 +1112,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(), u"child-src" }; - 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)); } @@ -1301,9 +1319,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 d5f1be94fcc3..9ab4e139132b 100644 --- a/dom/security/nsCSPParser.h +++ b/dom/security/nsCSPParser.h @@ -242,14 +242,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 27f809157109..d1aeeeab9f22 100644 --- a/dom/security/nsCSPUtils.cpp +++ b/dom/security/nsCSPUtils.cpp @@ -232,7 +232,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; @@ -1190,6 +1190,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: @@ -1242,7 +1247,8 @@ bool nsCSPDirective::equals(CSPDirective aDirective) const nsCSPChildSrcDirective::nsCSPChildSrcDirective(CSPDirective aDirective) : nsCSPDirective(aDirective) - , mHandleFrameSrc(false) + , mRestrictFrames(false) + , mRestrictWorkers(false) { } @@ -1250,30 +1256,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 0229e5a5001b..a6b0c444fadf 100644 --- a/dom/security/nsCSPUtils.h +++ b/dom/security/nsCSPUtils.h @@ -94,7 +94,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) @@ -470,7 +471,7 @@ class nsCSPDirective { bool visitSrcs(nsCSPSrcVisitor* aVisitor) const; - private: + protected: CSPDirective mDirective; nsTArray mSrcs; }; @@ -478,26 +479,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 mHandleFrameSrc; + 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 mRestrictWorkers; }; /* =============== nsBlockAllMixedContentDirective === */ diff --git a/dom/webidl/CSPDictionaries.webidl b/dom/webidl/CSPDictionaries.webidl index 54008f13ab05..f8de1c9ad822 100644 --- a/dom/webidl/CSPDictionaries.webidl +++ b/dom/webidl/CSPDictionaries.webidl @@ -30,6 +30,7 @@ dictionary CSP { sequence block-all-mixed-content; sequence require-sri-for; sequence sandbox; + sequence worker-src; }; dictionary CSPPolicies {