Bug 959388 - Deliver CSP from HTTP header. r=ckerschb,khuey
MozReview-Commit-ID: JS2DV6kI9Bi
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -177,16 +177,17 @@
#include "SVGElementFactory.h"
#include "nsRefreshDriver.h"
// FOR CSP (autogenerated by xpidl)
#include "nsIContentSecurityPolicy.h"
#include "mozilla/dom/nsCSPContext.h"
#include "mozilla/dom/nsCSPService.h"
+#include "mozilla/dom/nsCSPUtils.h"
#include "nsHTMLStyleSheet.h"
#include "nsHTMLCSSStyleSheet.h"
#include "SVGAttrAnimationRuleProcessor.h"
#include "mozilla/dom/DOMImplementation.h"
#include "mozilla/dom/ShadowRoot.h"
#include "mozilla/dom/Comment.h"
#include "nsTextNode.h"
#include "mozilla/dom/Link.h"
@@ -2643,39 +2644,16 @@ nsDocument::SendToConsole(nsCOMArray<nsI
nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
NS_ConvertUTF16toUTF8(category),
this, nsContentUtils::eSECURITY_PROPERTIES,
NS_ConvertUTF16toUTF8(messageTag).get());
}
}
-static nsresult
-AppendCSPFromHeader(nsIContentSecurityPolicy* csp,
- const nsAString& aHeaderValue,
- bool aReportOnly)
-{
- // Need to tokenize the header value since multiple headers could be
- // concatenated into one comma-separated list of policies.
- // See RFC2616 section 4.2 (last paragraph)
- nsresult rv = NS_OK;
- nsCharSeparatedTokenizer tokenizer(aHeaderValue, ',');
- while (tokenizer.hasMoreTokens()) {
- const nsSubstring& policy = tokenizer.nextToken();
- rv = csp->AppendPolicy(policy, aReportOnly, false);
- NS_ENSURE_SUCCESS(rv, rv);
- {
- MOZ_LOG(gCspPRLog, LogLevel::Debug,
- ("CSP refined with policy: \"%s\"",
- NS_ConvertUTF16toUTF8(policy).get()));
- }
- }
- return NS_OK;
-}
-
bool
nsDocument::IsLoopDocument(nsIChannel *aChannel)
{
nsCOMPtr<nsIURI> chanURI;
nsresult rv = aChannel->GetOriginalURI(getter_AddRefs(chanURI));
NS_ENSURE_SUCCESS(rv, false);
bool isAbout = false;
@@ -2926,23 +2904,23 @@ nsDocument::InitCSP(nsIChannel* aChannel
// If the pref has been removed, we continue without setting a CSP
if (loopCSP) {
csp->AppendPolicy(loopCSP, false, false);
}
}
// ----- if there's a full-strength CSP header, apply it.
if (!cspHeaderValue.IsEmpty()) {
- rv = AppendCSPFromHeader(csp, cspHeaderValue, false);
+ rv = CSP_AppendCSPFromHeader(csp, cspHeaderValue, false);
NS_ENSURE_SUCCESS(rv, rv);
}
// ----- if there's a report-only CSP header, apply it.
if (!cspROHeaderValue.IsEmpty()) {
- rv = AppendCSPFromHeader(csp, cspROHeaderValue, true);
+ rv = CSP_AppendCSPFromHeader(csp, cspROHeaderValue, true);
NS_ENSURE_SUCCESS(rv, rv);
}
// ----- Enforce frame-ancestor policy on any applied policies
nsCOMPtr<nsIDocShell> docShell(mDocumentContainer);
if (docShell) {
bool safeAncestry = false;
--- a/dom/security/nsCSPUtils.cpp
+++ b/dom/security/nsCSPUtils.cpp
@@ -308,16 +308,49 @@ permitsScheme(const nsAString& aEnforcem
// See nsHttpChannel::Connect() and also WebSocket.cpp. Please note,
// the report only policies should not allow the load and report
// the error back to the page.
return ((aUpgradeInsecure && !aReportOnly) &&
((scheme.EqualsASCII("http") && aEnforcementScheme.EqualsASCII("https")) ||
(scheme.EqualsASCII("ws") && aEnforcementScheme.EqualsASCII("wss"))));
}
+/*
+ * A helper function for appending a CSP header to an existing CSP
+ * policy.
+ *
+ * @param aCsp the CSP policy
+ * @param aHeaderValue the header
+ * @param aReportOnly is this a report-only header?
+ */
+
+nsresult
+CSP_AppendCSPFromHeader(nsIContentSecurityPolicy* aCsp,
+ const nsAString& aHeaderValue,
+ bool aReportOnly)
+{
+ NS_ENSURE_ARG(aCsp);
+
+ // Need to tokenize the header value since multiple headers could be
+ // concatenated into one comma-separated list of policies.
+ // See RFC2616 section 4.2 (last paragraph)
+ nsresult rv = NS_OK;
+ nsCharSeparatedTokenizer tokenizer(aHeaderValue, ',');
+ while (tokenizer.hasMoreTokens()) {
+ const nsSubstring& policy = tokenizer.nextToken();
+ rv = aCsp->AppendPolicy(policy, aReportOnly, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ {
+ CSPUTILSLOG(("CSP refined with policy: \"%s\"",
+ NS_ConvertUTF16toUTF8(policy).get()));
+ }
+ }
+ return NS_OK;
+}
+
/* ===== nsCSPSrc ============================ */
nsCSPBaseSrc::nsCSPBaseSrc()
{
}
nsCSPBaseSrc::~nsCSPBaseSrc()
{
--- a/dom/security/nsCSPUtils.h
+++ b/dom/security/nsCSPUtils.h
@@ -171,16 +171,20 @@ inline CSPKeyword CSP_KeywordToEnum(cons
if (lowerKey.EqualsASCII(CSPStrKeywords[i])) {
return static_cast<CSPKeyword>(i);
}
}
NS_ASSERTION(false, "Can not convert unknown Keyword to Enum");
return CSP_LAST_KEYWORD_VALUE;
}
+nsresult CSP_AppendCSPFromHeader(nsIContentSecurityPolicy* aCsp,
+ const nsAString& aHeaderValue,
+ bool aReportOnly);
+
/* =============== Helpers ================== */
class nsCSPHostSrc;
nsCSPHostSrc* CSP_CreateHostSrcFromURI(nsIURI* aURI);
bool CSP_IsValidDirective(const nsAString& aDir);
bool CSP_IsDirective(const nsAString& aValue, CSPDirective aDir);
bool CSP_IsKeyword(const nsAString& aValue, enum CSPKeyword aKey);
--- a/dom/workers/ScriptLoader.cpp
+++ b/dom/workers/ScriptLoader.cpp
@@ -5,16 +5,17 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "ScriptLoader.h"
#include "nsIChannel.h"
#include "nsIContentPolicy.h"
#include "nsIContentSecurityPolicy.h"
#include "nsIDocShell.h"
+#include "nsIDOMDocument.h"
#include "nsIHttpChannel.h"
#include "nsIHttpChannelInternal.h"
#include "nsIInputStreamPump.h"
#include "nsIIOService.h"
#include "nsIProtocolHandler.h"
#include "nsIScriptError.h"
#include "nsIScriptSecurityManager.h"
#include "nsIStreamLoader.h"
@@ -47,16 +48,18 @@
#include "mozilla/ipc/BackgroundUtils.h"
#include "mozilla/dom/CacheBinding.h"
#include "mozilla/dom/cache/CacheTypes.h"
#include "mozilla/dom/cache/Cache.h"
#include "mozilla/dom/cache/CacheStorage.h"
#include "mozilla/dom/ChannelInfo.h"
#include "mozilla/dom/Exceptions.h"
#include "mozilla/dom/InternalResponse.h"
+#include "mozilla/dom/nsCSPService.h"
+#include "mozilla/dom/nsCSPUtils.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/dom/PromiseNativeHandler.h"
#include "mozilla/dom/Response.h"
#include "mozilla/dom/ScriptSettings.h"
#include "mozilla/UniquePtr.h"
#include "Principal.h"
#include "WorkerHolder.h"
#include "WorkerPrivate.h"
@@ -1037,24 +1040,34 @@ private:
// but then give it a different origin.
aLoadInfo.mMutedErrorFlag.emplace(IsMainWorkerScript()
? false
: !principal->Subsumes(channelPrincipal));
// Make sure we're not seeing the result of a 404 or something by checking
// the 'requestSucceeded' attribute on the http channel.
nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(request);
+ nsAutoCString tCspHeaderValue, tCspROHeaderValue;
+
if (httpChannel) {
bool requestSucceeded;
rv = httpChannel->GetRequestSucceeded(&requestSucceeded);
NS_ENSURE_SUCCESS(rv, rv);
if (!requestSucceeded) {
return NS_ERROR_NOT_AVAILABLE;
}
+
+ httpChannel->GetResponseHeader(
+ NS_LITERAL_CSTRING("content-security-policy"),
+ tCspHeaderValue);
+
+ httpChannel->GetResponseHeader(
+ NS_LITERAL_CSTRING("content-security-policy-report-only"),
+ tCspROHeaderValue);
}
// May be null.
nsIDocument* parentDoc = mWorkerPrivate->GetDocument();
// Use the regular nsScriptLoader for this grunt work! Should be just fine
// because we're running on the main thread.
// Unlike <script> tags, Worker scripts are always decoded as UTF-8,
@@ -1150,19 +1163,58 @@ private:
}
}
// The principal can change, but it should still match the original
// load group's appId and browser element flag.
MOZ_ASSERT(NS_LoadGroupMatchesPrincipal(channelLoadGroup, channelPrincipal));
mWorkerPrivate->SetPrincipal(channelPrincipal, channelLoadGroup);
+
+ // We did inherit CSP in bug 1223647. If we do not already have a CSP, we
+ // should get it from the HTTP headers on the worker script.
+ if (!mWorkerPrivate->GetCSP() && CSPService::sCSPEnabled) {
+ NS_ConvertASCIItoUTF16 cspHeaderValue(tCspHeaderValue);
+ NS_ConvertASCIItoUTF16 cspROHeaderValue(tCspROHeaderValue);
+
+ nsIPrincipal* principal = mWorkerPrivate->GetPrincipal();
+ MOZ_ASSERT(principal, "Should not be null");
+
+ nsCOMPtr<nsIContentSecurityPolicy> csp;
+ rv = principal->EnsureCSP(nullptr, getter_AddRefs(csp));
+
+ if (csp) {
+ // If there's a CSP header, apply it.
+ if (!cspHeaderValue.IsEmpty()) {
+ rv = CSP_AppendCSPFromHeader(csp, cspHeaderValue, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ // If there's a report-only CSP header, apply it.
+ if (!cspROHeaderValue.IsEmpty()) {
+ rv = CSP_AppendCSPFromHeader(csp, cspROHeaderValue, true);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // Set evalAllowed, default value is set in GetAllowsEval
+ bool evalAllowed = false;
+ bool reportEvalViolations = false;
+ rv = csp->GetAllowsEval(&reportEvalViolations, &evalAllowed);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mWorkerPrivate->SetCSP(csp);
+ mWorkerPrivate->SetEvalAllowed(evalAllowed);
+ mWorkerPrivate->SetReportCSPViolations(reportEvalViolations);
+ }
+ }
+ if (parent) {
+ // XHR Params Allowed
+ mWorkerPrivate->SetXHRParamsAllowed(parent->XHRParamsAllowed());
+ }
}
- DataReceived();
return NS_OK;
}
void
DataReceivedFromCache(uint32_t aIndex, const uint8_t* aString,
uint32_t aStringLen,
const mozilla::dom::ChannelInfo& aChannelInfo,
UniquePtr<PrincipalInfo> aPrincipalInfo)
--- a/dom/workers/WorkerPrivate.h
+++ b/dom/workers/WorkerPrivate.h
@@ -664,16 +664,22 @@ public:
}
bool
GetReportCSPViolations() const
{
return mLoadInfo.mReportCSPViolations;
}
+ void
+ SetReportCSPViolations(bool aReport)
+ {
+ mLoadInfo.mReportCSPViolations = aReport;
+ }
+
bool
XHRParamsAllowed() const
{
return mLoadInfo.mXHRParamsAllowed;
}
void
SetXHRParamsAllowed(bool aAllowed)