Bug 1297156: Include content security policy when structured cloning principals. draft
authorDave Townsend <dtownsend@oxymoronical.com>
Wed, 11 Apr 2018 12:52:47 -0700
changeset 780712 5b5240fde632696af7e009fbf2957e27a2f3735a
parent 780587 cfe6399e142c71966ef58a16cfd52c0b46dc6b1e
child 780713 490748389a401067742e9d307432004dd8334edf
push id106099
push userdtownsend@mozilla.com
push dateWed, 11 Apr 2018 20:47:47 +0000
bugs1297156
milestone61.0a1
Bug 1297156: Include content security policy when structured cloning principals. When JavaScript sends an nsIPrincipal to the main process we use the structured clone algorithm to serialize it. Currently this discards any content security policy on the principal. This change includes that information so the CSP directives can be seen in the main process. MozReview-Commit-ID: 6s48xtaawrf
caps/nsJSPrincipals.cpp
dom/cache/DBSchema.cpp
dom/serviceworkers/ServiceWorkerRegistrar.cpp
dom/serviceworkers/test/gtest/TestReadWrite.cpp
ipc/glue/BackgroundUtils.cpp
ipc/glue/PBackgroundSharedTypes.ipdlh
--- a/caps/nsJSPrincipals.cpp
+++ b/caps/nsJSPrincipals.cpp
@@ -122,17 +122,18 @@ nsJSPrincipals::ReadPrincipals(JSContext
 
     return ReadKnownPrincipalType(aCx, aReader, tag, aOutPrincipals);
 }
 
 static bool
 ReadPrincipalInfo(JSStructuredCloneReader* aReader,
                   OriginAttributes& aAttrs,
                   nsACString& aSpec,
-                  nsACString& aOriginNoSuffix)
+                  nsACString& aOriginNoSuffix,
+                  nsTArray<ContentSecurityPolicy>* aPolicies = nullptr)
 {
     uint32_t suffixLength, specLength;
     if (!JS_ReadUint32Pair(aReader, &suffixLength, &specLength)) {
         return false;
     }
 
     nsAutoCString suffix;
     if (!suffix.SetLength(suffixLength, fallible)) {
@@ -150,32 +151,54 @@ ReadPrincipalInfo(JSStructuredCloneReade
     if (!aSpec.SetLength(specLength, fallible)) {
         return false;
     }
 
     if (!JS_ReadBytes(aReader, aSpec.BeginWriting(), specLength)) {
         return false;
     }
 
-    uint32_t originNoSuffixLength, dummy;
-    if (!JS_ReadUint32Pair(aReader, &originNoSuffixLength, &dummy)) {
+    uint32_t originNoSuffixLength, policyCount;
+    if (!JS_ReadUint32Pair(aReader, &originNoSuffixLength, &policyCount)) {
         return false;
     }
 
-    MOZ_ASSERT(dummy == 0);
+    if (!aPolicies) {
+        MOZ_ASSERT(policyCount == 0);
+    }
 
     if (!aOriginNoSuffix.SetLength(originNoSuffixLength, fallible)) {
         return false;
     }
 
     if (!JS_ReadBytes(aReader, aOriginNoSuffix.BeginWriting(),
                       originNoSuffixLength)) {
         return false;
     }
 
+    for (uint32_t i = 0; i < policyCount; i++) {
+        uint32_t policyLength, reportOnly;
+        if (!JS_ReadUint32Pair(aReader, &policyLength, &reportOnly)) {
+            return false;
+        }
+
+        nsAutoCString policyStr;
+        if (!policyStr.SetLength(policyLength, fallible)) {
+            return false;
+        }
+
+        if (!JS_ReadBytes(aReader, policyStr.BeginWriting(), policyLength)) {
+            return false;
+        }
+
+        if (aPolicies) {
+            aPolicies->AppendElement(ContentSecurityPolicy(NS_ConvertUTF8toUTF16(policyStr), reportOnly));
+        }
+    }
+
     return true;
 }
 
 static bool
 ReadPrincipalInfo(JSStructuredCloneReader* aReader,
                   uint32_t aTag,
                   PrincipalInfo& aInfo)
 {
@@ -210,29 +233,30 @@ ReadPrincipalInfo(JSStructuredCloneReade
             expanded.whitelist().AppendElement(sub);
         }
 
         aInfo = expanded;
     } else if (aTag == SCTAG_DOM_CONTENT_PRINCIPAL) {
         OriginAttributes attrs;
         nsAutoCString spec;
         nsAutoCString originNoSuffix;
-        if (!ReadPrincipalInfo(aReader, attrs, spec, originNoSuffix)) {
+        nsTArray<ContentSecurityPolicy> policies;
+        if (!ReadPrincipalInfo(aReader, attrs, spec, originNoSuffix, &policies)) {
             return false;
         }
 
 #ifdef FUZZING
         if (originNoSuffix.IsEmpty()) {
           return false;
         }
 #endif
 
         MOZ_DIAGNOSTIC_ASSERT(!originNoSuffix.IsEmpty());
 
-        aInfo = ContentPrincipalInfo(attrs, originNoSuffix, spec);
+        aInfo = ContentPrincipalInfo(attrs, originNoSuffix, spec, Move(policies));
     } else {
 #ifdef FUZZING
         return false;
 #else
         MOZ_CRASH("unexpected principal structured clone tag");
 #endif
     }
 
@@ -270,27 +294,42 @@ nsJSPrincipals::ReadKnownPrincipalType(J
     *aOutPrincipals = get(prin.forget().take());
     return true;
 }
 
 static bool
 WritePrincipalInfo(JSStructuredCloneWriter* aWriter,
                    const OriginAttributes& aAttrs,
                    const nsCString& aSpec,
-                   const nsCString& aOriginNoSuffix)
+                   const nsCString& aOriginNoSuffix,
+                   const nsTArray<ContentSecurityPolicy>* aPolicies = nullptr)
 {
   nsAutoCString suffix;
   aAttrs.CreateSuffix(suffix);
+  size_t policyCount = aPolicies ? aPolicies->Length() : 0;
 
-  return JS_WriteUint32Pair(aWriter, suffix.Length(), aSpec.Length()) &&
-         JS_WriteBytes(aWriter, suffix.get(), suffix.Length()) &&
-         JS_WriteBytes(aWriter, aSpec.get(), aSpec.Length()) &&
-         JS_WriteUint32Pair(aWriter, aOriginNoSuffix.Length(), 0) &&
-         JS_WriteBytes(aWriter, aOriginNoSuffix.get(),
-                       aOriginNoSuffix.Length());
+  if (!(JS_WriteUint32Pair(aWriter, suffix.Length(), aSpec.Length()) &&
+        JS_WriteBytes(aWriter, suffix.get(), suffix.Length()) &&
+        JS_WriteBytes(aWriter, aSpec.get(), aSpec.Length()) &&
+        JS_WriteUint32Pair(aWriter, aOriginNoSuffix.Length(), policyCount) &&
+        JS_WriteBytes(aWriter, aOriginNoSuffix.get(),
+                      aOriginNoSuffix.Length()))) {
+    return false;
+  }
+
+  for (uint32_t i = 0; i < policyCount; i++) {
+    nsCString policy;
+    CopyUTF16toUTF8((*aPolicies)[i].policy(), policy);
+    if (!(JS_WriteUint32Pair(aWriter, policy.Length(), (*aPolicies)[i].reportOnly()) &&
+          JS_WriteBytes(aWriter, PromiseFlatCString(policy).get(), policy.Length()))) {
+      return false;
+    }
+  }
+
+  return true;
 }
 
 static bool
 WritePrincipalInfo(JSStructuredCloneWriter* aWriter, const PrincipalInfo& aInfo)
 {
     if (aInfo.type() == PrincipalInfo::TNullPrincipalInfo) {
         const NullPrincipalInfo& nullInfo = aInfo;
         return JS_WriteUint32Pair(aWriter, SCTAG_DOM_NULL_PRINCIPAL, 0) &&
@@ -314,17 +353,17 @@ WritePrincipalInfo(JSStructuredCloneWrit
         }
         return true;
     }
 
     MOZ_ASSERT(aInfo.type() == PrincipalInfo::TContentPrincipalInfo);
     const ContentPrincipalInfo& cInfo = aInfo;
     return JS_WriteUint32Pair(aWriter, SCTAG_DOM_CONTENT_PRINCIPAL, 0) &&
            WritePrincipalInfo(aWriter, cInfo.attrs(), cInfo.spec(),
-                              cInfo.originNoSuffix());
+                              cInfo.originNoSuffix(), &(cInfo.securityPolicies()));
 }
 
 bool
 nsJSPrincipals::write(JSContext* aCx, JSStructuredCloneWriter* aWriter)
 {
     PrincipalInfo info;
     if (NS_WARN_IF(NS_FAILED(PrincipalToPrincipalInfo(this, &info)))) {
         xpc::Throw(aCx, NS_ERROR_DOM_DATA_CLONE_ERR);
--- a/dom/cache/DBSchema.cpp
+++ b/dom/cache/DBSchema.cpp
@@ -2124,18 +2124,20 @@ ReadResponse(mozIStorageConnection* aCon
 #ifdef DEBUG
     nsDependentCSubstring scheme = url->Scheme();
     MOZ_ASSERT(scheme == "http" || scheme == "https" || scheme == "file");
 #endif
 
     nsCString origin;
     url->Origin(origin);
 
+    // CSP is recovered from the headers, no need to initialise it here.
+    nsTArray<mozilla::ipc::ContentSecurityPolicy> policies;
     aSavedResponseOut->mValue.principalInfo() =
-      mozilla::ipc::ContentPrincipalInfo(attrs, origin, specNoSuffix);
+      mozilla::ipc::ContentPrincipalInfo(attrs, origin, specNoSuffix, Move(policies));
   }
 
   bool nullPadding = false;
   rv = state->GetIsNull(6, &nullPadding);
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
 
 #ifdef NIGHTLY_BUILD
   bool shouldUpdateTo26 = false;
--- a/dom/serviceworkers/ServiceWorkerRegistrar.cpp
+++ b/dom/serviceworkers/ServiceWorkerRegistrar.cpp
@@ -116,18 +116,20 @@ CreatePrincipalInfo(nsILineInputStream* 
   }
 
   nsCString origin;
   rv = GetOrigin(aEntry->scope(), origin);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
+  // CSP will be applied during the script load.
+  nsTArray<mozilla::ipc::ContentSecurityPolicy> policies;
   aEntry->principal() =
-    mozilla::ipc::ContentPrincipalInfo(attrs, origin, aEntry->scope());
+    mozilla::ipc::ContentPrincipalInfo(attrs, origin, aEntry->scope(), Move(policies));
 
   return NS_OK;
 }
 
 } // namespace
 
 NS_IMPL_ISUPPORTS(ServiceWorkerRegistrar,
                   nsIObserver,
--- a/dom/serviceworkers/test/gtest/TestReadWrite.cpp
+++ b/dom/serviceworkers/test/gtest/TestReadWrite.cpp
@@ -266,19 +266,21 @@ TEST(ServiceWorkerRegistrar, TestWriteDa
         nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_IMPORTS;
 
       reg.currentWorkerInstalledTime() = PR_Now();
       reg.currentWorkerActivatedTime() = PR_Now();
       reg.lastUpdateTime() = PR_Now();
 
       nsAutoCString spec;
       spec.AppendPrintf("spec write %d", i);
+
+      nsTArray<mozilla::ipc::ContentSecurityPolicy> policies;
       reg.principal() =
         mozilla::ipc::ContentPrincipalInfo(mozilla::OriginAttributes(i, i % 2),
-                                           spec, spec);
+                                           spec, spec, mozilla::Move(policies));
 
       swr->TestRegisterServiceWorker(reg);
     }
 
     nsresult rv = swr->TestWriteData();
     ASSERT_EQ(NS_OK, rv) << "WriteData() should not fail";
   }
 
@@ -818,19 +820,21 @@ TEST(ServiceWorkerRegistrar, TestDedupeW
       reg.currentWorkerHandlesFetch() = true;
       reg.cacheName() =
         NS_ConvertUTF8toUTF16(nsPrintfCString("cacheName write %d", i));
       reg.updateViaCache() =
         nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_IMPORTS;
 
       nsAutoCString spec;
       spec.AppendPrintf("spec write dedupe/%d", i);
+
+      nsTArray<mozilla::ipc::ContentSecurityPolicy> policies;
       reg.principal() =
         mozilla::ipc::ContentPrincipalInfo(mozilla::OriginAttributes(0, false),
-                                           spec, spec);
+                                           spec, spec, mozilla::Move(policies));
 
       swr->TestRegisterServiceWorker(reg);
     }
 
     nsresult rv = swr->TestWriteData();
     ASSERT_EQ(NS_OK, rv) << "WriteData() should not fail";
   }
 
