From 3b59b81c933adffd01677161f99a7469bddc5838 Mon Sep 17 00:00:00 2001 From: Kate McKinley Date: Wed, 28 Oct 2015 16:32:27 -0700 Subject: [PATCH] Bug 1045891 - CSP 2 child-src implementation. r=ckerschb --- dom/base/nsContentPolicy.cpp | 11 +-- dom/base/nsContentUtils.cpp | 27 ++++++++ dom/base/nsContentUtils.h | 23 +++++++ .../security/nsIContentSecurityPolicy.idl | 3 +- .../en-US/chrome/security/csp.properties | 3 + dom/security/nsCSPContext.cpp | 7 +- dom/security/nsCSPParser.cpp | 23 +++++++ dom/security/nsCSPParser.h | 9 +++ dom/security/nsCSPService.cpp | 14 ++-- dom/security/nsCSPUtils.cpp | 55 +++++++++++++++ dom/security/nsCSPUtils.h | 67 +++++++++++++------ 11 files changed, 210 insertions(+), 32 deletions(-) diff --git a/dom/base/nsContentPolicy.cpp b/dom/base/nsContentPolicy.cpp index 946262f1b161..b883f105a20c 100644 --- a/dom/base/nsContentPolicy.cpp +++ b/dom/base/nsContentPolicy.cpp @@ -124,8 +124,8 @@ nsContentPolicy::CheckPolicy(CPMethod policyMethod, nsContentPolicyType externalTypeOrScript = nsContentUtils::InternalContentPolicyTypeToExternalOrScript(contentType); - nsContentPolicyType externalTypeOrPreload = - nsContentUtils::InternalContentPolicyTypeToExternalOrPreload(contentType); + nsContentPolicyType externalTypeOrCSPInternal = + nsContentUtils::InternalContentPolicyTypeToExternalOrCSPInternal(contentType); nsCOMPtr mixedContentBlocker = do_GetService(NS_MIXEDCONTENTBLOCKER_CONTRACTID); @@ -152,13 +152,16 @@ nsContentPolicy::CheckPolicy(CPMethod policyMethod, type = externalTypeOrScript; } // Send the internal content policy type for CSP which needs to - // know about preloads, in particular: + // know about preloads and workers, in particular: // * TYPE_INTERNAL_SCRIPT_PRELOAD // * TYPE_INTERNAL_IMAGE_PRELOAD // * TYPE_INTERNAL_STYLESHEET_PRELOAD + // * TYPE_INTERNAL_WORKER + // * TYPE_INTERNAL_SHARED_WORKER + // * TYPE_INTERNAL_SERVICE_WORKER bool isCSP = cspService == entries[i]; if (isCSP) { - type = externalTypeOrPreload; + type = externalTypeOrCSPInternal; } rv = (entries[i]->*policyMethod)(type, contentLocation, requestingLocation, requestingContext, diff --git a/dom/base/nsContentUtils.cpp b/dom/base/nsContentUtils.cpp index ae21f339439d..680c2ef5ac45 100644 --- a/dom/base/nsContentUtils.cpp +++ b/dom/base/nsContentUtils.cpp @@ -8044,6 +8044,33 @@ nsContentUtils::InternalContentPolicyTypeToExternalOrPreload(nsContentPolicyType return InternalContentPolicyTypeToExternal(aType); } + +/* static */ +nsContentPolicyType +nsContentUtils::InternalContentPolicyTypeToExternalOrWorker(nsContentPolicyType aType) +{ + switch (aType) { + case nsIContentPolicy::TYPE_INTERNAL_WORKER: + case nsIContentPolicy::TYPE_INTERNAL_SHARED_WORKER: + case nsIContentPolicy::TYPE_INTERNAL_SERVICE_WORKER: + return aType; + + default: + return InternalContentPolicyTypeToExternal(aType); + } +} + +/* static */ +nsContentPolicyType +nsContentUtils::InternalContentPolicyTypeToExternalOrCSPInternal(nsContentPolicyType aType) +{ + if (aType == InternalContentPolicyTypeToExternalOrWorker(aType) || + aType == InternalContentPolicyTypeToExternalOrPreload(aType)) { + return aType; + } + return InternalContentPolicyTypeToExternal(aType); +} + nsresult nsContentUtils::SetFetchReferrerURIWithPolicy(nsIPrincipal* aPrincipal, nsIDocument* aDoc, diff --git a/dom/base/nsContentUtils.h b/dom/base/nsContentUtils.h index 480696309897..5218cad9287f 100644 --- a/dom/base/nsContentUtils.h +++ b/dom/base/nsContentUtils.h @@ -1013,6 +1013,29 @@ public: */ static nsContentPolicyType InternalContentPolicyTypeToExternalOrPreload(nsContentPolicyType aType); + /** + * Map internal content policy types to external ones, worker, or preload types: + * * TYPE_INTERNAL_WORKER + * * TYPE_INTERNAL_SHARED_WORKER + * * TYPE_INTERNAL_SERVICE_WORKER + * * TYPE_INTERNAL_SCRIPT_PRELOAD + * * TYPE_INTERNAL_IMAGE_PRELOAD + * * TYPE_INTERNAL_STYLESHEET_PRELOAD + * + * Note: DO NOT call this function unless you know what you're doing! + */ + static nsContentPolicyType InternalContentPolicyTypeToExternalOrCSPInternal(nsContentPolicyType aType); + + /** + * Map internal content policy types to external ones, worker, or preload types: + * * TYPE_INTERNAL_WORKER + * * TYPE_INTERNAL_SHARED_WORKER + * * TYPE_INTERNAL_SERVICE_WORKER + * + * Note: DO NOT call this function unless you know what you're doing! + */ + static nsContentPolicyType InternalContentPolicyTypeToExternalOrWorker(nsContentPolicyType aType); + /** * Quick helper to determine whether there are any mutation listeners * of a given type that apply to this content or any of its ancestors. diff --git a/dom/interfaces/security/nsIContentSecurityPolicy.idl b/dom/interfaces/security/nsIContentSecurityPolicy.idl index a66b08b3a71e..a94bd455e9b3 100644 --- a/dom/interfaces/security/nsIContentSecurityPolicy.idl +++ b/dom/interfaces/security/nsIContentSecurityPolicy.idl @@ -20,7 +20,7 @@ interface nsIURI; typedef unsigned short CSPDirective; -[scriptable, uuid(fe07ab08-21ba-470c-8b89-78d0e7298c68)] +[scriptable, uuid(36c6d419-24c2-40e8-9adb-11d0b1341770)] interface nsIContentSecurityPolicy : nsISerializable { /** @@ -50,6 +50,7 @@ interface nsIContentSecurityPolicy : nsISerializable const unsigned short REFERRER_DIRECTIVE = 15; const unsigned short WEB_MANIFEST_SRC_DIRECTIVE = 16; const unsigned short UPGRADE_IF_INSECURE_DIRECTIVE = 17; + const unsigned short CHILD_SRC_DIRECTIVE = 18; /** * 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 1d5237c537be..a838b4fc9c79 100644 --- a/dom/locales/en-US/chrome/security/csp.properties +++ b/dom/locales/en-US/chrome/security/csp.properties @@ -82,3 +82,6 @@ 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. diff --git a/dom/security/nsCSPContext.cpp b/dom/security/nsCSPContext.cpp index 6246475ac256..ccacd1fd827e 100644 --- a/dom/security/nsCSPContext.cpp +++ b/dom/security/nsCSPContext.cpp @@ -113,6 +113,7 @@ nsCSPContext::ShouldLoad(nsContentPolicyType aContentType, nsAutoCString spec; aContentLocation->GetSpec(spec); CSPCONTEXTLOG(("nsCSPContext::ShouldLoad, aContentLocation: %s", spec.get())); + CSPCONTEXTLOG((">>>> aContentType: %d", aContentType)); } bool isStyleOrScriptPreLoad = @@ -121,7 +122,9 @@ nsCSPContext::ShouldLoad(nsContentPolicyType aContentType, // Since we know whether we are dealing with a preload, we have to convert // the internal policytype ot the external policy type before moving on. - aContentType = nsContentUtils::InternalContentPolicyTypeToExternal(aContentType); + // We still need to know if this is a worker so child-src can handle that + // case correctly. + aContentType = nsContentUtils::InternalContentPolicyTypeToExternalOrWorker(aContentType); nsresult rv = NS_OK; @@ -187,7 +190,7 @@ nsCSPContext::ShouldLoad(nsContentPolicyType aContentType, if (CSPCONTEXTLOGENABLED()) { nsAutoCString spec; aContentLocation->GetSpec(spec); - CSPCONTEXTLOG(("nsCSPContext::ShouldLoad, decision: %s, aContentLocation: %s", *outDecision ? "load" : "deny", spec.get())); + CSPCONTEXTLOG(("nsCSPContext::ShouldLoad, decision: %s, aContentLocation: %s", *outDecision > 0 ? "load" : "deny", spec.get())); } return NS_OK; } diff --git a/dom/security/nsCSPParser.cpp b/dom/security/nsCSPParser.cpp index 3fd0e468e7a4..d98856e3e6da 100644 --- a/dom/security/nsCSPParser.cpp +++ b/dom/security/nsCSPParser.cpp @@ -125,6 +125,8 @@ nsCSPParser::nsCSPParser(cspTokens& aTokens, uint64_t aInnerWindowID) : mHasHashOrNonce(false) , mUnsafeInlineKeywordSrc(nullptr) + , mChildSrc(nullptr) + , mFrameSrc(nullptr) , mTokens(aTokens) , mSelfURI(aSelfURI) , mInnerWindowID(aInnerWindowID) @@ -994,6 +996,21 @@ nsCSPParser::directiveName() return new nsUpgradeInsecureDirective(CSP_StringToCSPDirective(mCurToken)); } + // child-src has it's own class to handle frame-src if necessary + if (CSP_IsDirective(mCurToken, nsIContentSecurityPolicy::CHILD_SRC_DIRECTIVE)) { + 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 (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; + } + return new nsCSPDirective(CSP_StringToCSPDirective(mCurToken)); } @@ -1086,6 +1103,12 @@ nsCSPParser::policy() mCurDir = mTokens[i]; directive(); } + + if (mChildSrc && !mFrameSrc) { + // if we have a child-src, it handles frame-src too, unless frame-src is set + mChildSrc->setHandleFrameSrc(); + } + return mPolicy; } diff --git a/dom/security/nsCSPParser.h b/dom/security/nsCSPParser.h index 4f933d60e63b..86d482fd0ed0 100644 --- a/dom/security/nsCSPParser.h +++ b/dom/security/nsCSPParser.h @@ -233,6 +233,15 @@ class nsCSPParser { bool mHasHashOrNonce; // false, if no hash or nonce is 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; + cspTokens mTokens; nsIURI* mSelfURI; nsCSPPolicy* mPolicy; diff --git a/dom/security/nsCSPService.cpp b/dom/security/nsCSPService.cpp index 2c2fe79a78f9..f46485850e40 100644 --- a/dom/security/nsCSPService.cpp +++ b/dom/security/nsCSPService.cpp @@ -106,8 +106,8 @@ CSPService::ShouldLoad(uint32_t aContentType, int16_t *aDecision) { MOZ_ASSERT(aContentType == - nsContentUtils::InternalContentPolicyTypeToExternalOrPreload(aContentType), - "We should only see external content policy types or preloads here."); + nsContentUtils::InternalContentPolicyTypeToExternalOrCSPInternal(aContentType), + "We should only see external content policy types or CSP special types (preloads or workers) here."); if (!aContentLocation) { return NS_ERROR_FAILURE; @@ -254,7 +254,7 @@ CSPService::ShouldProcess(uint32_t aContentType, int16_t *aDecision) { MOZ_ASSERT(aContentType == - nsContentUtils::InternalContentPolicyTypeToExternalOrPreload(aContentType), + nsContentUtils::InternalContentPolicyTypeToExternalOrCSPInternal(aContentType), "We should only see external content policy types or preloads here."); if (!aContentLocation) @@ -314,7 +314,13 @@ CSPService::AsyncOnChannelRedirect(nsIChannel *oldChannel, nsCOMPtr originalUri; rv = oldChannel->GetOriginalURI(getter_AddRefs(originalUri)); NS_ENSURE_SUCCESS(rv, rv); - nsContentPolicyType policyType = loadInfo->GetExternalContentPolicyType(); + /* On redirect, if the content policy is a preload type, rejecting the preload + * results in the load silently failing, so we convert preloads to the actual + * type. See Bug 1219453. + */ + nsContentPolicyType policyType = + nsContentUtils::InternalContentPolicyTypeToExternalOrWorker( + loadInfo->InternalContentPolicyType()); int16_t aDecision = nsIContentPolicy::ACCEPT; csp->ShouldLoad(policyType, // load type per nsIContentPolicy (uint32_t) diff --git a/dom/security/nsCSPUtils.cpp b/dom/security/nsCSPUtils.cpp index b0b43491a5c7..81cd4678d2bc 100644 --- a/dom/security/nsCSPUtils.cpp +++ b/dom/security/nsCSPUtils.cpp @@ -138,6 +138,8 @@ CSP_ContentTypeToDirective(nsContentPolicyType aType) // BLock XSLT as script, see bug 910139 case nsIContentPolicy::TYPE_XSLT: case nsIContentPolicy::TYPE_SCRIPT: + case nsIContentPolicy::TYPE_INTERNAL_SCRIPT: + case nsIContentPolicy::TYPE_INTERNAL_SCRIPT_PRELOAD: return nsIContentSecurityPolicy::SCRIPT_SRC_DIRECTIVE; case nsIContentPolicy::TYPE_STYLESHEET: @@ -152,6 +154,11 @@ CSP_ContentTypeToDirective(nsContentPolicyType aType) case nsIContentPolicy::TYPE_WEB_MANIFEST: return nsIContentSecurityPolicy::WEB_MANIFEST_SRC_DIRECTIVE; + case nsIContentPolicy::TYPE_INTERNAL_WORKER: + case nsIContentPolicy::TYPE_INTERNAL_SHARED_WORKER: + case nsIContentPolicy::TYPE_INTERNAL_SERVICE_WORKER: + return nsIContentSecurityPolicy::CHILD_SRC_DIRECTIVE; + case nsIContentPolicy::TYPE_SUBDOCUMENT: return nsIContentSecurityPolicy::FRAME_SRC_DIRECTIVE; @@ -902,6 +909,11 @@ nsCSPDirective::toDomCSPStruct(mozilla::dom::CSP& outCSP) const // does not have any srcs return; + case nsIContentSecurityPolicy::CHILD_SRC_DIRECTIVE: + outCSP.mChild_src.Construct(); + outCSP.mChild_src.Value() = mozilla::Move(srcs); + return; + // REFERRER_DIRECTIVE is handled in nsCSPPolicy::toDomCSPStruct() default: @@ -934,6 +946,49 @@ nsCSPDirective::getReportURIs(nsTArray &outReportURIs) const } } +bool nsCSPDirective::equals(CSPDirective aDirective) const +{ + return (mDirective == aDirective); +} + +/* =============== nsCSPChildSrcDirective ============= */ + +nsCSPChildSrcDirective::nsCSPChildSrcDirective(CSPDirective aDirective) + : nsCSPDirective(aDirective) + , mHandleFrameSrc(false) +{ +} + +nsCSPChildSrcDirective::~nsCSPChildSrcDirective() +{ +} + +void nsCSPChildSrcDirective::setHandleFrameSrc() +{ + mHandleFrameSrc = true; +} + +bool nsCSPChildSrcDirective::restrictsContentType(nsContentPolicyType aContentType) const +{ + if (aContentType == nsIContentPolicy::TYPE_SUBDOCUMENT) { + return mHandleFrameSrc; + } + + return (aContentType == nsIContentPolicy::TYPE_INTERNAL_WORKER + || aContentType == nsIContentPolicy::TYPE_INTERNAL_SHARED_WORKER + || aContentType == nsIContentPolicy::TYPE_INTERNAL_SERVICE_WORKER + ); +} + +bool nsCSPChildSrcDirective::equals(CSPDirective aDirective) const +{ + if (aDirective == nsIContentSecurityPolicy::FRAME_SRC_DIRECTIVE) { + return mHandleFrameSrc; + } + + return (aDirective == nsIContentSecurityPolicy::CHILD_SRC_DIRECTIVE); +} + /* =============== nsUpgradeInsecureDirective ============= */ nsUpgradeInsecureDirective::nsUpgradeInsecureDirective(CSPDirective aDirective) diff --git a/dom/security/nsCSPUtils.h b/dom/security/nsCSPUtils.h index 64d7e3030b64..a0cd8bd15428 100644 --- a/dom/security/nsCSPUtils.h +++ b/dom/security/nsCSPUtils.h @@ -70,24 +70,25 @@ void CSP_LogMessage(const nsAString& aMessage, // Order of elements below important! Make sure it matches the order as in // nsIContentSecurityPolicy.idl static const char* CSPStrDirectives[] = { - "-error-", // NO_DIRECTIVE - "default-src", // DEFAULT_SRC_DIRECTIVE - "script-src", // SCRIPT_SRC_DIRECTIVE - "object-src", // OBJECT_SRC_DIRECTIVE - "style-src", // STYLE_SRC_DIRECTIVE - "img-src", // IMG_SRC_DIRECTIVE - "media-src", // MEDIA_SRC_DIRECTIVE - "frame-src", // FRAME_SRC_DIRECTIVE - "font-src", // FONT_SRC_DIRECTIVE - "connect-src", // CONNECT_SRC_DIRECTIVE - "report-uri", // REPORT_URI_DIRECTIVE - "frame-ancestors", // FRAME_ANCESTORS_DIRECTIVE - "reflected-xss", // REFLECTED_XSS_DIRECTIVE - "base-uri", // BASE_URI_DIRECTIVE - "form-action", // FORM_ACTION_DIRECTIVE - "referrer", // REFERRER_DIRECTIVE - "manifest-src", // MANIFEST_SRC_DIRECTIVE - "upgrade-insecure-requests" // UPGRADE_IF_INSECURE_DIRECTIVE + "-error-", // NO_DIRECTIVE + "default-src", // DEFAULT_SRC_DIRECTIVE + "script-src", // SCRIPT_SRC_DIRECTIVE + "object-src", // OBJECT_SRC_DIRECTIVE + "style-src", // STYLE_SRC_DIRECTIVE + "img-src", // IMG_SRC_DIRECTIVE + "media-src", // MEDIA_SRC_DIRECTIVE + "frame-src", // FRAME_SRC_DIRECTIVE + "font-src", // FONT_SRC_DIRECTIVE + "connect-src", // CONNECT_SRC_DIRECTIVE + "report-uri", // REPORT_URI_DIRECTIVE + "frame-ancestors", // FRAME_ANCESTORS_DIRECTIVE + "reflected-xss", // REFLECTED_XSS_DIRECTIVE + "base-uri", // BASE_URI_DIRECTIVE + "form-action", // FORM_ACTION_DIRECTIVE + "referrer", // REFERRER_DIRECTIVE + "manifest-src", // MANIFEST_SRC_DIRECTIVE + "upgrade-insecure-requests", // UPGRADE_IF_INSECURE_DIRECTIVE + "child-src" // CHILD_SRC_DIRECTIVE }; inline const char* CSP_CSPDirectiveToString(CSPDirective aDir) @@ -303,13 +304,12 @@ class nsCSPDirective { virtual void addSrcs(const nsTArray& aSrcs) { mSrcs = aSrcs; } - bool restrictsContentType(nsContentPolicyType aContentType) const; + virtual bool restrictsContentType(nsContentPolicyType aContentType) const; inline bool isDefaultDirective() const { return mDirective == nsIContentSecurityPolicy::DEFAULT_SRC_DIRECTIVE; } - inline bool equals(CSPDirective aDirective) const - { return (mDirective == aDirective); } + virtual bool equals(CSPDirective aDirective) const; void getReportURIs(nsTArray &outReportURIs) const; @@ -318,6 +318,31 @@ class nsCSPDirective { nsTArray mSrcs; }; +/* =============== 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. + */ +class nsCSPChildSrcDirective : public nsCSPDirective { + public: + explicit nsCSPChildSrcDirective(CSPDirective aDirective); + virtual ~nsCSPChildSrcDirective(); + + void setHandleFrameSrc(); + + virtual bool restrictsContentType(nsContentPolicyType aContentType) const; + + virtual bool equals(CSPDirective aDirective) const; + + private: + bool mHandleFrameSrc; +}; + /* =============== nsUpgradeInsecureDirective === */ /*