Bug 1045891 - CSP 2 child-src implementation r=ckerschb
authorKate McKinley <kmckinley@mozilla.com>
Wed, 28 Oct 2015 16:32:27 -0700
changeset 307999 58d4c3876d0cd4ee42a48276a2c4ed6f7ee6e339
parent 307895 027084bb3cb52719fdea14e6d7346068454fc445
child 308000 4ca475a0254a7b2b50800bdf65a0729b45d0e071
push id7422
push userpehrsons@gmail.com
push dateWed, 11 Nov 2015 04:19:53 +0000
reviewersckerschb
bugs1045891
milestone45.0a1
Bug 1045891 - CSP 2 child-src implementation r=ckerschb
dom/base/nsContentPolicy.cpp
dom/base/nsContentUtils.cpp
dom/base/nsContentUtils.h
dom/interfaces/security/nsIContentSecurityPolicy.idl
dom/locales/en-US/chrome/security/csp.properties
dom/security/nsCSPContext.cpp
dom/security/nsCSPParser.cpp
dom/security/nsCSPParser.h
dom/security/nsCSPService.cpp
dom/security/nsCSPUtils.cpp
dom/security/nsCSPUtils.h
--- a/dom/base/nsContentPolicy.cpp
+++ b/dom/base/nsContentPolicy.cpp
@@ -119,18 +119,18 @@ nsContentPolicy::CheckPolicy(CPMethod   
     }
 
     nsContentPolicyType externalType =
         nsContentUtils::InternalContentPolicyTypeToExternal(contentType);
 
     nsContentPolicyType externalTypeOrScript =
         nsContentUtils::InternalContentPolicyTypeToExternalOrScript(contentType);
 
