summaryrefslogtreecommitdiffstats
path: root/dom/security/nsCSPContext.cpp
diff options
context:
space:
mode:
authorMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
committerMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
commit5f8de423f190bbb79a62f804151bc24824fa32d8 (patch)
tree10027f336435511475e392454359edea8e25895d /dom/security/nsCSPContext.cpp
parent49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff)
downloadUXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip
Add m-esr52 at 52.6.0
Diffstat (limited to 'dom/security/nsCSPContext.cpp')
-rw-r--r--dom/security/nsCSPContext.cpp1567
1 files changed, 1567 insertions, 0 deletions
diff --git a/dom/security/nsCSPContext.cpp b/dom/security/nsCSPContext.cpp
new file mode 100644
index 000000000..815c7734d
--- /dev/null
+++ b/dom/security/nsCSPContext.cpp
@@ -0,0 +1,1567 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsCOMPtr.h"
+#include "nsContentPolicyUtils.h"
+#include "nsContentUtils.h"
+#include "nsCSPContext.h"
+#include "nsCSPParser.h"
+#include "nsCSPService.h"
+#include "nsError.h"
+#include "nsIAsyncVerifyRedirectCallback.h"
+#include "nsIClassInfoImpl.h"
+#include "nsIDocShell.h"
+#include "nsIDocShellTreeItem.h"
+#include "nsIDOMHTMLDocument.h"
+#include "nsIDOMHTMLElement.h"
+#include "nsIDOMNode.h"
+#include "nsIHttpChannel.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIObjectInputStream.h"
+#include "nsIObjectOutputStream.h"
+#include "nsIObserver.h"
+#include "nsIObserverService.h"
+#include "nsIStringStream.h"
+#include "nsIUploadChannel.h"
+#include "nsIScriptError.h"
+#include "nsIWebNavigation.h"
+#include "nsMimeTypes.h"
+#include "nsNetUtil.h"
+#include "nsIContentPolicy.h"
+#include "nsSupportsPrimitives.h"
+#include "nsThreadUtils.h"
+#include "nsString.h"
+#include "nsScriptSecurityManager.h"
+#include "nsStringStream.h"
+#include "mozilla/Logging.h"
+#include "mozilla/dom/CSPReportBinding.h"
+#include "mozilla/dom/CSPDictionariesBinding.h"
+#include "mozilla/net/ReferrerPolicy.h"
+#include "nsINetworkInterceptController.h"
+#include "nsSandboxFlags.h"
+#include "nsIScriptElement.h"
+
+using namespace mozilla;
+
+static LogModule*
+GetCspContextLog()
+{
+ static LazyLogModule gCspContextPRLog("CSPContext");
+ return gCspContextPRLog;
+}
+
+#define CSPCONTEXTLOG(args) MOZ_LOG(GetCspContextLog(), mozilla::LogLevel::Debug, args)
+#define CSPCONTEXTLOGENABLED() MOZ_LOG_TEST(GetCspContextLog(), mozilla::LogLevel::Debug)
+
+static const uint32_t CSP_CACHE_URI_CUTOFF_SIZE = 512;
+
+/**
+ * Creates a key for use in the ShouldLoad cache.
+ * Looks like: <uri>!<nsIContentPolicy::LOAD_TYPE>
+ */
+nsresult
+CreateCacheKey_Internal(nsIURI* aContentLocation,
+ nsContentPolicyType aContentType,
+ nsACString& outCacheKey)
+{
+ if (!aContentLocation) {
+ return NS_ERROR_FAILURE;
+ }
+
+ bool isDataScheme = false;
+ nsresult rv = aContentLocation->SchemeIs("data", &isDataScheme);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ outCacheKey.Truncate();
+ if (aContentType != nsIContentPolicy::TYPE_SCRIPT && isDataScheme) {
+ // For non-script data: URI, use ("data:", aContentType) as the cache key.
+ outCacheKey.Append(NS_LITERAL_CSTRING("data:"));
+ outCacheKey.AppendInt(aContentType);
+ return NS_OK;
+ }
+
+ nsAutoCString spec;
+ rv = aContentLocation->GetSpec(spec);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Don't cache for a URI longer than the cutoff size.
+ if (spec.Length() <= CSP_CACHE_URI_CUTOFF_SIZE) {
+ outCacheKey.Append(spec);
+ outCacheKey.Append(NS_LITERAL_CSTRING("!"));
+ outCacheKey.AppendInt(aContentType);
+ }
+
+ return NS_OK;
+}
+
+/* ===== nsIContentSecurityPolicy impl ====== */
+
+NS_IMETHODIMP
+nsCSPContext::ShouldLoad(nsContentPolicyType aContentType,
+ nsIURI* aContentLocation,
+ nsIURI* aRequestOrigin,
+ nsISupports* aRequestContext,
+ const nsACString& aMimeTypeGuess,
+ nsISupports* aExtra,
+ int16_t* outDecision)
+{
+ if (CSPCONTEXTLOGENABLED()) {
+ CSPCONTEXTLOG(("nsCSPContext::ShouldLoad, aContentLocation: %s",
+ aContentLocation->GetSpecOrDefault().get()));
+ CSPCONTEXTLOG((">>>> aContentType: %d", aContentType));
+ }
+
+ bool isPreload = nsContentUtils::IsPreloadType(aContentType);
+
+ // 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.
+ // 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
+ // * Content Type is not whitelisted (CSP Reports, TYPE_DOCUMENT, etc).
+ // * Fast Path for Apps
+
+ nsAutoCString cacheKey;
+ rv = CreateCacheKey_Internal(aContentLocation, aContentType, cacheKey);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool isCached = mShouldLoadCache.Get(cacheKey, outDecision);
+ if (isCached && cacheKey.Length() > 0) {
+ // this is cached, use the cached value.
+ return NS_OK;
+ }
+
+ // Default decision, CSP can revise it if there's a policy to enforce
+ *outDecision = nsIContentPolicy::ACCEPT;
+
+ // If the content type doesn't map to a CSP directive, there's nothing for
+ // CSP to do.
+ CSPDirective dir = CSP_ContentTypeToDirective(aContentType);
+ if (dir == nsIContentSecurityPolicy::NO_DIRECTIVE) {
+ return NS_OK;
+ }
+
+ nsAutoString nonce;
+ bool parserCreated = false;
+ if (!isPreload) {
+ nsCOMPtr<nsIDOMHTMLElement> htmlElement = do_QueryInterface(aRequestContext);
+ if (htmlElement) {
+ rv = htmlElement->GetAttribute(NS_LITERAL_STRING("nonce"), nonce);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ nsCOMPtr<nsIScriptElement> script = do_QueryInterface(aRequestContext);
+ if (script && script->GetParserCreated() != mozilla::dom::NOT_FROM_PARSER) {
+ parserCreated = true;
+ }
+ }
+
+ // aExtra is only non-null if the channel got redirected.
+ bool wasRedirected = (aExtra != nullptr);
+ nsCOMPtr<nsIURI> originalURI = do_QueryInterface(aExtra);
+
+ bool permitted = permitsInternal(dir,
+ aContentLocation,
+ originalURI,
+ nonce,
+ wasRedirected,
+ isPreload,
+ false, // allow fallback to default-src
+ true, // send violation reports
+ true, // send blocked URI in violation reports
+ parserCreated);
+
+ *outDecision = permitted ? nsIContentPolicy::ACCEPT
+ : nsIContentPolicy::REJECT_SERVER;
+
+ // Done looping, cache any relevant result
+ if (cacheKey.Length() > 0 && !isPreload) {
+ mShouldLoadCache.Put(cacheKey, *outDecision);
+ }
+
+ if (CSPCONTEXTLOGENABLED()) {
+ CSPCONTEXTLOG(("nsCSPContext::ShouldLoad, decision: %s, "
+ "aContentLocation: %s",
+ *outDecision > 0 ? "load" : "deny",
+ aContentLocation->GetSpecOrDefault().get()));
+ }
+ return NS_OK;
+}
+
+bool
+nsCSPContext::permitsInternal(CSPDirective aDir,
+ nsIURI* aContentLocation,
+ nsIURI* aOriginalURI,
+ const nsAString& aNonce,
+ bool aWasRedirected,
+ bool aIsPreload,
+ bool aSpecific,
+ bool aSendViolationReports,
+ bool aSendContentLocationInViolationReports,
+ bool aParserCreated)
+{
+ bool permits = true;
+
+ nsAutoString violatedDirective;
+ for (uint32_t p = 0; p < mPolicies.Length(); p++) {
+
+ // According to the W3C CSP spec, frame-ancestors checks are ignored for
+ // report-only policies (when "monitoring").
+ if (aDir == nsIContentSecurityPolicy::FRAME_ANCESTORS_DIRECTIVE &&
+ mPolicies[p]->getReportOnlyFlag()) {
+ continue;
+ }
+
+ if (!mPolicies[p]->permits(aDir,
+ aContentLocation,
+ aNonce,
+ aWasRedirected,
+ aSpecific,
+ aParserCreated,
+ violatedDirective)) {
+ // If the policy is violated and not report-only, reject the load and
+ // report to the console
+ if (!mPolicies[p]->getReportOnlyFlag()) {
+ CSPCONTEXTLOG(("nsCSPContext::permitsInternal, false"));
+ permits = false;
+ }
+
+ // Do not send a report or notify observers if this is a preload - the
+ // decision may be wrong due to the inability to get the nonce, and will
+ // incorrectly fail the unit tests.
+ if (!aIsPreload && aSendViolationReports) {
+ this->AsyncReportViolation((aSendContentLocationInViolationReports ?
+ aContentLocation : nullptr),
+ aOriginalURI, /* in case of redirect originalURI is not null */
+ violatedDirective,
+ p, /* policy index */
+ EmptyString(), /* no observer subject */
+ EmptyString(), /* no source file */
+ EmptyString(), /* no script sample */
+ 0); /* no line number */
+ }
+ }
+ }
+
+ return permits;
+}
+
+
+
+/* ===== nsISupports implementation ========== */
+
+NS_IMPL_CLASSINFO(nsCSPContext,
+ nullptr,
+ nsIClassInfo::MAIN_THREAD_ONLY,
+ NS_CSPCONTEXT_CID)
+
+NS_IMPL_ISUPPORTS_CI(nsCSPContext,
+ nsIContentSecurityPolicy,
+ nsISerializable)
+
+nsCSPContext::nsCSPContext()
+ : mInnerWindowID(0)
+ , mLoadingContext(nullptr)
+ , mLoadingPrincipal(nullptr)
+ , mQueueUpMessages(true)
+{
+ CSPCONTEXTLOG(("nsCSPContext::nsCSPContext"));
+}
+
+nsCSPContext::~nsCSPContext()
+{
+ CSPCONTEXTLOG(("nsCSPContext::~nsCSPContext"));
+ for (uint32_t i = 0; i < mPolicies.Length(); i++) {
+ delete mPolicies[i];
+ }
+ mShouldLoadCache.Clear();
+}
+
+NS_IMETHODIMP
+nsCSPContext::GetPolicyString(uint32_t aIndex, nsAString& outStr)
+{
+ if (aIndex >= mPolicies.Length()) {
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+ mPolicies[aIndex]->toString(outStr);
+ return NS_OK;
+}
+
+const nsCSPPolicy*
+nsCSPContext::GetPolicy(uint32_t aIndex)
+{
+ if (aIndex >= mPolicies.Length()) {
+ return nullptr;
+ }
+ return mPolicies[aIndex];
+}
+
+NS_IMETHODIMP
+nsCSPContext::GetPolicyCount(uint32_t *outPolicyCount)
+{
+ *outPolicyCount = mPolicies.Length();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCSPContext::GetUpgradeInsecureRequests(bool *outUpgradeRequest)
+{
+ *outUpgradeRequest = false;
+ for (uint32_t i = 0; i < mPolicies.Length(); i++) {
+ if (mPolicies[i]->hasDirective(nsIContentSecurityPolicy::UPGRADE_IF_INSECURE_DIRECTIVE)) {
+ *outUpgradeRequest = true;
+ return NS_OK;
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCSPContext::GetBlockAllMixedContent(bool *outBlockAllMixedContent)
+{
+ *outBlockAllMixedContent = false;
+ for (uint32_t i = 0; i < mPolicies.Length(); i++) {
+ if (!mPolicies[i]->getReportOnlyFlag() &&
+ mPolicies[i]->hasDirective(nsIContentSecurityPolicy::BLOCK_ALL_MIXED_CONTENT)) {
+ *outBlockAllMixedContent = true;
+ return NS_OK;
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCSPContext::GetReferrerPolicy(uint32_t* outPolicy, bool* outIsSet)
+{
+ *outIsSet = false;
+ *outPolicy = mozilla::net::RP_Default;
+ nsAutoString refpol;
+ mozilla::net::ReferrerPolicy previousPolicy = mozilla::net::RP_Default;
+ for (uint32_t i = 0; i < mPolicies.Length(); i++) {
+ mPolicies[i]->getReferrerPolicy(refpol);
+ // only set the referrer policy if not delievered through a CSPRO and
+ // note that and an empty string in refpol means it wasn't set
+ // (that's the default in nsCSPPolicy).
+ if (!mPolicies[i]->getReportOnlyFlag() && !refpol.IsEmpty()) {
+ // Referrer Directive in CSP is no more used and going to be replaced by
+ // Referrer-Policy HTTP header. But we still keep using referrer directive,
+ // and would remove it later.
+ // Referrer Directive specs is not fully compliant with new referrer policy
+ // specs. What we are using here:
+ // - If the value of the referrer directive is invalid, the user agent
+ // should set the referrer policy to no-referrer.
+ // - If there are two policies that specify a referrer policy, then they
+ // must agree or the employed policy is no-referrer.
+ if (!mozilla::net::IsValidReferrerPolicy(refpol)) {
+ *outPolicy = mozilla::net::RP_No_Referrer;
+ *outIsSet = true;
+ return NS_OK;
+ }
+
+ uint32_t currentPolicy = mozilla::net::ReferrerPolicyFromString(refpol);
+ if (*outIsSet && previousPolicy != currentPolicy) {
+ *outPolicy = mozilla::net::RP_No_Referrer;
+ return NS_OK;
+ }
+
+ *outPolicy = currentPolicy;
+ *outIsSet = true;
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCSPContext::AppendPolicy(const nsAString& aPolicyString,
+ bool aReportOnly,
+ bool aDeliveredViaMetaTag)
+{
+ CSPCONTEXTLOG(("nsCSPContext::AppendPolicy: %s",
+ NS_ConvertUTF16toUTF8(aPolicyString).get()));
+
+ // Use the mSelfURI from setRequestContext, see bug 991474
+ NS_ASSERTION(mSelfURI, "mSelfURI required for AppendPolicy, but not set");
+ nsCSPPolicy* policy = nsCSPParser::parseContentSecurityPolicy(aPolicyString, mSelfURI,
+ aReportOnly, this,
+ aDeliveredViaMetaTag);
+ if (policy) {
+ mPolicies.AppendElement(policy);
+ // reset cache since effective policy changes
+ mShouldLoadCache.Clear();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCSPContext::GetAllowsEval(bool* outShouldReportViolation,
+ bool* outAllowsEval)
+{
+ *outShouldReportViolation = false;
+ *outAllowsEval = true;
+
+ for (uint32_t i = 0; i < mPolicies.Length(); i++) {
+ if (!mPolicies[i]->allows(nsIContentPolicy::TYPE_SCRIPT,
+ CSP_UNSAFE_EVAL,
+ EmptyString(),
+ false)) {
+ // policy is violated: must report the violation and allow the inline
+ // script if the policy is report-only.
+ *outShouldReportViolation = true;
+ if (!mPolicies[i]->getReportOnlyFlag()) {
+ *outAllowsEval = false;
+ }
+ }
+ }
+ return NS_OK;
+}
+
+// Helper function to report inline violations
+void
+nsCSPContext::reportInlineViolation(nsContentPolicyType aContentType,
+ const nsAString& aNonce,
+ const nsAString& aContent,
+ const nsAString& aViolatedDirective,
+ uint32_t aViolatedPolicyIndex, // TODO, use report only flag for that
+ uint32_t aLineNumber)
+{
+ nsString observerSubject;
+ // if the nonce is non empty, then we report the nonce error, otherwise
+ // let's report the hash error; no need to report the unsafe-inline error
+ // anymore.
+ if (!aNonce.IsEmpty()) {
+ observerSubject = (aContentType == nsIContentPolicy::TYPE_SCRIPT)
+ ? NS_LITERAL_STRING(SCRIPT_NONCE_VIOLATION_OBSERVER_TOPIC)
+ : NS_LITERAL_STRING(STYLE_NONCE_VIOLATION_OBSERVER_TOPIC);
+ }
+ else {
+ observerSubject = (aContentType == nsIContentPolicy::TYPE_SCRIPT)
+ ? NS_LITERAL_STRING(SCRIPT_HASH_VIOLATION_OBSERVER_TOPIC)
+ : NS_LITERAL_STRING(STYLE_HASH_VIOLATION_OBSERVER_TOPIC);
+ }
+
+ nsCOMPtr<nsISupportsCString> selfICString(do_CreateInstance(NS_SUPPORTS_CSTRING_CONTRACTID));
+ if (selfICString) {
+ selfICString->SetData(nsDependentCString("self"));
+ }
+ nsCOMPtr<nsISupports> selfISupports(do_QueryInterface(selfICString));
+
+ // use selfURI as the sourceFile
+ nsAutoCString sourceFile;
+ if (mSelfURI) {
+ mSelfURI->GetSpec(sourceFile);
+ }
+
+ nsAutoString codeSample(aContent);
+ // cap the length of the script sample at 40 chars
+ if (codeSample.Length() > 40) {
+ codeSample.Truncate(40);
+ codeSample.AppendLiteral("...");
+ }
+ AsyncReportViolation(selfISupports, // aBlockedContentSource
+ mSelfURI, // aOriginalURI
+ aViolatedDirective, // aViolatedDirective
+ aViolatedPolicyIndex, // aViolatedPolicyIndex
+ observerSubject, // aObserverSubject
+ NS_ConvertUTF8toUTF16(sourceFile), // aSourceFile
+ codeSample, // aScriptSample
+ aLineNumber); // aLineNum
+}
+
+NS_IMETHODIMP
+nsCSPContext::GetAllowsInline(nsContentPolicyType aContentType,
+ const nsAString& aNonce,
+ bool aParserCreated,
+ const nsAString& aContent,
+ uint32_t aLineNumber,
+ bool* outAllowsInline)
+{
+ *outAllowsInline = true;
+
+ MOZ_ASSERT(aContentType == nsContentUtils::InternalContentPolicyTypeToExternal(aContentType),
+ "We should only see external content policy types here.");
+
+ if (aContentType != nsIContentPolicy::TYPE_SCRIPT &&
+ aContentType != nsIContentPolicy::TYPE_STYLESHEET) {
+ MOZ_ASSERT(false, "can only allow inline for script or style");
+ return NS_OK;
+ }
+
+ // always iterate all policies, otherwise we might not send out all reports
+ for (uint32_t i = 0; i < mPolicies.Length(); i++) {
+ bool allowed =
+ mPolicies[i]->allows(aContentType, CSP_UNSAFE_INLINE, EmptyString(), aParserCreated) ||
+ mPolicies[i]->allows(aContentType, CSP_NONCE, aNonce, aParserCreated) ||
+ mPolicies[i]->allows(aContentType, CSP_HASH, aContent, aParserCreated);
+
+ if (!allowed) {
+ // policy is violoated: deny the load unless policy is report only and
+ // report the violation.
+ if (!mPolicies[i]->getReportOnlyFlag()) {
+ *outAllowsInline = false;
+ }
+ nsAutoString violatedDirective;
+ mPolicies[i]->getDirectiveStringForContentType(aContentType, violatedDirective);
+ reportInlineViolation(aContentType,
+ aNonce,
+ aContent,
+ violatedDirective,
+ i,
+ aLineNumber);
+ }
+ }
+ return NS_OK;
+}
+
+
+/**
+ * Reduces some code repetition for the various logging situations in
+ * LogViolationDetails.
+ *
+ * Call-sites for the eval/inline checks recieve two return values: allows
+ * and violates. Based on those, they must choose whether to call
+ * LogViolationDetails or not. Policies that are report-only allow the
+ * loads/compilations but violations should still be reported. Not all
+ * policies in this nsIContentSecurityPolicy instance will be violated,
+ * which is why we must check allows() again here.
+ *
+ * Note: This macro uses some parameters from its caller's context:
+ * p, mPolicies, this, aSourceFile, aScriptSample, aLineNum, selfISupports
+ *
+ * @param violationType: the VIOLATION_TYPE_* constant (partial symbol)
+ * such as INLINE_SCRIPT
+ * @param contentPolicyType: a constant from nsIContentPolicy such as TYPE_STYLESHEET
+ * @param nonceOrHash: for NONCE and HASH violations, it's the nonce or content
+ * string. For other violations, it is an empty string.
+ * @param keyword: the keyword corresponding to violation (UNSAFE_INLINE for most)
+ * @param observerTopic: the observer topic string to send with the CSP
+ * observer notifications.
+ *
+ * Please note that inline violations for scripts are reported within
+ * GetAllowsInline() and do not call this macro, hence we can pass 'false'
+ * as the argument _aParserCreated_ to allows().
+ */
+#define CASE_CHECK_AND_REPORT(violationType, contentPolicyType, nonceOrHash, \
+ keyword, observerTopic) \
+ case nsIContentSecurityPolicy::VIOLATION_TYPE_ ## violationType : \
+ PR_BEGIN_MACRO \
+ if (!mPolicies[p]->allows(nsIContentPolicy::TYPE_ ## contentPolicyType, \
+ keyword, nonceOrHash, false)) \
+ { \
+ nsAutoString violatedDirective; \
+ mPolicies[p]->getDirectiveStringForContentType( \
+ nsIContentPolicy::TYPE_ ## contentPolicyType, \
+ violatedDirective); \
+ this->AsyncReportViolation(selfISupports, nullptr, violatedDirective, p, \
+ NS_LITERAL_STRING(observerTopic), \
+ aSourceFile, aScriptSample, aLineNum); \
+ } \
+ PR_END_MACRO; \
+ break
+
+/**
+ * For each policy, log any violation on the Error Console and send a report
+ * if a report-uri is present in the policy
+ *
+ * @param aViolationType
+ * one of the VIOLATION_TYPE_* constants, e.g. inline-script or eval
+ * @param aSourceFile
+ * name of the source file containing the violation (if available)
+ * @param aContentSample
+ * sample of the violating content (to aid debugging)
+ * @param aLineNum
+ * source line number of the violation (if available)
+ * @param aNonce
+ * (optional) If this is a nonce violation, include the nonce so we can
+ * recheck to determine which policies were violated and send the
+ * appropriate reports.
+ * @param aContent
+ * (optional) If this is a hash violation, include contents of the inline
+ * resource in the question so we can recheck the hash in order to
+ * determine which policies were violated and send the appropriate
+ * reports.
+ */
+NS_IMETHODIMP
+nsCSPContext::LogViolationDetails(uint16_t aViolationType,
+ const nsAString& aSourceFile,
+ const nsAString& aScriptSample,
+ int32_t aLineNum,
+ const nsAString& aNonce,
+ const nsAString& aContent)
+{
+ for (uint32_t p = 0; p < mPolicies.Length(); p++) {
+ NS_ASSERTION(mPolicies[p], "null pointer in nsTArray<nsCSPPolicy>");
+
+ nsCOMPtr<nsISupportsCString> selfICString(do_CreateInstance(NS_SUPPORTS_CSTRING_CONTRACTID));
+ if (selfICString) {
+ selfICString->SetData(nsDependentCString("self"));
+ }
+ nsCOMPtr<nsISupports> selfISupports(do_QueryInterface(selfICString));
+
+ switch (aViolationType) {
+ CASE_CHECK_AND_REPORT(EVAL, SCRIPT, NS_LITERAL_STRING(""),
+ CSP_UNSAFE_EVAL, EVAL_VIOLATION_OBSERVER_TOPIC);
+ CASE_CHECK_AND_REPORT(INLINE_STYLE, STYLESHEET, NS_LITERAL_STRING(""),
+ CSP_UNSAFE_INLINE, INLINE_STYLE_VIOLATION_OBSERVER_TOPIC);
+ CASE_CHECK_AND_REPORT(INLINE_SCRIPT, SCRIPT, NS_LITERAL_STRING(""),
+ CSP_UNSAFE_INLINE, INLINE_SCRIPT_VIOLATION_OBSERVER_TOPIC);
+ CASE_CHECK_AND_REPORT(NONCE_SCRIPT, SCRIPT, aNonce,
+ CSP_UNSAFE_INLINE, SCRIPT_NONCE_VIOLATION_OBSERVER_TOPIC);
+ CASE_CHECK_AND_REPORT(NONCE_STYLE, STYLESHEET, aNonce,
+ CSP_UNSAFE_INLINE, STYLE_NONCE_VIOLATION_OBSERVER_TOPIC);
+ CASE_CHECK_AND_REPORT(HASH_SCRIPT, SCRIPT, aContent,
+ CSP_UNSAFE_INLINE, SCRIPT_HASH_VIOLATION_OBSERVER_TOPIC);
+ CASE_CHECK_AND_REPORT(HASH_STYLE, STYLESHEET, aContent,
+ CSP_UNSAFE_INLINE, STYLE_HASH_VIOLATION_OBSERVER_TOPIC);
+ CASE_CHECK_AND_REPORT(REQUIRE_SRI_FOR_STYLE, STYLESHEET, NS_LITERAL_STRING(""),
+ CSP_REQUIRE_SRI_FOR, REQUIRE_SRI_STYLE_VIOLATION_OBSERVER_TOPIC);
+ CASE_CHECK_AND_REPORT(REQUIRE_SRI_FOR_SCRIPT, SCRIPT, NS_LITERAL_STRING(""),
+ CSP_REQUIRE_SRI_FOR, REQUIRE_SRI_SCRIPT_VIOLATION_OBSERVER_TOPIC);
+
+
+ default:
+ NS_ASSERTION(false, "LogViolationDetails with invalid type");
+ break;
+ }
+ }
+ return NS_OK;
+}
+
+#undef CASE_CHECK_AND_REPORT
+
+NS_IMETHODIMP
+nsCSPContext::SetRequestContext(nsIDOMDocument* aDOMDocument,
+ nsIPrincipal* aPrincipal)
+{
+ NS_PRECONDITION(aDOMDocument || aPrincipal,
+ "Can't set context without doc or principal");
+ NS_ENSURE_ARG(aDOMDocument || aPrincipal);
+
+ if (aDOMDocument) {
+ nsCOMPtr<nsIDocument> doc = do_QueryInterface(aDOMDocument);
+ mLoadingContext = do_GetWeakReference(doc);
+ mSelfURI = doc->GetDocumentURI();
+ mLoadingPrincipal = doc->NodePrincipal();
+ doc->GetReferrer(mReferrer);
+ mInnerWindowID = doc->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 = doc->GetDocumentLoadGroup();
+
+ // set the flag on the document for CSP telemetry
+ doc->SetHasCSP(true);
+ }
+ 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 console messages
+ // sending messages to the browser conolse instead of the web console in that case.
+ mQueueUpMessages = false;
+ }
+
+ NS_ASSERTION(mSelfURI, "mSelfURI not available, can not translate 'self' into actual URI");
+ return NS_OK;
+}
+
+struct ConsoleMsgQueueElem {
+ nsXPIDLString mMsg;
+ nsString mSourceName;
+ nsString mSourceLine;
+ uint32_t mLineNumber;
+ uint32_t mColumnNumber;
+ uint32_t mSeverityFlag;
+};
+
+void
+nsCSPContext::flushConsoleMessages()
+{
+ // should flush messages even if doc is not available
+ nsCOMPtr<nsIDocument> doc = do_QueryReferent(mLoadingContext);
+ if (doc) {
+ mInnerWindowID = doc->InnerWindowID();
+ }
+ mQueueUpMessages = false;
+
+ for (uint32_t i = 0; i < mConsoleMsgQueue.Length(); i++) {
+ ConsoleMsgQueueElem &elem = mConsoleMsgQueue[i];
+ CSP_LogMessage(elem.mMsg, elem.mSourceName, elem.mSourceLine,
+ elem.mLineNumber, elem.mColumnNumber,
+ elem.mSeverityFlag, "CSP", mInnerWindowID);
+ }
+ mConsoleMsgQueue.Clear();
+}
+
+void
+nsCSPContext::logToConsole(const char16_t* aName,
+ const char16_t** aParams,
+ uint32_t aParamsLength,
+ const nsAString& aSourceName,
+ const nsAString& aSourceLine,
+ uint32_t aLineNumber,
+ uint32_t aColumnNumber,
+ uint32_t aSeverityFlag)
+{
+ // let's check if we have to queue up console messages
+ if (mQueueUpMessages) {
+ nsXPIDLString msg;
+ CSP_GetLocalizedStr(aName, aParams, aParamsLength, getter_Copies(msg));
+ ConsoleMsgQueueElem &elem = *mConsoleMsgQueue.AppendElement();
+ elem.mMsg = msg;
+ elem.mSourceName = PromiseFlatString(aSourceName);
+ elem.mSourceLine = PromiseFlatString(aSourceLine);
+ elem.mLineNumber = aLineNumber;
+ elem.mColumnNumber = aColumnNumber;
+ elem.mSeverityFlag = aSeverityFlag;
+ return;
+ }
+ CSP_LogLocalizedStr(aName, aParams, aParamsLength, aSourceName,
+ aSourceLine, aLineNumber, aColumnNumber,
+ aSeverityFlag, "CSP", mInnerWindowID);
+}
+
+/**
+ * Strip URI for reporting according to:
+ * http://www.w3.org/TR/CSP/#violation-reports
+ *
+ * @param aURI
+ * The uri to be stripped for reporting
+ * @param aSelfURI
+ * The uri of the protected resource
+ * which is needed to enforce the SOP.
+ * @return ASCII serialization of the uri to be reported.
+ */
+void
+StripURIForReporting(nsIURI* aURI,
+ nsIURI* aSelfURI,
+ nsACString& outStrippedURI)
+{
+ // 1) If the origin of uri is a globally unique identifier (for example,
+ // aURI has a scheme of data, blob, or filesystem), then return the
+ // ASCII serialization of uri’s scheme.
+ bool isHttpOrFtp =
+ (NS_SUCCEEDED(aURI->SchemeIs("http", &isHttpOrFtp)) && isHttpOrFtp) ||
+ (NS_SUCCEEDED(aURI->SchemeIs("https", &isHttpOrFtp)) && isHttpOrFtp) ||
+ (NS_SUCCEEDED(aURI->SchemeIs("ftp", &isHttpOrFtp)) && isHttpOrFtp);
+
+ if (!isHttpOrFtp) {
+ // not strictly spec compliant, but what we really care about is
+ // http/https and also ftp. If it's not http/https or ftp, then treat aURI
+ // as if it's a globally unique identifier and just return the scheme.
+ aURI->GetScheme(outStrippedURI);
+ return;
+ }
+
+ // 2) If the origin of uri is not the same as the origin of the protected
+ // resource, then return the ASCII serialization of uri’s origin.
+ if (!NS_SecurityCompareURIs(aSelfURI, aURI, false)) {
+ // cross origin redirects also fall into this category, see:
+ // http://www.w3.org/TR/CSP/#violation-reports
+ aURI->GetPrePath(outStrippedURI);
+ return;
+ }
+
+ // 3) Return uri, with any fragment component removed.
+ aURI->GetSpecIgnoringRef(outStrippedURI);
+}
+
+/**
+ * Sends CSP violation reports to all sources listed under report-uri.
+ *
+ * @param aBlockedContentSource
+ * Either a CSP Source (like 'self', as string) or nsIURI: the source
+ * of the violation.
+ * @param aOriginalUri
+ * The original URI if the blocked content is a redirect, else null
+ * @param aViolatedDirective
+ * the directive that was violated (string).
+ * @param aSourceFile
+ * name of the file containing the inline script violation
+ * @param aScriptSample
+ * a sample of the violating inline script
+ * @param aLineNum
+ * source line number of the violation (if available)
+ */
+nsresult
+nsCSPContext::SendReports(nsISupports* aBlockedContentSource,
+ nsIURI* aOriginalURI,
+ nsAString& aViolatedDirective,
+ uint32_t aViolatedPolicyIndex,
+ nsAString& aSourceFile,
+ nsAString& aScriptSample,
+ uint32_t aLineNum)
+{
+ NS_ENSURE_ARG_MAX(aViolatedPolicyIndex, mPolicies.Length() - 1);
+
+#ifdef MOZ_B2G
+ // load group information (on process-split necko implementations like b2g).
+ // (fix this in bug 1011086)
+ if (!mCallingChannelLoadGroup) {
+ NS_WARNING("Load group required but not present for report sending; cannot send CSP violation reports");
+ return NS_ERROR_FAILURE;
+ }
+#endif
+
+ dom::CSPReport report;
+ nsresult rv;
+
+ // blocked-uri
+ if (aBlockedContentSource) {
+ nsAutoCString reportBlockedURI;
+ nsCOMPtr<nsIURI> uri = do_QueryInterface(aBlockedContentSource);
+ // could be a string or URI
+ if (uri) {
+ StripURIForReporting(uri, mSelfURI, reportBlockedURI);
+ } else {
+ nsCOMPtr<nsISupportsCString> cstr = do_QueryInterface(aBlockedContentSource);
+ if (cstr) {
+ cstr->GetData(reportBlockedURI);
+ }
+ }
+ if (reportBlockedURI.IsEmpty()) {
+ // this can happen for frame-ancestors violation where the violating
+ // ancestor is cross-origin.
+ NS_WARNING("No blocked URI (null aBlockedContentSource) for CSP violation report.");
+ }
+ report.mCsp_report.mBlocked_uri = NS_ConvertUTF8toUTF16(reportBlockedURI);
+ }
+
+ // document-uri
+ nsAutoCString reportDocumentURI;
+ StripURIForReporting(mSelfURI, mSelfURI, reportDocumentURI);
+ report.mCsp_report.mDocument_uri = NS_ConvertUTF8toUTF16(reportDocumentURI);
+
+ // original-policy
+ nsAutoString originalPolicy;
+ rv = this->GetPolicyString(aViolatedPolicyIndex, originalPolicy);
+ NS_ENSURE_SUCCESS(rv, rv);
+ report.mCsp_report.mOriginal_policy = originalPolicy;
+
+ // referrer
+ if (!mReferrer.IsEmpty()) {
+ report.mCsp_report.mReferrer = mReferrer;
+ }
+
+ // violated-directive
+ report.mCsp_report.mViolated_directive = aViolatedDirective;
+
+ // source-file
+ if (!aSourceFile.IsEmpty()) {
+ // if aSourceFile is a URI, we have to make sure to strip fragments
+ nsCOMPtr<nsIURI> sourceURI;
+ NS_NewURI(getter_AddRefs(sourceURI), aSourceFile);
+ if (sourceURI) {
+ nsAutoCString spec;
+ sourceURI->GetSpecIgnoringRef(spec);
+ aSourceFile = NS_ConvertUTF8toUTF16(spec);
+ }
+ report.mCsp_report.mSource_file.Construct();
+ report.mCsp_report.mSource_file.Value() = aSourceFile;
+ }
+
+ // script-sample
+ if (!aScriptSample.IsEmpty()) {
+ report.mCsp_report.mScript_sample.Construct();
+ report.mCsp_report.mScript_sample.Value() = aScriptSample;
+ }
+
+ // line-number
+ if (aLineNum != 0) {
+ report.mCsp_report.mLine_number.Construct();
+ report.mCsp_report.mLine_number.Value() = aLineNum;
+ }
+
+ nsString csp_report;
+ if (!report.ToJSON(csp_report)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // ---------- Assembled, now send it to all the report URIs ----------- //
+
+ nsTArray<nsString> reportURIs;
+ mPolicies[aViolatedPolicyIndex]->getReportURIs(reportURIs);
+
+
+ nsCOMPtr<nsIDocument> doc = do_QueryReferent(mLoadingContext);
+ nsCOMPtr<nsIURI> reportURI;
+ nsCOMPtr<nsIChannel> reportChannel;
+
+ for (uint32_t r = 0; r < reportURIs.Length(); r++) {
+ nsAutoCString reportURICstring = NS_ConvertUTF16toUTF8(reportURIs[r]);
+ // try to create a new uri from every report-uri string
+ rv = NS_NewURI(getter_AddRefs(reportURI), reportURIs[r]);
+ if (NS_FAILED(rv)) {
+ const char16_t* params[] = { reportURIs[r].get() };
+ CSPCONTEXTLOG(("Could not create nsIURI for report URI %s",
+ reportURICstring.get()));
+ logToConsole(u"triedToSendReport", params, ArrayLength(params),
+ aSourceFile, aScriptSample, aLineNum, 0, nsIScriptError::errorFlag);
+ continue; // don't return yet, there may be more URIs
+ }
+
+ // try to create a new channel for every report-uri
+ nsLoadFlags loadFlags = nsIRequest::LOAD_NORMAL | nsIChannel::LOAD_CLASSIFY_URI;
+ if (doc) {
+ rv = NS_NewChannel(getter_AddRefs(reportChannel),
+ reportURI,
+ doc,
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
+ nsIContentPolicy::TYPE_CSP_REPORT,
+ nullptr, // aLoadGroup
+ nullptr, // aCallbacks
+ loadFlags);
+ }
+ else {
+ rv = NS_NewChannel(getter_AddRefs(reportChannel),
+ reportURI,
+ mLoadingPrincipal,
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
+ nsIContentPolicy::TYPE_CSP_REPORT,
+ nullptr, // aLoadGroup
+ nullptr, // aCallbacks
+ loadFlags);
+ }
+
+ if (NS_FAILED(rv)) {
+ CSPCONTEXTLOG(("Could not create new channel for report URI %s",
+ reportURICstring.get()));
+ continue; // don't return yet, there may be more URIs
+ }
+
+ // log a warning to console if scheme is not http or https
+ bool isHttpScheme =
+ (NS_SUCCEEDED(reportURI->SchemeIs("http", &isHttpScheme)) && isHttpScheme) ||
+ (NS_SUCCEEDED(reportURI->SchemeIs("https", &isHttpScheme)) && isHttpScheme);
+
+ if (!isHttpScheme) {
+ const char16_t* params[] = { reportURIs[r].get() };
+ logToConsole(u"reportURInotHttpsOrHttp2", params, ArrayLength(params),
+ aSourceFile, aScriptSample, aLineNum, 0, nsIScriptError::errorFlag);
+ continue;
+ }
+
+ // make sure this is an anonymous request (no cookies) so in case the
+ // policy URI is injected, it can't be abused for CSRF.
+ nsLoadFlags flags;
+ rv = reportChannel->GetLoadFlags(&flags);
+ NS_ENSURE_SUCCESS(rv, rv);
+ flags |= nsIRequest::LOAD_ANONYMOUS;
+ rv = reportChannel->SetLoadFlags(flags);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // we need to set an nsIChannelEventSink on the channel object
+ // so we can tell it to not follow redirects when posting the reports
+ RefPtr<CSPReportRedirectSink> reportSink = new CSPReportRedirectSink();
+ if (doc && doc->GetDocShell()) {
+ nsCOMPtr<nsINetworkInterceptController> interceptController =
+ do_QueryInterface(doc->GetDocShell());
+ reportSink->SetInterceptController(interceptController);
+ }
+ reportChannel->SetNotificationCallbacks(reportSink);
+
+ // apply the loadgroup from the channel taken by setRequestContext. If
+ // there's no loadgroup, AsyncOpen will fail on process-split necko (since
+ // the channel cannot query the iTabChild).
+ rv = reportChannel->SetLoadGroup(mCallingChannelLoadGroup);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // wire in the string input stream to send the report
+ nsCOMPtr<nsIStringInputStream> sis(do_CreateInstance(NS_STRINGINPUTSTREAM_CONTRACTID));
+ NS_ASSERTION(sis, "nsIStringInputStream is needed but not available to send CSP violation reports");
+ nsAutoCString utf8CSPReport = NS_ConvertUTF16toUTF8(csp_report);
+ rv = sis->SetData(utf8CSPReport.get(), utf8CSPReport.Length());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIUploadChannel> uploadChannel(do_QueryInterface(reportChannel));
+ if (!uploadChannel) {
+ // It's possible the URI provided can't be uploaded to, in which case
+ // we skip this one. We'll already have warned about a non-HTTP URI earlier.
+ continue;
+ }
+
+ rv = uploadChannel->SetUploadStream(sis, NS_LITERAL_CSTRING("application/csp-report"), -1);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // if this is an HTTP channel, set the request method to post
+ nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(reportChannel));
+ if (httpChannel) {
+ httpChannel->SetRequestMethod(NS_LITERAL_CSTRING("POST"));
+ }
+
+ RefPtr<CSPViolationReportListener> listener = new CSPViolationReportListener();
+ rv = reportChannel->AsyncOpen2(listener);
+
+ // AsyncOpen should not fail, but could if there's no load group (like if
+ // SetRequestContext is not given a channel). This should fail quietly and
+ // not return an error since it's really ok if reports don't go out, but
+ // it's good to log the error locally.
+
+ if (NS_FAILED(rv)) {
+ const char16_t* params[] = { reportURIs[r].get() };
+ CSPCONTEXTLOG(("AsyncOpen failed for report URI %s", params[0]));
+ logToConsole(u"triedToSendReport", params, ArrayLength(params),
+ aSourceFile, aScriptSample, aLineNum, 0, nsIScriptError::errorFlag);
+ } else {
+ CSPCONTEXTLOG(("Sent violation report to URI %s", reportURICstring.get()));
+ }
+ }
+ return NS_OK;
+}
+
+/**
+ * Dispatched from the main thread to send reports for one CSP violation.
+ */
+class CSPReportSenderRunnable final : public Runnable
+{
+ public:
+ CSPReportSenderRunnable(nsISupports* aBlockedContentSource,
+ nsIURI* aOriginalURI,
+ uint32_t aViolatedPolicyIndex,
+ bool aReportOnlyFlag,
+ const nsAString& aViolatedDirective,
+ const nsAString& aObserverSubject,
+ const nsAString& aSourceFile,
+ const nsAString& aScriptSample,
+ uint32_t aLineNum,
+ nsCSPContext* aCSPContext)
+ : mBlockedContentSource(aBlockedContentSource)
+ , mOriginalURI(aOriginalURI)
+ , mViolatedPolicyIndex(aViolatedPolicyIndex)
+ , mReportOnlyFlag(aReportOnlyFlag)
+ , mViolatedDirective(aViolatedDirective)
+ , mSourceFile(aSourceFile)
+ , mScriptSample(aScriptSample)
+ , mLineNum(aLineNum)
+ , mCSPContext(aCSPContext)
+ {
+ NS_ASSERTION(!aViolatedDirective.IsEmpty(), "Can not send reports without a violated directive");
+ // the observer subject is an nsISupports: either an nsISupportsCString
+ // from the arg passed in directly, or if that's empty, it's the blocked
+ // source.
+ if (aObserverSubject.IsEmpty()) {
+ mObserverSubject = aBlockedContentSource;
+ } else {
+ nsCOMPtr<nsISupportsCString> supportscstr =
+ do_CreateInstance(NS_SUPPORTS_CSTRING_CONTRACTID);
+ NS_ASSERTION(supportscstr, "Couldn't allocate nsISupportsCString");
+ supportscstr->SetData(NS_ConvertUTF16toUTF8(aObserverSubject));
+ mObserverSubject = do_QueryInterface(supportscstr);
+ }
+ }
+
+ NS_IMETHOD Run() override
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // 1) notify observers
+ nsCOMPtr<nsIObserverService> observerService = mozilla::services::GetObserverService();
+ NS_ASSERTION(observerService, "needs observer service");
+ nsresult rv = observerService->NotifyObservers(mObserverSubject,
+ CSP_VIOLATION_TOPIC,
+ mViolatedDirective.get());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // 2) send reports for the policy that was violated
+ mCSPContext->SendReports(mBlockedContentSource, mOriginalURI,
+ mViolatedDirective, mViolatedPolicyIndex,
+ mSourceFile, mScriptSample, mLineNum);
+
+ // 3) log to console (one per policy violation)
+ // mBlockedContentSource could be a URI or a string.
+ nsCOMPtr<nsIURI> blockedURI = do_QueryInterface(mBlockedContentSource);
+ // if mBlockedContentSource is not a URI, it could be a string
+ nsCOMPtr<nsISupportsCString> blockedString = do_QueryInterface(mBlockedContentSource);
+
+ nsCString blockedDataStr;
+
+ if (blockedURI) {
+ blockedURI->GetSpec(blockedDataStr);
+ bool isData = false;
+ rv = blockedURI->SchemeIs("data", &isData);
+ if (NS_SUCCEEDED(rv) && isData) {
+ blockedDataStr.Truncate(40);
+ blockedDataStr.AppendASCII("...");
+ }
+ } else if (blockedString) {
+ blockedString->GetData(blockedDataStr);
+ }
+
+ if (blockedDataStr.Length() > 0) {
+ nsString blockedDataChar16 = NS_ConvertUTF8toUTF16(blockedDataStr);
+ const char16_t* params[] = { mViolatedDirective.get(),
+ blockedDataChar16.get() };
+ mCSPContext->logToConsole(mReportOnlyFlag ? u"CSPROViolationWithURI" :
+ u"CSPViolationWithURI",
+ params, ArrayLength(params), mSourceFile, mScriptSample,
+ mLineNum, 0, nsIScriptError::errorFlag);
+ }
+ return NS_OK;
+ }
+
+ private:
+ nsCOMPtr<nsISupports> mBlockedContentSource;
+ nsCOMPtr<nsIURI> mOriginalURI;
+ uint32_t mViolatedPolicyIndex;
+ bool mReportOnlyFlag;
+ nsString mViolatedDirective;
+ nsCOMPtr<nsISupports> mObserverSubject;
+ nsString mSourceFile;
+ nsString mScriptSample;
+ uint32_t mLineNum;
+ RefPtr<nsCSPContext> mCSPContext;
+};
+
+/**
+ * Asynchronously notifies any nsIObservers listening to the CSP violation
+ * topic that a violation occurred. Also triggers report sending and console
+ * logging. All asynchronous on the main thread.
+ *
+ * @param aBlockedContentSource
+ * Either a CSP Source (like 'self', as string) or nsIURI: the source
+ * of the violation.
+ * @param aOriginalUri
+ * The original URI if the blocked content is a redirect, else null
+ * @param aViolatedDirective
+ * the directive that was violated (string).
+ * @param aViolatedPolicyIndex
+ * the index of the policy that was violated (so we know where to send
+ * the reports).
+ * @param aObserverSubject
+ * optional, subject sent to the nsIObservers listening to the CSP
+ * violation topic.
+ * @param aSourceFile
+ * name of the file containing the inline script violation
+ * @param aScriptSample
+ * a sample of the violating inline script
+ * @param aLineNum
+ * source line number of the violation (if available)
+ */
+nsresult
+nsCSPContext::AsyncReportViolation(nsISupports* aBlockedContentSource,
+ nsIURI* aOriginalURI,
+ const nsAString& aViolatedDirective,
+ uint32_t aViolatedPolicyIndex,
+ const nsAString& aObserverSubject,
+ const nsAString& aSourceFile,
+ const nsAString& aScriptSample,
+ uint32_t aLineNum)
+{
+ NS_ENSURE_ARG_MAX(aViolatedPolicyIndex, mPolicies.Length() - 1);
+
+ NS_DispatchToMainThread(new CSPReportSenderRunnable(aBlockedContentSource,
+ aOriginalURI,
+ aViolatedPolicyIndex,
+ mPolicies[aViolatedPolicyIndex]->getReportOnlyFlag(),
+ aViolatedDirective,
+ aObserverSubject,
+ aSourceFile,
+ aScriptSample,
+ aLineNum,
+ this));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCSPContext::RequireSRIForType(nsContentPolicyType aContentType, bool* outRequiresSRIForType)
+{
+ *outRequiresSRIForType = false;
+ for (uint32_t i = 0; i < mPolicies.Length(); i++) {
+ if (mPolicies[i]->hasDirective(REQUIRE_SRI_FOR)) {
+ if (mPolicies[i]->requireSRIForType(aContentType)) {
+ *outRequiresSRIForType = true;
+ return NS_OK;
+ }
+ }
+ }
+ return NS_OK;
+}
+
+/**
+ * Based on the given docshell, determines if this CSP context allows the
+ * ancestry.
+ *
+ * In order to determine the URI of the parent document (one causing the load
+ * of this protected document), this function obtains the docShellTreeItem,
+ * then walks up the hierarchy until it finds a privileged (chrome) tree item.
+ * Getting the a tree item's URI looks like this in pseudocode:
+ *
+ * nsIDocShellTreeItem->GetDocument()->GetDocumentURI();
+ *
+ * aDocShell is the docShell for the protected document.
+ */
+NS_IMETHODIMP
+nsCSPContext::PermitsAncestry(nsIDocShell* aDocShell, bool* outPermitsAncestry)
+{
+ nsresult rv;
+
+ // Can't check ancestry without a docShell.
+ if (aDocShell == nullptr) {
+ return NS_ERROR_FAILURE;
+ }
+
+ *outPermitsAncestry = true;
+
+ // extract the ancestry as an array
+ nsCOMArray<nsIURI> ancestorsArray;
+
+ nsCOMPtr<nsIInterfaceRequestor> ir(do_QueryInterface(aDocShell));
+ nsCOMPtr<nsIDocShellTreeItem> treeItem(do_GetInterface(ir));
+ nsCOMPtr<nsIDocShellTreeItem> parentTreeItem;
+ nsCOMPtr<nsIURI> currentURI;
+ nsCOMPtr<nsIURI> uriClone;
+
+ // iterate through each docShell parent item
+ while (NS_SUCCEEDED(treeItem->GetParent(getter_AddRefs(parentTreeItem))) &&
+ parentTreeItem != nullptr) {
+
+ nsIDocument* doc = parentTreeItem->GetDocument();
+ NS_ASSERTION(doc, "Could not get nsIDocument from nsIDocShellTreeItem in nsCSPContext::PermitsAncestry");
+ NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
+
+ currentURI = doc->GetDocumentURI();
+
+ if (currentURI) {
+ // stop when reaching chrome
+ bool isChrome = false;
+ rv = currentURI->SchemeIs("chrome", &isChrome);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (isChrome) { break; }
+
+ // delete the userpass from the URI.
+ rv = currentURI->CloneIgnoringRef(getter_AddRefs(uriClone));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // We don't care if this succeeds, just want to delete a userpass if
+ // there was one.
+ uriClone->SetUserPass(EmptyCString());
+
+ if (CSPCONTEXTLOGENABLED()) {
+ CSPCONTEXTLOG(("nsCSPContext::PermitsAncestry, found ancestor: %s",
+ uriClone->GetSpecOrDefault().get()));
+ }
+ ancestorsArray.AppendElement(uriClone);
+ }
+
+ // next ancestor
+ treeItem = parentTreeItem;
+ }
+
+ nsAutoString violatedDirective;
+
+ // Now that we've got the ancestry chain in ancestorsArray, time to check
+ // them against any CSP.
+ // NOTE: the ancestors are not allowed to be sent cross origin; this is a
+ // restriction not placed on subresource loads.
+
+ for (uint32_t a = 0; a < ancestorsArray.Length(); a++) {
+ if (CSPCONTEXTLOGENABLED()) {
+ CSPCONTEXTLOG(("nsCSPContext::PermitsAncestry, checking ancestor: %s",
+ ancestorsArray[a]->GetSpecOrDefault().get()));
+ }
+ // omit the ancestor URI in violation reports if cross-origin as per spec
+ // (it is a violation of the same-origin policy).
+ bool okToSendAncestor = NS_SecurityCompareURIs(ancestorsArray[a], mSelfURI, true);
+
+
+ bool permits = permitsInternal(nsIContentSecurityPolicy::FRAME_ANCESTORS_DIRECTIVE,
+ ancestorsArray[a],
+ nullptr, // no redirect here.
+ EmptyString(), // no nonce
+ false, // no redirect here.
+ false, // not a preload.
+ true, // specific, do not use default-src
+ true, // send violation reports
+ okToSendAncestor,
+ false); // not parser created
+ if (!permits) {
+ *outPermitsAncestry = false;
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCSPContext::Permits(nsIURI* aURI,
+ CSPDirective aDir,
+ bool aSpecific,
+ bool* outPermits)
+{
+ // Can't perform check without aURI
+ if (aURI == nullptr) {
+ return NS_ERROR_FAILURE;
+ }
+
+ *outPermits = permitsInternal(aDir,
+ aURI,
+ nullptr, // no original (pre-redirect) URI
+ EmptyString(), // no nonce
+ false, // not redirected.
+ false, // not a preload.
+ aSpecific,
+ true, // send violation reports
+ true, // send blocked URI in violation reports
+ false); // not parser created
+
+ if (CSPCONTEXTLOGENABLED()) {
+ CSPCONTEXTLOG(("nsCSPContext::Permits, aUri: %s, aDir: %d, isAllowed: %s",
+ aURI->GetSpecOrDefault().get(), aDir,
+ *outPermits ? "allow" : "deny"));
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCSPContext::ToJSON(nsAString& outCSPinJSON)
+{
+ outCSPinJSON.Truncate();
+ dom::CSPPolicies jsonPolicies;
+ jsonPolicies.mCsp_policies.Construct();
+
+ for (uint32_t p = 0; p < mPolicies.Length(); p++) {
+ dom::CSP jsonCSP;
+ mPolicies[p]->toDomCSPStruct(jsonCSP);
+ jsonPolicies.mCsp_policies.Value().AppendElement(jsonCSP, fallible);
+ }
+
+ // convert the gathered information to JSON
+ if (!jsonPolicies.ToJSON(outCSPinJSON)) {
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCSPContext::GetCSPSandboxFlags(uint32_t* aOutSandboxFlags)
+{
+ if (!aOutSandboxFlags) {
+ return NS_ERROR_FAILURE;
+ }
+ *aOutSandboxFlags = SANDBOXED_NONE;
+
+ for (uint32_t i = 0; i < mPolicies.Length(); i++) {
+ uint32_t flags = mPolicies[i]->getSandboxFlags();
+
+ // current policy doesn't have sandbox flag, check next policy
+ if (!flags) {
+ continue;
+ }
+
+ // current policy has sandbox flags, if the policy is in enforcement-mode
+ // (i.e. not report-only) set these flags and check for policies with more
+ // restrictions
+ if (!mPolicies[i]->getReportOnlyFlag()) {
+ *aOutSandboxFlags |= flags;
+ } else {
+ // sandbox directive is ignored in report-only mode, warn about it and
+ // continue the loop checking for an enforcement policy.
+ nsAutoString policy;
+ mPolicies[i]->toString(policy);
+
+ CSPCONTEXTLOG(("nsCSPContext::GetCSPSandboxFlags, report only policy, ignoring sandbox in: %s",
+ policy.get()));
+
+ const char16_t* params[] = { policy.get() };
+ logToConsole(u"ignoringReportOnlyDirective", params, ArrayLength(params),
+ EmptyString(), EmptyString(), 0, 0, nsIScriptError::warningFlag);
+ }
+ }
+
+ return NS_OK;
+}
+
+/* ========== CSPViolationReportListener implementation ========== */
+
+NS_IMPL_ISUPPORTS(CSPViolationReportListener, nsIStreamListener, nsIRequestObserver, nsISupports);
+
+CSPViolationReportListener::CSPViolationReportListener()
+{
+}
+
+CSPViolationReportListener::~CSPViolationReportListener()
+{
+}
+
+nsresult
+AppendSegmentToString(nsIInputStream* aInputStream,
+ void* aClosure,
+ const char* aRawSegment,
+ uint32_t aToOffset,
+ uint32_t aCount,
+ uint32_t* outWrittenCount)
+{
+ nsCString* decodedData = static_cast<nsCString*>(aClosure);
+ decodedData->Append(aRawSegment, aCount);
+ *outWrittenCount = aCount;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+CSPViolationReportListener::OnDataAvailable(nsIRequest* aRequest,
+ nsISupports* aContext,
+ nsIInputStream* aInputStream,
+ uint64_t aOffset,
+ uint32_t aCount)
+{
+ uint32_t read;
+ nsCString decodedData;
+ return aInputStream->ReadSegments(AppendSegmentToString,
+ &decodedData,
+ aCount,
+ &read);
+}
+
+NS_IMETHODIMP
+CSPViolationReportListener::OnStopRequest(nsIRequest* aRequest,
+ nsISupports* aContext,
+ nsresult aStatus)
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+CSPViolationReportListener::OnStartRequest(nsIRequest* aRequest,
+ nsISupports* aContext)
+{
+ return NS_OK;
+}
+
+/* ========== CSPReportRedirectSink implementation ========== */
+
+NS_IMPL_ISUPPORTS(CSPReportRedirectSink, nsIChannelEventSink, nsIInterfaceRequestor);
+
+CSPReportRedirectSink::CSPReportRedirectSink()
+{
+}
+
+CSPReportRedirectSink::~CSPReportRedirectSink()
+{
+}
+
+NS_IMETHODIMP
+CSPReportRedirectSink::AsyncOnChannelRedirect(nsIChannel* aOldChannel,
+ nsIChannel* aNewChannel,
+ uint32_t aRedirFlags,
+ nsIAsyncVerifyRedirectCallback* aCallback)
+{
+ // cancel the old channel so XHR failure callback happens
+ nsresult rv = aOldChannel->Cancel(NS_ERROR_ABORT);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // notify an observer that we have blocked the report POST due to a redirect,
+ // used in testing, do this async since we're in an async call now to begin with
+ nsCOMPtr<nsIURI> uri;
+ rv = aOldChannel->GetURI(getter_AddRefs(uri));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIObserverService> observerService = mozilla::services::GetObserverService();
+ NS_ASSERTION(observerService, "Observer service required to log CSP violations");
+ observerService->NotifyObservers(uri,
+ CSP_VIOLATION_TOPIC,
+ u"denied redirect while sending violation report");
+
+ return NS_BINDING_REDIRECTED;
+}
+
+NS_IMETHODIMP
+CSPReportRedirectSink::GetInterface(const nsIID& aIID, void** aResult)
+{
+ if (aIID.Equals(NS_GET_IID(nsINetworkInterceptController)) &&
+ mInterceptController) {
+ nsCOMPtr<nsINetworkInterceptController> copy(mInterceptController);
+ *aResult = copy.forget().take();
+
+ return NS_OK;
+ }
+
+ return QueryInterface(aIID, aResult);
+}
+
+void
+CSPReportRedirectSink::SetInterceptController(nsINetworkInterceptController* aInterceptController)
+{
+ mInterceptController = aInterceptController;
+}
+
+/* ===== nsISerializable implementation ====== */
+
+NS_IMETHODIMP
+nsCSPContext::Read(nsIObjectInputStream* aStream)
+{
+ nsresult rv;
+ nsCOMPtr<nsISupports> supports;
+
+ rv = NS_ReadOptionalObject(aStream, true, getter_AddRefs(supports));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mSelfURI = do_QueryInterface(supports);
+ NS_ASSERTION(mSelfURI, "need a self URI to de-serialize");
+
+ uint32_t numPolicies;
+ rv = aStream->Read32(&numPolicies);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString policyString;
+
+ while (numPolicies > 0) {
+ numPolicies--;
+
+ rv = aStream->ReadString(policyString);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool reportOnly = false;
+ rv = aStream->ReadBoolean(&reportOnly);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // @param deliveredViaMetaTag:
+ // when parsing the CSP policy string initially we already remove directives
+ // that should not be processed when delivered via the meta tag. Such directives
+ // will not be present at this point anymore.
+ nsCSPPolicy* policy = nsCSPParser::parseContentSecurityPolicy(policyString,
+ mSelfURI,
+ reportOnly,
+ this,
+ false);
+ if (policy) {
+ mPolicies.AppendElement(policy);
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsCSPContext::Write(nsIObjectOutputStream* aStream)
+{
+ nsresult rv = NS_WriteOptionalCompoundObject(aStream,
+ mSelfURI,
+ NS_GET_IID(nsIURI),
+ true);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Serialize all the policies.
+ aStream->Write32(mPolicies.Length());
+
+ nsAutoString polStr;
+ for (uint32_t p = 0; p < mPolicies.Length(); p++) {
+ polStr.Truncate();
+ mPolicies[p]->toString(polStr);
+ aStream->WriteWStringZ(polStr.get());
+ aStream->WriteBoolean(mPolicies[p]->getReportOnlyFlag());
+ }
+ return NS_OK;
+}