Bug 1530854: Always create CSP on Principal so the explicit CSP in the nsISHEntry holds a reference to the potentially dynamically modified CSP in case of a meta CSP. r=bzbarsky
authorChristoph Kerschbaumer <ckerschb@christophkerschbaumer.com>
Thu, 14 Mar 2019 06:26:29 +0000
changeset 521866 30b70cd830631e09d18fafe5937fdbc4ed4b1134
parent 521865 0607c72b4d88f2481152837fb94000b383e051dc
child 521867 2c24099776a7c46fa4e764984901e386f5d83fd5
push id10870
push usernbeleuzu@mozilla.com
push dateFri, 15 Mar 2019 20:00:07 +0000
treeherdermozilla-beta@c594aee5b7a4 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbzbarsky
bugs1530854
milestone67.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1530854: Always create CSP on Principal so the explicit CSP in the nsISHEntry holds a reference to the potentially dynamically modified CSP in case of a meta CSP. r=bzbarsky Differential Revision: https://phabricator.services.mozilla.com/D21919
caps/nsScriptSecurityManager.cpp
dom/base/Document.cpp
dom/security/nsCSPContext.cpp
dom/security/test/csp/test_data_doc_ignore_meta_csp.html
--- a/caps/nsScriptSecurityManager.cpp
+++ b/caps/nsScriptSecurityManager.cpp
@@ -284,22 +284,79 @@ static void InheritAndSetCSPOnPrincipalI
 
   // if the principalToInherit had a CSP, add it to the before
   // created NullPrincipal (unless it already has one)
   MOZ_ASSERT(aPrincipal->GetIsNullPrincipal(),
              "inheriting the CSP only valid for NullPrincipal");
   nsCOMPtr<nsIContentSecurityPolicy> nullPrincipalCSP;
   aPrincipal->GetCsp(getter_AddRefs(nullPrincipalCSP));
   if (nullPrincipalCSP) {
-    MOZ_ASSERT(nullPrincipalCSP == originalCSP,
-               "There should be no other CSP here.");
+#ifdef DEBUG
+    {
+      uint32_t nullPrincipalCSPCount = 0;
+      nullPrincipalCSP->GetPolicyCount(&nullPrincipalCSPCount);
+
+      uint32_t originalCSPCount = 0;
+      originalCSP->GetPolicyCount(&originalCSPCount);
+
+      MOZ_ASSERT(nullPrincipalCSPCount == originalCSPCount,
+                 "There should be no other CSP here.");
+
+      nsAutoString nullPrincipalCSPStr, originalCSPStr;
+      for (uint32_t i = 0; i < originalCSPCount; ++i) {
+        originalCSP->GetPolicyString(i, originalCSPStr);
+        nullPrincipalCSP->GetPolicyString(i, nullPrincipalCSPStr);
+        MOZ_ASSERT(originalCSPStr.Equals(nullPrincipalCSPStr),
+                   "There should be no other CSP string here.");
+      }
+    }
+#endif
     // CSPs are equal, no need to set it again.
     return;
   }
-  aPrincipal->SetCsp(originalCSP);
+
+  // After 965637 all that magical CSP inheritance goes away. For now,
+  // we have to create a clone of the current CSP and have to manually
+  // set it on the Principal.
+  uint32_t count = 0;
+  rv = originalCSP->GetPolicyCount(&count);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return;
+  }
+
+  if (count == 0) {
+    // fast path: if there is nothing to inherit, we can return here.
+    return;
+  }
+
+  RefPtr<nsCSPContext> newCSP = new nsCSPContext();
+  nsWeakPtr loadingContext =
+      static_cast<nsCSPContext*>(originalCSP.get())->GetLoadingContext();
+  nsCOMPtr<Document> doc = do_QueryReferent(loadingContext);
+
+  rv = doc ? newCSP->SetRequestContext(doc, nullptr)
+           : newCSP->SetRequestContext(nullptr, aPrincipal);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return;
+  }
+
+  for (uint32_t i = 0; i < count; ++i) {
+    const nsCSPPolicy* policy = originalCSP->GetPolicy(i);
+    MOZ_ASSERT(policy);
+
+    nsAutoString policyString;
+    policy->toString(policyString);
+
+    rv = newCSP->AppendPolicy(policyString, policy->getReportOnlyFlag(),
+                              policy->getDeliveredViaMetaTagFlag());
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return;
+    }
+  }
+  aPrincipal->SetCsp(newCSP);
 }
 
 nsresult nsScriptSecurityManager::GetChannelResultPrincipal(
     nsIChannel* aChannel, nsIPrincipal** aPrincipal, bool aIgnoreSandboxing) {
   MOZ_ASSERT(aChannel, "Must have channel!");
 
   // Check whether we have an nsILoadInfo that says what we should do.
   nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
--- a/dom/base/Document.cpp
+++ b/dom/base/Document.cpp
@@ -2540,24 +2540,21 @@ nsresult Document::StartDocumentLoad(con
       mUpgradeInsecureRequests = doc->GetUpgradeInsecureRequests(false);
       // if the parent document makes use of upgrade-insecure-requests
       // then subdocument preloads should always be upgraded.
       mUpgradeInsecurePreloads =
           mUpgradeInsecureRequests || doc->GetUpgradeInsecureRequests(true);
     }
   }
 