--- a/ipc/glue/BackgroundUtils.cpp
+++ b/ipc/glue/BackgroundUtils.cpp
@@ -19,16 +19,18 @@
 #include "mozilla/LoadInfo.h"
 #include "ContentPrincipal.h"
 #include "NullPrincipal.h"
 #include "nsContentUtils.h"
 #include "nsString.h"
 #include "nsTArray.h"
 #include "mozilla/nsRedirectHistoryEntry.h"
 #include "URIUtils.h"
+#include "mozilla/dom/nsCSPUtils.h"
+#include "mozilla/dom/nsCSPContext.h"
 
 namespace mozilla {
 namespace net {
 class OptionalLoadInfoArgs;
 }
 
 using mozilla::BasePrincipal;
 using mozilla::Maybe;
@@ -101,16 +103,39 @@ PrincipalInfoToPrincipal(const Principal
       // Origin must match what the_new_principal.getOrigin returns.
       nsAutoCString originNoSuffix;
       rv = principal->GetOriginNoSuffix(originNoSuffix);
       if (NS_WARN_IF(NS_FAILED(rv)) ||
           !info.originNoSuffix().Equals(originNoSuffix)) {
         MOZ_CRASH("Origin must be available when deserialized");
       }
 
+      if (info.securityPolicies().Length() > 0) {
+        nsCOMPtr<nsIContentSecurityPolicy> csp = do_CreateInstance(NS_CSPCONTEXT_CONTRACTID, &rv);
+        if (NS_WARN_IF(NS_FAILED(rv))) {
+          return nullptr;
+        }
+
+        rv = csp->SetRequestContext(nullptr, principal);
+        if (NS_WARN_IF(NS_FAILED(rv))) {
+          return nullptr;
+        }
+
+        for (uint32_t i = 0; i < info.securityPolicies().Length(); i++) {
+          rv = csp->AppendPolicy(info.securityPolicies()[i].policy(),
+                                 info.securityPolicies()[i].reportOnly(),
+                                 false);
+          if (NS_WARN_IF(NS_FAILED(rv))) {
+            return nullptr;
+          }
+        }
+
+        principal->SetCsp(csp);
+      }
+
       return principal.forget();
     }
 
     case PrincipalInfo::TExpandedPrincipalInfo: {
       const ExpandedPrincipalInfo& info = aPrincipalInfo.get_ExpandedPrincipalInfo();
 
       nsTArray<nsCOMPtr<nsIPrincipal>> whitelist;
       nsCOMPtr<nsIPrincipal> wlPrincipal;
@@ -231,18 +256,41 @@ PrincipalToPrincipalInfo(nsIPrincipal* a
   }
 
   nsCString originNoSuffix;
   rv = aPrincipal->GetOriginNoSuffix(originNoSuffix);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
+  nsCOMPtr<nsIContentSecurityPolicy> csp;
+  rv = aPrincipal->GetCsp(getter_AddRefs(csp));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  nsTArray<ContentSecurityPolicy> policies;
+  if (csp) {
+    uint32_t count;
+    rv = csp->GetPolicyCount(&count);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    for (uint32_t i = 0; i < count; i++) {
+      const nsCSPPolicy* policy = csp->GetPolicy(i);
+      nsString str;
+      policy->toString(str);
+
+      policies.AppendElement(ContentSecurityPolicy(str, policy->getReportOnlyFlag()));
+    }
+  }
+
   *aPrincipalInfo = ContentPrincipalInfo(aPrincipal->OriginAttributesRef(),
-                                         originNoSuffix, spec);
+                                         originNoSuffix, spec, Move(policies));
   return NS_OK;
 }
 
 bool
 IsPincipalInfoPrivate(const PrincipalInfo& aPrincipalInfo)
 {
   if (aPrincipalInfo.type() != ipc::PrincipalInfo::TContentPrincipalInfo) {
     return false;
--- a/ipc/glue/PBackgroundSharedTypes.ipdlh
+++ b/ipc/glue/PBackgroundSharedTypes.ipdlh
@@ -3,30 +3,38 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 using mozilla::OriginAttributes from "mozilla/ipc/BackgroundUtils.h";
 using struct mozilla::void_t from "ipc/IPCMessageUtils.h";
 
 namespace mozilla {
 namespace ipc {
 
+struct ContentSecurityPolicy
+{
+  nsString policy;
+  bool reportOnly;
+};
+
 struct ContentPrincipalInfo
 {
   OriginAttributes attrs;
 
   // Origin is not simply a part of the spec. Based on the scheme of the URI
   // spec, we generate different kind of origins: for instance any file: URL
   // shares the same origin, about: URLs have the full spec as origin and so
   // on.
   // Another important reason why we have this attribute is that
   // ContentPrincipalInfo is used out of the main-thread. Having this value
   // here allows us to retrive the origin without creating a full nsIPrincipal.
   nsCString originNoSuffix;
 
   nsCString spec;
+
+  ContentSecurityPolicy[] securityPolicies;
 };
 
 struct SystemPrincipalInfo
 { };
 
 struct NullPrincipalInfo
 {
   OriginAttributes attrs;