Issue #2402 - Implement security policy violation event. https://bugzilla.mozilla.org/show_bug.cgi?id=1037335

This commit is contained in:
Brian Smith 2024-01-04 04:43:00 -06:00 • committed by roytam1
commit ec29404083
5 changed files with 215 additions and 60 deletions

View file

@ -6,11 +6,9 @@
#include "nsIContentPolicy.idl" #include "nsIContentPolicy.idl"
interface nsIURI; interface nsIURI;
interface nsIChannel;
interface nsIDocShell; interface nsIDocShell;
interface nsIDOMDocument; interface nsIDOMDocument;
interface nsIPrincipal; interface nsIPrincipal;
interface nsIURI;
/** /**
* nsIContentSecurityPolicy * nsIContentSecurityPolicy

View file

@ -799,31 +799,16 @@ StripURIForReporting(nsIURI* aURI,
aURI->GetSpecIgnoringRef(outStrippedURI); 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 nsresult
nsCSPContext::SendReports(nsISupports* aBlockedContentSource, nsCSPContext::GatherSecurityPolicyViolationEventData(
nsIURI* aOriginalURI, nsISupports* aBlockedContentSource,
nsAString& aViolatedDirective, nsIURI* aOriginalURI,
uint32_t aViolatedPolicyIndex, nsAString& aViolatedDirective,
nsAString& aSourceFile, uint32_t aViolatedPolicyIndex,
nsAString& aScriptSample, nsAString& aSourceFile,
uint32_t aLineNum) nsAString& aScriptSample,
uint32_t aLineNum,
mozilla::dom::SecurityPolicyViolationEventInit& aViolationEventInit)
{ {
NS_ENSURE_ARG_MAX(aViolatedPolicyIndex, mPolicies.Length() - 1); NS_ENSURE_ARG_MAX(aViolatedPolicyIndex, mPolicies.Length() - 1);
@ -837,9 +822,16 @@ nsCSPContext::SendReports(nsISupports* aBlockedContentSource,
return NS_OK; return NS_OK;
} }
dom::CSPReport report;
nsresult rv; nsresult rv;
// document-uri
nsAutoCString reportDocumentURI;
StripURIForReporting(mSelfURI, mSelfURI, reportDocumentURI);
aViolationEventInit.mDocumentURI = NS_ConvertUTF8toUTF16(reportDocumentURI);
// referrer
aViolationEventInit.mReferrer = mReferrer;
// blocked-uri // blocked-uri
if (aBlockedContentSource) { if (aBlockedContentSource) {
nsAutoCString reportBlockedURI; nsAutoCString reportBlockedURI;
@ -858,27 +850,20 @@ nsCSPContext::SendReports(nsISupports* aBlockedContentSource,
// ancestor is cross-origin. // ancestor is cross-origin.
NS_WARNING("No blocked URI (null aBlockedContentSource) for CSP violation report."); NS_WARNING("No blocked URI (null aBlockedContentSource) for CSP violation report.");
} }
report.mCsp_report.mBlocked_uri = NS_ConvertUTF8toUTF16(reportBlockedURI); aViolationEventInit.mBlockedURI = NS_ConvertUTF8toUTF16(reportBlockedURI);
} }
// document-uri // violated-directive
nsAutoCString reportDocumentURI; aViolationEventInit.mViolatedDirective = aViolatedDirective;
StripURIForReporting(mSelfURI, mSelfURI, reportDocumentURI);
report.mCsp_report.mDocument_uri = NS_ConvertUTF8toUTF16(reportDocumentURI); // effective-directive
aViolationEventInit.mEffectiveDirective = aViolatedDirective;
// original-policy // original-policy
nsAutoString originalPolicy; nsAutoString originalPolicy;
rv = this->GetPolicyString(aViolatedPolicyIndex, originalPolicy); rv = this->GetPolicyString(aViolatedPolicyIndex, originalPolicy);
NS_ENSURE_SUCCESS(rv, rv); NS_ENSURE_SUCCESS(rv, rv);
report.mCsp_report.mOriginal_policy = originalPolicy; aViolationEventInit.mOriginalPolicy = originalPolicy;
// referrer
if (!mReferrer.IsEmpty()) {
report.mCsp_report.mReferrer = mReferrer;
}
// violated-directive
report.mCsp_report.mViolated_directive = aViolatedDirective;
// source-file // source-file
if (!aSourceFile.IsEmpty()) { if (!aSourceFile.IsEmpty()) {
@ -890,20 +875,87 @@ nsCSPContext::SendReports(nsISupports* aBlockedContentSource,
sourceURI->GetSpecIgnoringRef(spec); sourceURI->GetSpecIgnoringRef(spec);
aSourceFile = NS_ConvertUTF8toUTF16(spec); aSourceFile = NS_ConvertUTF8toUTF16(spec);
} }
aViolationEventInit.mSourceFile = aSourceFile;
}
// sample
aViolationEventInit.mSample = aScriptSample;
// disposition
aViolationEventInit.mDisposition = mPolicies[aViolatedPolicyIndex]->getReportOnlyFlag()
? mozilla::dom::SecurityPolicyViolationEventDisposition::Report
: mozilla::dom::SecurityPolicyViolationEventDisposition::Enforce;
// status-code
uint16_t statusCode = 0;
{
nsCOMPtr<nsIDocument> doc = do_QueryReferent(mLoadingContext);
if (doc) {
nsCOMPtr<nsIHttpChannel> channel = do_QueryInterface(doc->GetChannel());
if (channel) {
uint32_t responseStatus = 0;
nsresult rv = channel->GetResponseStatus(&responseStatus);
if (NS_SUCCEEDED(rv) && (responseStatus <= UINT16_MAX)) {
statusCode = static_cast<uint16_t>(responseStatus);
}
}
}
}
aViolationEventInit.mStatusCode = statusCode;
// line-number
aViolationEventInit.mLineNumber = aLineNum;
// column-number
// TODO: Set correct column number.
aViolationEventInit.mColumnNumber = 0;
aViolationEventInit.mBubbles = true;
aViolationEventInit.mComposed = true;
return NS_OK;
}
nsresult
nsCSPContext::SendReports(
const mozilla::dom::SecurityPolicyViolationEventInit& aViolationEventInit,
uint32_t aViolatedPolicyIndex)
{
NS_ENSURE_ARG_MAX(aViolatedPolicyIndex, mPolicies.Length() - 1);
dom::CSPReport report;
// blocked-uri
report.mCsp_report.mBlocked_uri = aViolationEventInit.mBlockedURI;
// document-uri
report.mCsp_report.mDocument_uri = aViolationEventInit.mDocumentURI;
// original-policy
report.mCsp_report.mOriginal_policy = aViolationEventInit.mOriginalPolicy;
// referrer
report.mCsp_report.mReferrer = aViolationEventInit.mReferrer;
// violated-directive
report.mCsp_report.mViolated_directive = aViolationEventInit.mViolatedDirective;
// source-file
if (!aViolationEventInit.mSourceFile.IsEmpty()) {
report.mCsp_report.mSource_file.Construct(); report.mCsp_report.mSource_file.Construct();
report.mCsp_report.mSource_file.Value() = aSourceFile; report.mCsp_report.mSource_file.Value() = aViolationEventInit.mSourceFile;
} }
// script-sample // script-sample
if (!aScriptSample.IsEmpty()) { if (!aViolationEventInit.mSample.IsEmpty()) {
report.mCsp_report.mScript_sample.Construct(); report.mCsp_report.mScript_sample.Construct();
report.mCsp_report.mScript_sample.Value() = aScriptSample; report.mCsp_report.mScript_sample.Value() = aViolationEventInit.mSample;
} }
// line-number // line-number
if (aLineNum != 0) { if (aViolationEventInit.mLineNumber != 0) {
report.mCsp_report.mLine_number.Construct(); report.mCsp_report.mLine_number.Construct();
report.mCsp_report.mLine_number.Value() = aLineNum; report.mCsp_report.mLine_number.Value() = aViolationEventInit.mLineNumber;
} }
nsString csp_report; nsString csp_report;
@ -916,11 +968,11 @@ nsCSPContext::SendReports(nsISupports* aBlockedContentSource,
nsTArray<nsString> reportURIs; nsTArray<nsString> reportURIs;
mPolicies[aViolatedPolicyIndex]->getReportURIs(reportURIs); mPolicies[aViolatedPolicyIndex]->getReportURIs(reportURIs);
nsCOMPtr<nsIDocument> doc = do_QueryReferent(mLoadingContext); nsCOMPtr<nsIDocument> doc = do_QueryReferent(mLoadingContext);
nsCOMPtr<nsIURI> reportURI; nsCOMPtr<nsIURI> reportURI;
nsCOMPtr<nsIChannel> reportChannel; nsCOMPtr<nsIChannel> reportChannel;
nsresult rv;
for (uint32_t r = 0; r < reportURIs.Length(); r++) { for (uint32_t r = 0; r < reportURIs.Length(); r++) {
nsAutoCString reportURICstring = NS_ConvertUTF16toUTF8(reportURIs[r]); nsAutoCString reportURICstring = NS_ConvertUTF16toUTF8(reportURIs[r]);
// try to create a new uri from every report-uri string // try to create a new uri from every report-uri string
@ -930,7 +982,8 @@ nsCSPContext::SendReports(nsISupports* aBlockedContentSource,
CSPCONTEXTLOG(("Could not create nsIURI for report URI %s", CSPCONTEXTLOG(("Could not create nsIURI for report URI %s",
reportURICstring.get())); reportURICstring.get()));
logToConsole(u"triedToSendReport", params, ArrayLength(params), logToConsole(u"triedToSendReport", params, ArrayLength(params),
aSourceFile, aScriptSample, aLineNum, 0, nsIScriptError::errorFlag); aViolationEventInit.mSourceFile, aViolationEventInit.mSample,
aViolationEventInit.mLineNumber, 0, nsIScriptError::errorFlag);
continue; // don't return yet, there may be more URIs continue; // don't return yet, there may be more URIs
} }
@ -971,7 +1024,8 @@ nsCSPContext::SendReports(nsISupports* aBlockedContentSource,
if (!isHttpScheme) { if (!isHttpScheme) {
const char16_t* params[] = { reportURIs[r].get() }; const char16_t* params[] = { reportURIs[r].get() };
logToConsole(u"reportURInotHttpsOrHttp2", params, ArrayLength(params), logToConsole(u"reportURInotHttpsOrHttp2", params, ArrayLength(params),
aSourceFile, aScriptSample, aLineNum, 0, nsIScriptError::errorFlag); aViolationEventInit.mSourceFile, aViolationEventInit.mSample,
aViolationEventInit.mLineNumber, 0, nsIScriptError::errorFlag);
continue; continue;
} }
@ -1035,7 +1089,8 @@ nsCSPContext::SendReports(nsISupports* aBlockedContentSource,
const char16_t* params[] = { reportURIs[r].get() }; const char16_t* params[] = { reportURIs[r].get() };
CSPCONTEXTLOG(("AsyncOpen failed for report URI %s", params[0])); CSPCONTEXTLOG(("AsyncOpen failed for report URI %s", params[0]));
logToConsole(u"triedToSendReport", params, ArrayLength(params), logToConsole(u"triedToSendReport", params, ArrayLength(params),
aSourceFile, aScriptSample, aLineNum, 0, nsIScriptError::errorFlag); aViolationEventInit.mSourceFile, aViolationEventInit.mSample,
aViolationEventInit.mLineNumber, 0, nsIScriptError::errorFlag);
} else { } else {
CSPCONTEXTLOG(("Sent violation report to URI %s", reportURICstring.get())); CSPCONTEXTLOG(("Sent violation report to URI %s", reportURICstring.get()));
} }
@ -1043,6 +1098,26 @@ nsCSPContext::SendReports(nsISupports* aBlockedContentSource,
return NS_OK; return NS_OK;
} }
nsresult
nsCSPContext::FireViolationEvent(
const mozilla::dom::SecurityPolicyViolationEventInit& aViolationEventInit)
{
nsCOMPtr<nsIDocument> doc = do_QueryReferent(mLoadingContext);
if (!doc) {
return NS_OK;
}
RefPtr<mozilla::dom::Event> event =
mozilla::dom::SecurityPolicyViolationEvent::Constructor(
doc,
NS_LITERAL_STRING("securitypolicyviolation"),
aViolationEventInit);
event->SetTrusted(true);
bool rv;
return doc->DispatchEvent(event, &rv);
}
/** /**
* Dispatched from the main thread to send reports for one CSP violation. * Dispatched from the main thread to send reports for one CSP violation.
*/ */
@ -1088,6 +1163,14 @@ class CSPReportSenderRunnable final : public Runnable
{ {
MOZ_ASSERT(NS_IsMainThread()); 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 // 1) notify observers
nsCOMPtr<nsIObserverService> observerService = mozilla::services::GetObserverService(); nsCOMPtr<nsIObserverService> observerService = mozilla::services::GetObserverService();
NS_ASSERTION(observerService, "needs observer service"); NS_ASSERTION(observerService, "needs observer service");
@ -1097,9 +1180,7 @@ class CSPReportSenderRunnable final : public Runnable
NS_ENSURE_SUCCESS(rv, rv); NS_ENSURE_SUCCESS(rv, rv);
// 2) send reports for the policy that was violated // 2) send reports for the policy that was violated
mCSPContext->SendReports(mBlockedContentSource, mOriginalURI, mCSPContext->SendReports(init, mViolatedPolicyIndex);
mViolatedDirective, mViolatedPolicyIndex,
mSourceFile, mScriptSample, mLineNum);
// 3) log to console (one per policy violation) // 3) log to console (one per policy violation)
// mBlockedContentSource could be a URI or a string. // mBlockedContentSource could be a URI or a string.
@ -1130,6 +1211,10 @@ class CSPReportSenderRunnable final : public Runnable
params, ArrayLength(params), mSourceFile, mScriptSample, params, ArrayLength(params), mSourceFile, mScriptSample,
mLineNum, 0, nsIScriptError::errorFlag); mLineNum, 0, nsIScriptError::errorFlag);
} }
// 4) fire violation event
mCSPContext->FireViolationEvent(init);
return NS_OK; return NS_OK;
} }

