Bug 1037335 - Implement security policy violation event. r=ckerschb,smaug

MozReview-Commit-ID: 4BYThUXduI4

--HG--
extra : rebase_source : 5d4a34c5e6bb7fd3774fafb1de72e761bce4591f
This commit is contained in:
Chung-Sheng Fu 2017-11-29 16:53:00 +02:00
parent 748dad0399
commit 8dd7eb1b95
5 changed files with 216 additions and 61 deletions

View File

@ -6,13 +6,10 @@
#include "nsIContentPolicy.idl"
interface nsIURI;
interface nsIChannel;
interface nsIDocShell;
interface nsIDOMDocument;
interface nsIEventTarget;
interface nsIPrincipal;
interface nsIScriptElement;
interface nsIURI;
/**
* nsIContentSecurityPolicy

View File

@ -843,37 +843,29 @@ StripURIForReporting(nsIURI* aURI,
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& aViolationEventInit)
{
NS_ENSURE_ARG_MAX(aViolatedPolicyIndex, mPolicies.Length() - 1);
dom::CSPReport report;
nsresult rv;
// document-uri
nsAutoCString reportDocumentURI;
StripURIForReporting(mSelfURI, mSelfURI, reportDocumentURI);
aViolationEventInit.mDocumentURI = NS_ConvertUTF8toUTF16(reportDocumentURI);
// referrer
aViolationEventInit.mReferrer = mReferrer;
// blocked-uri
if (aBlockedContentSource) {
nsAutoCString reportBlockedURI;
@ -892,27 +884,20 @@ nsCSPContext::SendReports(nsISupports* aBlockedContentSource,
// ancestor is cross-origin.
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
nsAutoCString reportDocumentURI;
StripURIForReporting(mSelfURI, mSelfURI, reportDocumentURI);
report.mCsp_report.mDocument_uri = NS_ConvertUTF8toUTF16(reportDocumentURI);
// violated-directive
aViolationEventInit.mViolatedDirective = aViolatedDirective;
// effective-directive
aViolationEventInit.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;
aViolationEventInit.mOriginalPolicy = originalPolicy;
// source-file
if (!aSourceFile.IsEmpty()) {
@ -924,20 +909,87 @@ nsCSPContext::SendReports(nsISupports* aBlockedContentSource,
sourceURI->GetSpecIgnoringRef(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.Value() = aSourceFile;
report.mCsp_report.mSource_file.Value() = aViolationEventInit.mSourceFile;
}
// script-sample
if (!aScriptSample.IsEmpty()) {
if (!aViolationEventInit.mSample.IsEmpty()) {
report.mCsp_report.mScript_sample.Construct();
report.mCsp_report.mScript_sample.Value() = aScriptSample;
report.mCsp_report.mScript_sample.Value() = aViolationEventInit.mSample;
}
// line-number
if (aLineNum != 0) {
if (aViolationEventInit.mLineNumber != 0) {
report.mCsp_report.mLine_number.Construct();
report.mCsp_report.mLine_number.Value() = aLineNum;
report.mCsp_report.mLine_number.Value() = aViolationEventInit.mLineNumber;
}
nsString csp_report;
@ -950,11 +1002,11 @@ nsCSPContext::SendReports(nsISupports* aBlockedContentSource,
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
@ -964,7 +1016,8 @@ nsCSPContext::SendReports(nsISupports* aBlockedContentSource,
CSPCONTEXTLOG(("Could not create nsIURI for report URI %s",
reportURICstring.get()));
logToConsole("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
}
@ -1005,7 +1058,8 @@ nsCSPContext::SendReports(nsISupports* aBlockedContentSource,
if (!isHttpScheme) {
const char16_t* params[] = { reportURIs[r].get() };
logToConsole("reportURInotHttpsOrHttp2", params, ArrayLength(params),
aSourceFile, aScriptSample, aLineNum, 0, nsIScriptError::errorFlag);
aViolationEventInit.mSourceFile, aViolationEventInit.mSample,
aViolationEventInit.mLineNumber, 0, nsIScriptError::errorFlag);
continue;
}
@ -1070,7 +1124,8 @@ nsCSPContext::SendReports(nsISupports* aBlockedContentSource,
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);
aViolationEventInit.mSourceFile, aViolationEventInit.mSample,
aViolationEventInit.mLineNumber, 0, nsIScriptError::errorFlag);
} else {
CSPCONTEXTLOG(("Sent violation report to URI %s", reportURICstring.get()));
}
@ -1078,6 +1133,26 @@ nsCSPContext::SendReports(nsISupports* aBlockedContentSource,
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.
*/
@ -1124,6 +1199,14 @@ class CSPReportSenderRunnable final : public Runnable
{
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");
@ -1133,9 +1216,7 @@ class CSPReportSenderRunnable final : public Runnable
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.
@ -1168,6 +1249,10 @@ class CSPReportSenderRunnable final : public Runnable
params, ArrayLength(params), mSourceFile, mScriptSample,
mLineNum, 0, nsIScriptError::errorFlag);
}
// 4) fire violation event
mCSPContext->FireViolationEvent(init);
return NS_OK;
}

View File

@ -8,6 +8,7 @@
#define nsCSPContext_h___
#include "mozilla/dom/nsCSPUtils.h"
#include "mozilla/dom/SecurityPolicyViolationEvent.h"
#include "nsDataHashtable.h"
#include "nsIChannel.h"
#include "nsIChannelEventSink.h"
@ -58,13 +59,43 @@ class nsCSPContext : public nsIContentSecurityPolicy
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 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,
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

@ -1089,6 +1089,7 @@ GENERATED_EVENTS_WEBIDL_FILES = [
'ProgressEvent.webidl',
'PromiseRejectionEvent.webidl',
'ScrollViewChangeEvent.webidl',
'SecurityPolicyViolationEvent.webidl',
'StyleRuleChangeEvent.webidl',
'StyleSheetApplicableStateChangeEvent.webidl',
'StyleSheetChangeEvent.webidl',