-  // If this is not a data document, set CSP.
-  if (!mLoadedAsData) {
-    nsresult rv = InitCSP(aChannel);
-    NS_ENSURE_SUCCESS(rv, rv);
-  }
+  nsresult rv = InitCSP(aChannel);
+  NS_ENSURE_SUCCESS(rv, rv);
 
   // Initialize FeaturePolicy
-  nsresult rv = InitFeaturePolicy(aChannel);
+  rv = InitFeaturePolicy(aChannel);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // XFO needs to be checked after CSP because it is ignored if
   // the CSP defines frame-ancestors.
   if (!FramingChecker::CheckFrameOptions(aChannel, docShell, NodePrincipal())) {
     MOZ_LOG(gCspPRLog, LogLevel::Debug,
             ("XFO doesn't like frame's ancestry, not loading."));
     // stop!  ERROR page!
@@ -2645,16 +2642,21 @@ nsresult Document::InitCSP(nsIChannel* a
   MOZ_ASSERT(!mScriptGlobalObject,
              "CSP must be initialized before mScriptGlobalObject is set!");
   if (!StaticPrefs::security_csp_enable()) {
     MOZ_LOG(gCspPRLog, LogLevel::Debug,
             ("CSP is disabled, skipping CSP init for document %p", this));
     return NS_OK;
   }
 
+  // If this is a data document - no need to set CSP.
+  if (mLoadedAsData) {
+    return NS_OK;
+  }
+
   nsAutoCString tCspHeaderValue, tCspROHeaderValue;
 
   nsCOMPtr<nsIHttpChannel> httpChannel;
   nsresult rv = GetHttpChannelHelper(aChannel, getter_AddRefs(httpChannel));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
@@ -2668,16 +2670,30 @@ nsresult Document::InitCSP(nsIChannel* a
   }
   NS_ConvertASCIItoUTF16 cspHeaderValue(tCspHeaderValue);
   NS_ConvertASCIItoUTF16 cspROHeaderValue(tCspROHeaderValue);
 
   // Check if this is a document from a WebExtension.
   nsCOMPtr<nsIPrincipal> principal = NodePrincipal();
   auto addonPolicy = BasePrincipal::Cast(principal)->AddonPolicy();
 
+  // Unless the NodePrincipal is a SystemPrincipal, which currently can not
+  // hold a CSP, we always call EnsureCSP() on the Principal. We do this
+  // so that a Meta CSP does not have to create a new CSP object. This is
+  // important because history entries hold a reference to the CSP object,
+  // which then gets dynamically updated in case a meta CSP is present.
+  // Note that after Bug 965637 we can remove that carve out, because the
+  // CSP will hang off the Client/Document and not the Principal anymore.
+  if (principal->IsSystemPrincipal()) {
+    return NS_OK;
+  }
+  nsCOMPtr<nsIContentSecurityPolicy> csp;
+  rv = principal->EnsureCSP(this, getter_AddRefs(csp));
+  NS_ENSURE_SUCCESS(rv, rv);
+
   // Check if this is a signed content to apply default CSP.
   bool applySignedContentCSP = false;
   nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
   if (loadInfo->GetVerifySignedContent()) {
     applySignedContentCSP = true;
   }
 
   // If there's no CSP to apply, go ahead and return early
@@ -2693,20 +2709,16 @@ nsresult Document::InitCSP(nsIChannel* a
     }
 
     return NS_OK;
   }
 
   MOZ_LOG(gCspPRLog, LogLevel::Debug,
           ("Document is an add-on or CSP header specified %p", this));
 
-  nsCOMPtr<nsIContentSecurityPolicy> csp;
-  rv = principal->EnsureCSP(this, getter_AddRefs(csp));
-  NS_ENSURE_SUCCESS(rv, rv);
-
   // ----- if the doc is an addon, apply its CSP.
   if (addonPolicy) {
     nsCOMPtr<nsIAddonPolicyService> aps =
         do_GetService("@mozilla.org/addons/policy-service;1");
 
     nsAutoString addonCSP;
     Unused << ExtensionPolicyService::GetSingleton().GetBaseCSP(addonCSP);
     csp->AppendPolicy(addonCSP, false, false);
--- a/dom/security/nsCSPContext.cpp
+++ b/dom/security/nsCSPContext.cpp
@@ -377,17 +377,24 @@ nsCSPContext::AppendPolicy(const nsAStri
       referrer = NS_ConvertUTF16toUTF8(mReferrer);
       CSPCONTEXTLOG(
           ("nsCSPContext::AppendPolicy added UPGRADE_IF_INSECURE_DIRECTIVE "
            "self-uri=%s referrer=%s",
            selfURIspec.get(), referrer.get()));
     }
 
     mPolicies.AppendElement(policy);
+
+    // set the flag on the document for CSP telemetry
+    nsCOMPtr<Document> doc = do_QueryReferent(mLoadingContext);
+    if (doc) {
+      doc->SetHasCSP(true);
+    }
   }
+
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsCSPContext::GetAllowsEval(bool* outShouldReportViolation,
                             bool* outAllowsEval) {
   EnsureIPCPoliciesRead();
   *outShouldReportViolation = false;
@@ -667,19 +674,16 @@ nsCSPContext::SetRequestContext(Document
     mLoadingPrincipal = aDocument->NodePrincipal();
     aDocument->GetReferrer(mReferrer);
     mInnerWindowID = aDocument->InnerWindowID();
     // the innerWindowID is not available for CSPs delivered through the
     // header at the time setReqeustContext is called - let's queue up
     // console messages until it becomes available, see flushConsoleMessages
     mQueueUpMessages = !mInnerWindowID;
     mCallingChannelLoadGroup = aDocument->GetDocumentLoadGroup();
-
-    // set the flag on the document for CSP telemetry
-    aDocument->SetHasCSP(true);
     mEventTarget = aDocument->EventTargetFor(TaskCategory::Other);
   } else {
     CSPCONTEXTLOG(
         ("No Document in SetRequestContext; can not query loadgroup; sending "
          "reports may fail."));
     mLoadingPrincipal = aPrincipal;
     mLoadingPrincipal->GetURI(getter_AddRefs(mSelfURI));
     // if no document is available, then it also does not make sense to queue
--- a/dom/security/test/csp/test_data_doc_ignore_meta_csp.html
+++ b/dom/security/test/csp/test_data_doc_ignore_meta_csp.html
@@ -21,17 +21,17 @@ SimpleTest.waitForExplicitFinish();
 window.addEventListener("message", receiveMessage);
 function receiveMessage(event) {
   window.removeEventListener("message", receiveMessage);
   is(event.data.result, "dataDocCreated", "sanity: received msg from loaded frame");
 
   var frame = document.getElementById("testframe");
   var principal = SpecialPowers.wrap(frame.contentDocument).nodePrincipal;
   var cspJSON = principal.cspJSON;
-  is(cspJSON, "{}", "there should be no CSP attached to the iframe");
+  is(cspJSON, "{\"csp-policies\":[]}", "there should be no CSP attached to the iframe");
   SimpleTest.finish();
 }
 
 document.getElementById("testframe").src = "file_data_doc_ignore_meta_csp.html";
 
 </script>
 </body>
 </html>