View file

@ -7,6 +7,7 @@
#define nsCSPContext_h___ #define nsCSPContext_h___
#include "mozilla/dom/nsCSPUtils.h" #include "mozilla/dom/nsCSPUtils.h"
#include "mozilla/dom/SecurityPolicyViolationEvent.h"
#include "nsDataHashtable.h" #include "nsDataHashtable.h"
#include "nsIChannel.h" #include "nsIChannel.h"
#include "nsIChannelEventSink.h" #include "nsIChannelEventSink.h"
@ -56,13 +57,42 @@ class nsCSPContext : public nsIContentSecurityPolicy
uint32_t aColumnNumber, uint32_t aColumnNumber,
uint32_t aSeverityFlag); uint32_t aSeverityFlag);
nsresult SendReports(nsISupports* aBlockedContentSource,
nsIURI* aOriginalURI, /**
nsAString& aViolatedDirective, * Construct SecurityPolicyViolationEventInit structure.
uint32_t aViolatedPolicyIndex, *
nsAString& aSourceFile, * @param aBlockedContentSource
nsAString& aScriptSample, * Either a CSP Source (like 'self', as string) or nsIURI: the source
uint32_t aLineNum); * 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 aViolationEventInit
* The output
*/
nsresult GatherSecurityPolicyViolationEventData(
nsISupports* aBlockedContentSource,
nsIURI* aOriginalURI,
nsAString& aViolatedDirective,
uint32_t aViolatedPolicyIndex,
nsAString& aSourceFile,
nsAString& aScriptSample,
uint32_t aLineNum,
mozilla::dom::SecurityPolicyViolationEventInit& aViolationEventInit);
nsresult SendReports(
const mozilla::dom::SecurityPolicyViolationEventInit& aViolationEventInit,
uint32_t aViolatedPolicyIndex);
nsresult FireViolationEvent(
const mozilla::dom::SecurityPolicyViolationEventInit& aViolationEventInit);
nsresult AsyncReportViolation(nsISupports* aBlockedContentSource, nsresult AsyncReportViolation(nsISupports* aBlockedContentSource,
nsIURI* aOriginalURI, nsIURI* aOriginalURI,

View file

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

View file

@ -698,6 +698,7 @@ GENERATED_EVENTS_WEBIDL_FILES = [
'PromiseRejectionEvent.webidl', 'PromiseRejectionEvent.webidl',
'RecordErrorEvent.webidl', 'RecordErrorEvent.webidl',
'ScrollViewChangeEvent.webidl', 'ScrollViewChangeEvent.webidl',
'SecurityPolicyViolationEvent.webidl',
'ServiceWorkerMessageEvent.webidl', 'ServiceWorkerMessageEvent.webidl',
'StyleRuleChangeEvent.webidl', 'StyleRuleChangeEvent.webidl',
'StyleSheetApplicableStateChangeEvent.webidl', 'StyleSheetApplicableStateChangeEvent.webidl',