-    nsContentPolicyType externalTypeOrPreload =
-       nsContentUtils::InternalContentPolicyTypeToExternalOrPreload(contentType);
+    nsContentPolicyType externalTypeOrCSPInternal =
+       nsContentUtils::InternalContentPolicyTypeToExternalOrCSPInternal(contentType);
 
     nsCOMPtr<nsIContentPolicy> mixedContentBlocker =
         do_GetService(NS_MIXEDCONTENTBLOCKER_CONTRACTID);
 
     nsCOMPtr<nsIContentPolicy> cspService =
       do_GetService(CSPSERVICE_CONTRACTID);
 
     /* 
@@ -147,23 +147,26 @@ nsContentPolicy::CheckPolicy(CPMethod   
         // which needs to know about TYPE_INTERNAL_WORKER,
         // TYPE_INTERNAL_SHARED_WORKER and TYPE_INTERNAL_SERVICE_WORKER.
         bool isMixedContentBlocker = mixedContentBlocker == entries[i];
         nsContentPolicyType type = externalType;
         if (isMixedContentBlocker) {
             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,
                                          mimeType, extra, requestPrincipal,
                                          decision);
 
         if (NS_SUCCEEDED(rv) && NS_CP_REJECTED(*decision)) {
             /* policy says no, no point continuing to check */
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -8039,16 +8039,43 @@ nsContentUtils::InternalContentPolicyTyp
   if (aType == nsIContentPolicy::TYPE_INTERNAL_SCRIPT_PRELOAD ||
       aType == nsIContentPolicy::TYPE_INTERNAL_IMAGE_PRELOAD ||
       aType == nsIContentPolicy::TYPE_INTERNAL_STYLESHEET_PRELOAD) {
     return aType;
   }
   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,
                                               nsIHttpChannel* aChannel)
 {
   NS_ENSURE_ARG_POINTER(aPrincipal);
   NS_ENSURE_ARG_POINTER(aChannel);
 
--- a/dom/base/nsContentUtils.h
+++ b/dom/base/nsContentUtils.h
@@ -1009,16 +1009,39 @@ public:
    *   * TYPE_INTERNAL_IMAGE_PRELOAD
    *   * TYPE_INTERNAL_STYLESHEET_PRELOAD
    *
    * Note: DO NOT call this function unless you know what you're doing!
    */
   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.
    * The method has the side effect to call document's MayDispatchMutationEvent
    * using aTargetForSubtreeModified as the parameter.
    *
    * @param aNode  The node to search for listeners
    * @param aType  The type of listener (NS_EVENT_BITS_MUTATION_*)
    * @param aTargetForSubtreeModified The node which is the target of the
--- a/dom/interfaces/security/nsIContentSecurityPolicy.idl
+++ b/dom/interfaces/security/nsIContentSecurityPolicy.idl
@@ -15,17 +15,17 @@ interface nsIURI;
  * nsIContentSecurityPolicy
  * Describes an XPCOM component used to model and enforce CSPs.  Instances of
  * this class may have multiple policies within them, but there should only be
  * one of these per document/principal.
  */
 
 typedef unsigned short CSPDirective;
 
-[scriptable, uuid(fe07ab08-21ba-470c-8b89-78d0e7298c68)]
+[scriptable, uuid(36c6d419-24c2-40e8-9adb-11d0b1341770)]
 interface nsIContentSecurityPolicy : nsISerializable
 {
   /**
    * Directives supported by Content Security Policy.  These are enums for
    * the CSPDirective type.
    * The NO_DIRECTIVE entry is  used for checking default permissions and
    * returning failure when asking CSP which directive to check.
    *
@@ -45,16 +45,17 @@ interface nsIContentSecurityPolicy : nsI
   const unsigned short REPORT_URI_DIRECTIVE           = 10;
   const unsigned short FRAME_ANCESTORS_DIRECTIVE      = 11;
   const unsigned short REFLECTED_XSS_DIRECTIVE        = 12;
   const unsigned short BASE_URI_DIRECTIVE             = 13;
   const unsigned short FORM_ACTION_DIRECTIVE          = 14;
   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
    * index.
    */
   AString getPolicy(in unsigned long index);
 
   /**
--- a/dom/locales/en-US/chrome/security/csp.properties
+++ b/dom/locales/en-US/chrome/security/csp.properties
@@ -77,8 +77,11 @@ couldntParseInvalidHost = Couldn't parse
 # %1$S is the string source
 couldntParseScheme = Couldn't parse scheme in %1$S
 # LOCALIZATION NOTE (couldntParsePort):
 # %1$S is the string source
 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.
--- a/dom/security/nsCSPContext.cpp
+++ b/dom/security/nsCSPContext.cpp
@@ -108,25 +108,28 @@ nsCSPContext::ShouldLoad(nsContentPolicy
                          const nsACString&   aMimeTypeGuess,
                          nsISupports*        aExtra,
                          int16_t*            outDecision)
 {
   if (CSPCONTEXTLOGENABLED()) {
     nsAutoCString spec;
     aContentLocation->GetSpec(spec);
     CSPCONTEXTLOG(("nsCSPContext::ShouldLoad, aContentLocation: %s", spec.get()));
+    CSPCONTEXTLOG((">>>>                      aContentType: %d", aContentType));
   }
 
   bool isStyleOrScriptPreLoad =
     (aContentType == nsIContentPolicy::TYPE_INTERNAL_SCRIPT_PRELOAD ||
      aContentType == nsIContentPolicy::TYPE_INTERNAL_STYLESHEET_PRELOAD);
 
   // 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;
 
   // This ShouldLoad function is called from nsCSPService::ShouldLoad,
   // which already checked a number of things, including:
   // * aContentLocation is not null; we can consume this without further checks
   // * scheme is not a whitelisted scheme (about: chrome:, etc).
   // * CSP is enabled
@@ -182,17 +185,17 @@ nsCSPContext::ShouldLoad(nsContentPolicy
   // Done looping, cache any relevant result
   if (cacheKey.Length() > 0 && !isStyleOrScriptPreLoad) {
     mShouldLoadCache.Put(cacheKey, *outDecision);
   }
 
   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;
 }
 
 bool
 nsCSPContext::permitsInternal(CSPDirective aDir,
                               nsIURI* aContentLocation,
                               nsIURI* aOriginalURI,
--- a/dom/security/nsCSPParser.cpp
+++ b/dom/security/nsCSPParser.cpp
@@ -120,16 +120,18 @@ nsCSPTokenizer::tokenizeCSPPolicy(const 
 
 /* ===== nsCSPParser ==================== */
 
 nsCSPParser::nsCSPParser(cspTokens& aTokens,
                          nsIURI* aSelfURI,
                          uint64_t aInnerWindowID)
  : mHasHashOrNonce(false)
  , mUnsafeInlineKeywordSrc(nullptr)
+ , mChildSrc(nullptr)
+ , mFrameSrc(nullptr)
  , mTokens(aTokens)
  , mSelfURI(aSelfURI)
  , mInnerWindowID(aInnerWindowID)
 {
   CSPPARSERLOG(("nsCSPParser::nsCSPParser"));
 }
 
 nsCSPParser::~nsCSPParser()
@@ -989,16 +991,31 @@ nsCSPParser::directiveName()
     return nullptr;
   }
 
   // special case handling for upgrade-insecure-requests
   if (CSP_IsDirective(mCurToken, nsIContentSecurityPolicy::UPGRADE_IF_INSECURE_DIRECTIVE)) {
     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));
 }
 
 // directive = *WSP [ directive-name [ WSP directive-value ] ]
 void
 nsCSPParser::directive()
 {
   // Set the directiveName to mCurToken
@@ -1081,16 +1098,22 @@ nsCSPParser::policy()
   mPolicy = new nsCSPPolicy();
   for (uint32_t i = 0; i < mTokens.Length(); i++) {
     // All input is already tokenized; set one tokenized array in the form of
     // [ name, src, src, ... ]
     // to mCurDir and call directive which processes the current directive.
     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;
 }
 
 nsCSPPolicy*
 nsCSPParser::parseContentSecurityPolicy(const nsAString& aPolicyString,
                                         nsIURI *aSelfURI,
                                         bool aReportOnly,
                                         uint64_t aInnerWindowID)
--- a/dom/security/nsCSPParser.h
+++ b/dom/security/nsCSPParser.h
@@ -228,15 +228,24 @@ class nsCSPParser {
     nsString           mCurValue;
     nsString           mCurToken;
     nsTArray<nsString> mCurDir;
 
     // cache variables to ignore unsafe-inline if hash or nonce is specified
     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;
     uint64_t           mInnerWindowID; // used for console reporting
 };
 
 #endif /* nsCSPParser_h___ */
--- a/dom/security/nsCSPService.cpp
+++ b/dom/security/nsCSPService.cpp
@@ -101,18 +101,18 @@ CSPService::ShouldLoad(uint32_t aContent
                        nsIURI *aRequestOrigin,
                        nsISupports *aRequestContext,
                        const nsACString &aMimeTypeGuess,
                        nsISupports *aExtra,
                        nsIPrincipal *aRequestPrincipal,
                        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;
   }
 
   if (MOZ_LOG_TEST(gCspPRLog, LogLevel::Debug)) {
     nsAutoCString location;
     aContentLocation->GetSpec(location);
@@ -249,17 +249,17 @@ CSPService::ShouldProcess(uint32_t      
                           nsIURI           *aRequestOrigin,
                           nsISupports      *aRequestContext,
                           const nsACString &aMimeTypeGuess,
                           nsISupports      *aExtra,
                           nsIPrincipal     *aRequestPrincipal,
                           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)
     return NS_ERROR_FAILURE;
 
   *aDecision = nsIContentPolicy::ACCEPT;
   return NS_OK;
 }
@@ -309,17 +309,23 @@ CSPService::AsyncOnChannelRedirect(nsICh
    * information set in the LoadInfo when channels are created.
    *
    * We check if the CSP permits this host for this type of load, if not,
    * we cancel the load now.
    */
   nsCOMPtr<nsIURI> 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)
                   newUri,         // nsIURI
                   nullptr,        // nsIURI
                   nullptr,        // nsISupports
                   EmptyCString(), // ACString - MIME guess
                   originalUri,    // aMimeTypeGuess
--- a/dom/security/nsCSPUtils.cpp
+++ b/dom/security/nsCSPUtils.cpp
@@ -133,30 +133,37 @@ CSP_ContentTypeToDirective(nsContentPoli
   switch (aType) {
     case nsIContentPolicy::TYPE_IMAGE:
     case nsIContentPolicy::TYPE_IMAGESET:
       return nsIContentSecurityPolicy::IMG_SRC_DIRECTIVE;
 
     // 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:
       return nsIContentSecurityPolicy::STYLE_SRC_DIRECTIVE;
 
     case nsIContentPolicy::TYPE_FONT:
       return nsIContentSecurityPolicy::FONT_SRC_DIRECTIVE;
 
     case nsIContentPolicy::TYPE_MEDIA:
       return nsIContentSecurityPolicy::MEDIA_SRC_DIRECTIVE;
 
     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;
 
     case nsIContentPolicy::TYPE_WEBSOCKET:
     case nsIContentPolicy::TYPE_XMLHTTPREQUEST:
     case nsIContentPolicy::TYPE_BEACON:
     case nsIContentPolicy::TYPE_FETCH:
       return nsIContentSecurityPolicy::CONNECT_SRC_DIRECTIVE;
@@ -897,16 +904,21 @@ nsCSPDirective::toDomCSPStruct(mozilla::
       outCSP.mForm_action.Value() = mozilla::Move(srcs);
       return;
 
     case nsIContentSecurityPolicy::UPGRADE_IF_INSECURE_DIRECTIVE:
       outCSP.mUpgrade_insecure_requests.Construct();
       // 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:
       NS_ASSERTION(false, "cannot find directive to convert CSP to JSON");
   }
 }
 
 
@@ -929,16 +941,59 @@ nsCSPDirective::getReportURIs(nsTArray<n
   nsString tmpReportURI;
   for (uint32_t i = 0; i < mSrcs.Length(); i++) {
     tmpReportURI.Truncate();
     mSrcs[i]->toString(tmpReportURI);
     outReportURIs.AppendElement(tmpReportURI);
   }
 }
 
+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)
 : nsCSPDirective(aDirective)
 {
 }
 
 nsUpgradeInsecureDirective::~nsUpgradeInsecureDirective()
--- a/dom/security/nsCSPUtils.h
+++ b/dom/security/nsCSPUtils.h
@@ -65,34 +65,35 @@ void CSP_LogMessage(const nsAString& aMe
 // these strings map to the CSPDirectives in nsIContentSecurityPolicy
 // NOTE: When implementing a new directive, you will need to add it here but also
 // add a corresponding entry to the constants in nsIContentSecurityPolicy.idl
 // and also create an entry for the new directive in
 // nsCSPDirective::toDomCSPStruct() and add it to CSPDictionaries.webidl.
 // 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)
 {
   return CSPStrDirectives[static_cast<uint32_t>(aDir)];
 }
 
 inline CSPDirective CSP_StringToCSPDirective(const nsAString& aDir)
@@ -298,31 +299,55 @@ class nsCSPDirective {
                          bool aReportOnly, bool aUpgradeInsecure) const;
     virtual bool allows(enum CSPKeyword aKeyword, const nsAString& aHashOrNonce) const;
     virtual void toString(nsAString& outStr) const;
     void toDomCSPStruct(mozilla::dom::CSP& outCSP) const;
 
     virtual void addSrcs(const nsTArray<nsCSPBaseSrc*>& 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<nsString> &outReportURIs) const;
 
   private:
     CSPDirective            mDirective;
     nsTArray<nsCSPBaseSrc*> 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 === */
 
 /*
  * Upgrading insecure requests includes the following actors:
  * (1) CSP:
  *     The CSP implementation whitelists the http-request
  *     in case the policy is executed in enforcement mode.
  *     The CSP implementation however does not allow http