Bug 1037335 - Implement security policy violation event.
MozReview-Commit-ID: 4BYThUXduI4
--- a/dom/interfaces/security/nsIContentSecurityPolicy.idl
+++ b/dom/interfaces/security/nsIContentSecurityPolicy.idl
@@ -1,24 +1,21 @@
/* 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 "nsISerializable.idl"
#include "nsIContentPolicy.idl"
interface nsIURI;
-interface nsIChannel;
interface nsIDocShell;
interface nsIDOMDocument;
interface nsIDOMEventTarget;
interface nsIEventTarget;
interface nsIPrincipal;
-interface nsIScriptElement;
-interface nsIURI;
/**
* nsIContentSecurityPolicy
* Describes an XPCOM component used to model and enforce CSPs. Instances of
* this class may have multiple policies within them, but there should only be
* one of these per document/principal.
*/
--- a/dom/security/nsCSPContext.cpp
+++ b/dom/security/nsCSPContext.cpp
@@ -13,16 +13,17 @@
#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 "nsIEventTarget.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"
@@ -36,21 +37,21 @@
#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/dom/NodeBinding.h"
#include "mozilla/net/ReferrerPolicy.h"
#include "nsINetworkInterceptController.h"
#include "nsSandboxFlags.h"
#include "nsIScriptElement.h"
-#include "nsIEventTarget.h"
#include "mozilla/dom/DocGroup.h"
#include "nsXULAppAPI.h"
using namespace mozilla;
static LogModule*
GetCspContextLog()
{
@@ -833,47 +834,39 @@ StripURIForReporting(nsIURI* aURI,
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)
+nsCSPContext::GatherSecurityPolicyViolationEventData(
+ nsISupports* aBlockedContentSource,
+ nsIURI* aOriginalURI,
+ nsAString& aViolatedDirective,
+ uint32_t aViolatedPolicyIndex,
+ nsAString& aSourceFile,
+ nsAString& aScriptSample,
+ uint32_t aLineNum,
+ mozilla::dom::SecurityPolicyViolationEventInit& aInit)
{
NS_ENSURE_ARG_MAX(aViolatedPolicyIndex, mPolicies.Length() - 1);
- dom::CSPReport report;
nsresult rv;
+ // document-uri
+ nsAutoCString reportDocumentURI;
+ StripURIForReporting(mSelfURI, mSelfURI, reportDocumentURI);
+ aInit.mDocumentURI = NS_ConvertUTF8toUTF16(reportDocumentURI);
+
+ // referrer
+ aInit.mReferrer = mReferrer;
+
// 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 {
@@ -882,89 +875,156 @@ nsCSPContext::SendReports(nsISupports* a
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);
+ aInit.mBlockedURI = NS_ConvertUTF8toUTF16(reportBlockedURI);
}
- // document-uri
- nsAutoCString reportDocumentURI;
- StripURIForReporting(mSelfURI, mSelfURI, reportDocumentURI);
- report.mCsp_report.mDocument_uri = NS_ConvertUTF8toUTF16(reportDocumentURI);
+ // violated-directive
+ aInit.mViolatedDirective = aViolatedDirective;
+
+ // effective-directive
+ aInit.mEffectiveDirective = aViolatedDirective;
// 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;
+ aInit.mOriginalPolicy = originalPolicy;
// source-file
- if (!aSourceFile.IsEmpty()) {
+ [&aSourceFile] () {
+ if (aSourceFile.IsEmpty()) {
+ return;
+ }
+
// 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);
+ if (!sourceURI) {
+ return;
+ }
+
+ nsAutoCString spec;
+ sourceURI->GetSpecIgnoringRef(spec);
+ aSourceFile = NS_ConvertUTF8toUTF16(spec);
+ } ();
+ aInit.mSourceFile = aSourceFile;
+
+ // sample
+ aInit.mSample = aScriptSample;
+
+ // disposition
+ aInit.mDisposition = mPolicies[aViolatedPolicyIndex]->getReportOnlyFlag()
+ ? mozilla::dom::SecurityPolicyViolationEventDisposition::Report
+ : mozilla::dom::SecurityPolicyViolationEventDisposition::Enforce;
+
+ // status-code
+ aInit.mStatusCode = [] (nsCOMPtr<nsIDocument> doc) -> uint16_t {
+ if (!doc) {
+ return 0;
+ }
+
+ nsCOMPtr<nsIHttpChannel> channel = do_QueryInterface(doc->GetChannel());
+ if (!channel) {
+ return 0;
+ }
+
+ uint32_t statusCode = 0;
+ nsresult rv = channel->GetResponseStatus(&statusCode);
+ if (NS_FAILED(rv)) {
+ return 0;
}
+
+ return static_cast<uint16_t>(statusCode);
+ } (do_QueryReferent(mLoadingContext));
+
+ // line-number
+ aInit.mLineNumber = aLineNum;
+
+ // column-number
+ aInit.mColumnNumber = 0; // TODO
+
+ aInit.mBubbles = true;
+
+ return NS_OK;
+}
+
+nsresult
+nsCSPContext::SendReports(
+ const mozilla::dom::SecurityPolicyViolationEventInit& aInit,
+ uint32_t aViolatedPolicyIndex)
+{
+ NS_ENSURE_ARG_MAX(aViolatedPolicyIndex, mPolicies.Length() - 1);
+
+ dom::CSPReport report;
+
+ // blocked-uri
+ report.mCsp_report.mBlocked_uri = aInit.mBlockedURI;
+
+ // document-uri
+ report.mCsp_report.mDocument_uri = aInit.mDocumentURI;
+
+ // original-policy
+ report.mCsp_report.mOriginal_policy = aInit.mOriginalPolicy;
+
+ // referrer
+ report.mCsp_report.mReferrer = aInit.mReferrer;
+
+ // violated-directive
+ report.mCsp_report.mViolated_directive = aInit.mViolatedDirective;
+
+ // source-file
+ if (!aInit.mSourceFile.IsEmpty()) {
report.mCsp_report.mSource_file.Construct();
- report.mCsp_report.mSource_file.Value() = aSourceFile;
+ report.mCsp_report.mSource_file.Value() = aInit.mSourceFile;
}
// script-sample
- if (!aScriptSample.IsEmpty()) {
+ if (!aInit.mSample.IsEmpty()) {
report.mCsp_report.mScript_sample.Construct();
- report.mCsp_report.mScript_sample.Value() = aScriptSample;
+ report.mCsp_report.mScript_sample.Value() = aInit.mSample;
}
// line-number
- if (aLineNum != 0) {
+ if (aInit.mLineNumber != 0) {
report.mCsp_report.mLine_number.Construct();
- report.mCsp_report.mLine_number.Value() = aLineNum;
+ report.mCsp_report.mLine_number.Value() = aInit.mLineNumber;
}
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;
+ nsresult rv;
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("triedToSendReport", params, ArrayLength(params),
- aSourceFile, aScriptSample, aLineNum, 0, nsIScriptError::errorFlag);
+ aInit.mSourceFile, aInit.mSample, aInit.mLineNumber, 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,
@@ -995,17 +1055,17 @@ nsCSPContext::SendReports(nsISupports* a
// 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("reportURInotHttpsOrHttp2", params, ArrayLength(params),
- aSourceFile, aScriptSample, aLineNum, 0, nsIScriptError::errorFlag);
+ aInit.mSourceFile, aInit.mSample, aInit.mLineNumber, 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);
@@ -1060,24 +1120,62 @@ nsCSPContext::SendReports(nsISupports* a
// 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", NS_ConvertUTF16toUTF8(params[0]).get()));
logToConsole("triedToSendReport", params, ArrayLength(params),
- aSourceFile, aScriptSample, aLineNum, 0, nsIScriptError::errorFlag);
+ aInit.mSourceFile, aInit.mSample, aInit.mLineNumber, 0, nsIScriptError::errorFlag);
} else {
CSPCONTEXTLOG(("Sent violation report to URI %s", reportURICstring.get()));
}
}
return NS_OK;
}
+nsresult
+nsCSPContext::FireViolationEvent(
+ nsIDOMEventTarget* aTarget,
+ const mozilla::dom::SecurityPolicyViolationEventInit& aInit)
+{
+ nsCOMPtr<nsIDocument> doc = do_QueryReferent(mLoadingContext);
+ if (!doc) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsINode> target = do_QueryInterface(aTarget);
+
+ // If target is not null, and global is a Window, and target’s
+ // shadow-including root is not global’s associated Document, set target to
+ // null.
+ mozilla::dom::GetRootNodeOptions opt;
+ opt.mComposed = true;
+ if (target && (doc != target->GetRootNode(opt))) {
+ target = nullptr;
+ }
+
+ // If target is null:
+ // 1. Set target be violation’s global object.
+ // 2. If target is a Window, set target to target’s associated Document.
+ if (!target) {
+ target = doc;
+ }
+
+ RefPtr<mozilla::dom::Event> event =
+ mozilla::dom::SecurityPolicyViolationEvent::Constructor(
+ target,
+ NS_LITERAL_STRING("securitypolicyviolation"),
+ aInit);
+
+ bool rv;
+ return target->DispatchEvent(event, &rv);
+}
+
/**
* Dispatched from the main thread to send reports for one CSP violation.
*/
class CSPReportSenderRunnable final : public Runnable
{
public:
CSPReportSenderRunnable(nsIDOMEventTarget* aTarget,
nsISupports* aBlockedContentSource,
@@ -1116,28 +1214,34 @@ class CSPReportSenderRunnable final : pu
mObserverSubject = do_QueryInterface(supportscstr);
}
}
NS_IMETHOD Run() override
{
MOZ_ASSERT(NS_IsMainThread());
+ // 0) prepare violation data
+ mozilla::dom::SecurityPolicyViolationEventInit init;
+ mCSPContext->GatherSecurityPolicyViolationEventData(
+ mBlockedContentSource, mOriginalURI,
+ mViolatedDirective, mViolatedPolicyIndex,
+ mSourceFile, mScriptSample, mLineNum,
+ init);
+
// 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);
+ mCSPContext->SendReports(init, mViolatedPolicyIndex);
// 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;
@@ -1160,16 +1264,20 @@ class CSPReportSenderRunnable final : pu
nsString blockedDataChar16 = NS_ConvertUTF8toUTF16(blockedDataStr);
const char16_t* params[] = { mViolatedDirective.get(),
blockedDataChar16.get() };
mCSPContext->logToConsole(mReportOnlyFlag ? "CSPROViolationWithURI" :
"CSPViolationWithURI",
params, ArrayLength(params), mSourceFile, mScriptSample,
mLineNum, 0, nsIScriptError::errorFlag);
}
+
+ // 4) fire violation event
+ mCSPContext->FireViolationEvent(mTarget, init);
+
return NS_OK;
}
private:
nsCOMPtr<nsIDOMEventTarget> mTarget;
nsCOMPtr<nsISupports> mBlockedContentSource;
nsCOMPtr<nsIURI> mOriginalURI;
uint32_t mViolatedPolicyIndex;
--- a/dom/security/nsCSPContext.h
+++ b/dom/security/nsCSPContext.h
@@ -3,16 +3,17 @@
/* 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/. */
#ifndef nsCSPContext_h___
#define nsCSPContext_h___
#include "mozilla/dom/nsCSPUtils.h"
+#include "mozilla/dom/SecurityPolicyViolationEvent.h"
#include "nsDataHashtable.h"
#include "nsIChannel.h"
#include "nsIChannelEventSink.h"
#include "nsIClassInfo.h"
#include "nsIContentSecurityPolicy.h"
#include "nsIInterfaceRequestor.h"
#include "nsISerializable.h"
#include "nsIStreamListener.h"
@@ -53,23 +54,54 @@ class nsCSPContext : public nsIContentSe
const char16_t** aParams,
uint32_t aParamsLength,
const nsAString& aSourceName,
const nsAString& aSourceLine,
uint32_t aLineNumber,
uint32_t aColumnNumber,
uint32_t aSeverityFlag);
- nsresult SendReports(nsISupports* aBlockedContentSource,
- nsIURI* aOriginalURI,
- nsAString& aViolatedDirective,
- uint32_t aViolatedPolicyIndex,
- nsAString& aSourceFile,
- nsAString& aScriptSample,
- uint32_t aLineNum);
+
+
+ /**
+ * Construct SecurityPolicyViolationEventInit structure.
+ *
+ * @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)
+ * @param aInit
+ * The output
+ */
+ nsresult GatherSecurityPolicyViolationEventData(
+ nsISupports* aBlockedContentSource,
+ nsIURI* aOriginalURI,
+ nsAString& aViolatedDirective,
+ uint32_t aViolatedPolicyIndex,
+ nsAString& aSourceFile,
+ nsAString& aScriptSample,
+ uint32_t aLineNum,
+ mozilla::dom::SecurityPolicyViolationEventInit& aInit);
+
+ nsresult SendReports(
+ const mozilla::dom::SecurityPolicyViolationEventInit& aInit,
+ uint32_t aViolatedPolicyIndex);
+
+ nsresult FireViolationEvent(
+ nsIDOMEventTarget* aTarget,
+ const mozilla::dom::SecurityPolicyViolationEventInit& aInit);
nsresult AsyncReportViolation(nsIDOMEventTarget* aTarget,
nsISupports* aBlockedContentSource,
nsIURI* aOriginalURI,
const nsAString& aViolatedDirective,
uint32_t aViolatedPolicyIndex,
const nsAString& aObserverSubject,
const nsAString& aSourceFile,
new file mode 100644
--- /dev/null
+++ b/dom/webidl/SecurityPolicyViolationEvent.webidl
@@ -0,0 +1,41 @@
+/* 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/. */
+
+enum SecurityPolicyViolationEventDisposition
+{
+ "enforce", "report"
+};
+
+[Constructor(DOMString type, optional SecurityPolicyViolationEventInit eventInitDict)]
+interface SecurityPolicyViolationEvent : Event
+{
+ readonly attribute DOMString documentURI;
+ readonly attribute DOMString referrer;
+ readonly attribute DOMString blockedURI;
+ readonly attribute DOMString violatedDirective;
+ readonly attribute DOMString effectiveDirective;
+ readonly attribute DOMString originalPolicy;
+ readonly attribute DOMString sourceFile;
+ readonly attribute DOMString sample;
+ readonly attribute SecurityPolicyViolationEventDisposition disposition;
+ readonly attribute unsigned short statusCode;
+ readonly attribute long lineNumber;
+ readonly attribute long columnNumber;
+};
+
+dictionary SecurityPolicyViolationEventInit : EventInit
+{
+ DOMString documentURI = "";
+ DOMString referrer = "";
+ DOMString blockedURI = "";
+ DOMString violatedDirective = "";
+ DOMString effectiveDirective = "";
+ DOMString originalPolicy = "";
+ DOMString sourceFile = "";
+ DOMString sample = "";
+ SecurityPolicyViolationEventDisposition disposition = "report";
+ unsigned short statusCode = 0;
+ long lineNumber = 0;
+ long columnNumber = 0;
+};
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -1099,16 +1099,17 @@ GENERATED_EVENTS_WEBIDL_FILES = [
'PluginCrashedEvent.webidl',
'PopStateEvent.webidl',
'PopupBlockedEvent.webidl',
'PresentationConnectionAvailableEvent.webidl',
'PresentationConnectionCloseEvent.webidl',
'ProgressEvent.webidl',
'PromiseRejectionEvent.webidl',
'ScrollViewChangeEvent.webidl',
+ 'SecurityPolicyViolationEvent.webidl',
'StyleRuleChangeEvent.webidl',
'StyleSheetApplicableStateChangeEvent.webidl',
'StyleSheetChangeEvent.webidl',
'TCPServerSocketEvent.webidl',
'TCPSocketErrorEvent.webidl',
'TCPSocketEvent.webidl',
'TrackEvent.webidl',
'UDPMessageEvent.webidl',