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
--- 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;