summaryrefslogtreecommitdiffstats
path: root/dom/security
diff options
context:
space:
mode:
Diffstat (limited to 'dom/security')
-rw-r--r--dom/security/FramingChecker.cpp4
-rw-r--r--dom/security/metrics.yaml14
-rw-r--r--dom/security/nsCSPContext.cpp82
-rw-r--r--dom/security/nsCSPContext.h38
-rw-r--r--dom/security/nsCSPParser.cpp159
-rw-r--r--dom/security/nsCSPParser.h2
-rw-r--r--dom/security/nsCSPUtils.cpp85
-rw-r--r--dom/security/nsCSPUtils.h48
-rw-r--r--dom/security/nsHTTPSOnlyUtils.cpp35
-rw-r--r--dom/security/test/gtest/TestCSPParser.cpp17
-rw-r--r--dom/security/test/https-first/browser_httpsfirst.js9
11 files changed, 383 insertions, 110 deletions
diff --git a/dom/security/FramingChecker.cpp b/dom/security/FramingChecker.cpp
index ecd7a6863e..bee587e701 100644
--- a/dom/security/FramingChecker.cpp
+++ b/dom/security/FramingChecker.cpp
@@ -151,6 +151,8 @@ bool FramingChecker::CheckFrameOptions(nsIChannel* aChannel,
return true;
}
+ static const char kASCIIWhitespace[] = "\t ";
+
// Step 3-4. reduce the header options to a unique set and count how many
// unique values (that we track) are encountered. this avoids using a set to
// stop attackers from inheriting arbitrary values in memory and reduce the
@@ -158,7 +160,7 @@ bool FramingChecker::CheckFrameOptions(nsIChannel* aChannel,
XFOHeader xfoOptions;
for (const nsACString& next : xfoHeaderValue.Split(',')) {
nsAutoCString option(next);
- option.StripWhitespace();
+ option.Trim(kASCIIWhitespace);
if (option.LowerCaseEqualsLiteral("allowall")) {
xfoOptions.ALLOWALL = true;
diff --git a/dom/security/metrics.yaml b/dom/security/metrics.yaml
index 02084407b1..d48069e4b1 100644
--- a/dom/security/metrics.yaml
+++ b/dom/security/metrics.yaml
@@ -118,8 +118,8 @@ httpsfirst:
description: >
If a HTTPS-First (`dom.security.https_first` enabled) upgrade isn't
successful, measures the timespan between the navigation start and the
- downgrade. This is essentially the overhead caused by HTTPS-First if a
- site does not support HTTPS.
+ downgrade. This does not include the case in which the https request times
+ out and the http request sent after 3s gets a response faster.
time_unit: millisecond
bugs:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1868380
@@ -135,11 +135,11 @@ httpsfirst:
downgrade_time_schemeless:
type: timing_distribution
description: >
- If a schemeless HTTPS-First (`dom.security.https_first` disabled, but
- load marked as schemeless) upgrade isn't successful, measures the
- timespan between the navigation start and the downgrade. This is
- essentially the overhead caused by HTTPS-First if a site does not support
- HTTPS.
+ If a schemeless HTTPS-First (`dom.security.https_first` disabled, but load
+ marked as schemeless) upgrade isn't successful, measures the timespan
+ between the navigation start and the downgrade. This does not include the
+ case in which the https request times out and the http request sent after
+ 3s gets a response faster.
time_unit: millisecond
bugs:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1868380
diff --git a/dom/security/nsCSPContext.cpp b/dom/security/nsCSPContext.cpp
index de67e2bf1c..b9675e39fc 100644
--- a/dom/security/nsCSPContext.cpp
+++ b/dom/security/nsCSPContext.cpp
@@ -6,6 +6,7 @@
#include <string>
#include <unordered_set>
+#include <utility>
#include "nsCOMPtr.h"
#include "nsContentPolicyUtils.h"
@@ -42,6 +43,7 @@
#include "mozilla/Logging.h"
#include "mozilla/Preferences.h"
#include "mozilla/StaticPrefs_security.h"
+#include "mozilla/Variant.h"
#include "mozilla/dom/CSPReportBinding.h"
#include "mozilla/dom/CSPDictionariesBinding.h"
#include "mozilla/ipc/PBackgroundSharedTypes.h"
@@ -741,16 +743,16 @@ nsCSPContext::LogViolationDetails(
continue;
}
- nsAutoString violatedDirective;
- nsAutoString violatedDirectiveString;
+ nsAutoString violatedDirectiveName;
+ nsAutoString violatedDirectiveNameAndValue;
bool reportSample = false;
mPolicies[p]->getViolatedDirectiveInformation(
- SCRIPT_SRC_DIRECTIVE, violatedDirective, violatedDirectiveString,
- &reportSample);
+ SCRIPT_SRC_DIRECTIVE, violatedDirectiveName,
+ violatedDirectiveNameAndValue, &reportSample);
AsyncReportViolation(
aTriggeringElement, aCSPEventListener, nullptr, blockedContentSource,
- nullptr, violatedDirective, violatedDirectiveString,
+ nullptr, violatedDirectiveName, violatedDirectiveNameAndValue,
CSPDirective::SCRIPT_SRC_DIRECTIVE /* aEffectiveDirective */, p,
observerSubject, aSourceFile, reportSample, aScriptSample, aLineNum,
aColumnNum);
@@ -965,7 +967,7 @@ void StripURIForReporting(nsIURI* aSelfURI, nsIURI* aURI,
}
nsresult nsCSPContext::GatherSecurityPolicyViolationEventData(
- nsIURI* aBlockedURI, const nsACString& aBlockedString, nsIURI* aOriginalURI,
+ Resource& aResource, nsIURI* aOriginalURI,
const nsAString& aEffectiveDirective, uint32_t aViolatedPolicyIndex,
const nsAString& aSourceFile, const nsAString& aScriptSample,
uint32_t aLineNum, uint32_t aColumnNum,
@@ -988,13 +990,19 @@ nsresult nsCSPContext::GatherSecurityPolicyViolationEventData(
CopyUTF8toUTF16(mReferrer, aViolationEventInit.mReferrer);
// blocked-uri
- if (aBlockedURI) {
+ // Corresponds to
+ // <https://w3c.github.io/webappsec-csp/#obtain-violation-blocked-uri>.
+ if (aResource.is<nsIURI*>()) {
nsAutoCString reportBlockedURI;
- StripURIForReporting(mSelfURI, aOriginalURI ? aOriginalURI : aBlockedURI,
+ StripURIForReporting(mSelfURI,
+ aOriginalURI ? aOriginalURI : aResource.as<nsIURI*>(),
aEffectiveDirective, reportBlockedURI);
CopyUTF8toUTF16(reportBlockedURI, aViolationEventInit.mBlockedURI);
} else {
- CopyUTF8toUTF16(aBlockedString, aViolationEventInit.mBlockedURI);
+ nsAutoCString blockedContentSource;
+ BlockedContentSourceToString(aResource.as<BlockedContentSource>(),
+ blockedContentSource);
+ CopyUTF8toUTF16(blockedContentSource, aViolationEventInit.mBlockedURI);
}
// effective-directive
@@ -1372,8 +1380,8 @@ class CSPReportSenderRunnable final : public Runnable {
nsIURI* aBlockedURI,
nsCSPContext::BlockedContentSource aBlockedContentSource,
nsIURI* aOriginalURI, uint32_t aViolatedPolicyIndex, bool aReportOnlyFlag,
- const nsAString& aViolatedDirective,
- const nsAString& aViolatedDirectiveString,
+ const nsAString& aViolatedDirectiveName,
+ const nsAString& aViolatedDirectiveNameAndValue,
const CSPDirective aEffectiveDirective, const nsAString& aObserverSubject,
const nsAString& aSourceFile, bool aReportSample,
const nsAString& aScriptSample, uint32_t aLineNum, uint32_t aColumnNum,
@@ -1387,15 +1395,15 @@ class CSPReportSenderRunnable final : public Runnable {
mViolatedPolicyIndex(aViolatedPolicyIndex),
mReportOnlyFlag(aReportOnlyFlag),
mReportSample(aReportSample),
- mViolatedDirective(aViolatedDirective),
- mViolatedDirectiveString(aViolatedDirectiveString),
+ mViolatedDirectiveName(aViolatedDirectiveName),
+ mViolatedDirectiveNameAndValue(aViolatedDirectiveNameAndValue),
mEffectiveDirective(aEffectiveDirective),
mSourceFile(aSourceFile),
mScriptSample(aScriptSample),
mLineNum(aLineNum),
mColumnNum(aColumnNum),
mCSPContext(aCSPContext) {
- NS_ASSERTION(!aViolatedDirective.IsEmpty(),
+ NS_ASSERTION(!aViolatedDirectiveName.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
@@ -1439,18 +1447,19 @@ class CSPReportSenderRunnable final : public Runnable {
// 0) prepare violation data
mozilla::dom::SecurityPolicyViolationEventInit init;
- nsAutoCString blockedContentSource;
- BlockedContentSourceToString(mBlockedContentSource, blockedContentSource);
-
nsAutoString effectiveDirective;
effectiveDirective.AssignASCII(
CSP_CSPDirectiveToString(mEffectiveDirective));
+ using Resource = nsCSPContext::Resource;
+
+ Resource resource = mBlockedURI ? Resource(mBlockedURI.get())
+ : Resource(mBlockedContentSource);
+
nsresult rv = mCSPContext->GatherSecurityPolicyViolationEventData(
- mBlockedURI, blockedContentSource, mOriginalURI, effectiveDirective,
- mViolatedPolicyIndex, mSourceFile,
- mReportSample ? mScriptSample : EmptyString(), mLineNum, mColumnNum,
- init);
+ resource, mOriginalURI, effectiveDirective, mViolatedPolicyIndex,
+ mSourceFile, mReportSample ? mScriptSample : EmptyString(), mLineNum,
+ mColumnNum, init);
NS_ENSURE_SUCCESS(rv, rv);
// 1) notify observers
@@ -1458,7 +1467,7 @@ class CSPReportSenderRunnable final : public Runnable {
mozilla::services::GetObserverService();
if (mObserverSubject && observerService) {
rv = observerService->NotifyObservers(
- mObserverSubject, CSP_VIOLATION_TOPIC, mViolatedDirective.get());
+ mObserverSubject, CSP_VIOLATION_TOPIC, mViolatedDirectiveName.get());
NS_ENSURE_SUCCESS(rv, rv);
}
@@ -1471,7 +1480,7 @@ class CSPReportSenderRunnable final : public Runnable {
// 4) fire violation event
// A frame-ancestors violation has occurred, but we should not dispatch
// the violation event to a potentially cross-origin ancestor.
- if (!mViolatedDirective.EqualsLiteral("frame-ancestors")) {
+ if (!mViolatedDirectiveName.EqualsLiteral("frame-ancestors")) {
mCSPContext->FireViolationEvent(mTriggeringElement, mCSPEventListener,
init);
}
@@ -1502,7 +1511,7 @@ class CSPReportSenderRunnable final : public Runnable {
: "CSPInlineScriptViolation";
}
- AutoTArray<nsString, 2> params = {mViolatedDirectiveString,
+ AutoTArray<nsString, 2> params = {mViolatedDirectiveNameAndValue,
effectiveDirective};
mCSPContext->logToConsole(errorName, params, mSourceFile, mScriptSample,
mLineNum, mColumnNum,
@@ -1511,7 +1520,7 @@ class CSPReportSenderRunnable final : public Runnable {
}
case nsCSPContext::BlockedContentSource::eEval: {
- AutoTArray<nsString, 2> params = {mViolatedDirectiveString,
+ AutoTArray<nsString, 2> params = {mViolatedDirectiveNameAndValue,
effectiveDirective};
mCSPContext->logToConsole(mReportOnlyFlag ? "CSPROEvalScriptViolation"
: "CSPEvalScriptViolation",
@@ -1521,7 +1530,7 @@ class CSPReportSenderRunnable final : public Runnable {
}
case nsCSPContext::BlockedContentSource::eWasmEval: {
- AutoTArray<nsString, 2> params = {mViolatedDirectiveString,
+ AutoTArray<nsString, 2> params = {mViolatedDirectiveNameAndValue,
effectiveDirective};
mCSPContext->logToConsole(mReportOnlyFlag
? "CSPROWasmEvalScriptViolation"
@@ -1569,8 +1578,8 @@ class CSPReportSenderRunnable final : public Runnable {
: "CSPGenericViolation";
}
- AutoTArray<nsString, 3> params = {mViolatedDirectiveString, source,
- effectiveDirective};
+ AutoTArray<nsString, 3> params = {mViolatedDirectiveNameAndValue,
+ source, effectiveDirective};
mCSPContext->logToConsole(errorName, params, mSourceFile, mScriptSample,
mLineNum, mColumnNum,
nsIScriptError::errorFlag);
@@ -1586,8 +1595,8 @@ class CSPReportSenderRunnable final : public Runnable {
uint32_t mViolatedPolicyIndex;
bool mReportOnlyFlag;
bool mReportSample;
- nsString mViolatedDirective;
- nsString mViolatedDirectiveString;
+ nsString mViolatedDirectiveName;
+ nsString mViolatedDirectiveNameAndValue;
CSPDirective mEffectiveDirective;
nsCOMPtr<nsISupports> mObserverSubject;
nsString mSourceFile;
@@ -1609,7 +1618,7 @@ class CSPReportSenderRunnable final : public Runnable {
* of the violation.
* @param aOriginalUri
* The original URI if the blocked content is a redirect, else null
- * @param aViolatedDirective
+ * @param aViolatedDirectiveName
* the directive that was violated (string).
* @param aViolatedPolicyIndex
* the index of the policy that was violated (so we know where to send
@@ -1629,8 +1638,8 @@ class CSPReportSenderRunnable final : public Runnable {
nsresult nsCSPContext::AsyncReportViolation(
Element* aTriggeringElement, nsICSPEventListener* aCSPEventListener,
nsIURI* aBlockedURI, BlockedContentSource aBlockedContentSource,
- nsIURI* aOriginalURI, const nsAString& aViolatedDirective,
- const nsAString& aViolatedDirectiveString,
+ nsIURI* aOriginalURI, const nsAString& aViolatedDirectiveName,
+ const nsAString& aViolatedDirectiveNameAndValue,
const CSPDirective aEffectiveDirective, uint32_t aViolatedPolicyIndex,
const nsAString& aObserverSubject, const nsAString& aSourceFile,
bool aReportSample, const nsAString& aScriptSample, uint32_t aLineNum,
@@ -1641,9 +1650,10 @@ nsresult nsCSPContext::AsyncReportViolation(
nsCOMPtr<nsIRunnable> task = new CSPReportSenderRunnable(
aTriggeringElement, aCSPEventListener, aBlockedURI, aBlockedContentSource,
aOriginalURI, aViolatedPolicyIndex,
- mPolicies[aViolatedPolicyIndex]->getReportOnlyFlag(), aViolatedDirective,
- aViolatedDirectiveString, aEffectiveDirective, aObserverSubject,
- aSourceFile, aReportSample, aScriptSample, aLineNum, aColumnNum, this);
+ mPolicies[aViolatedPolicyIndex]->getReportOnlyFlag(),
+ aViolatedDirectiveName, aViolatedDirectiveNameAndValue,
+ aEffectiveDirective, aObserverSubject, aSourceFile, aReportSample,
+ aScriptSample, aLineNum, aColumnNum, this);
if (XRE_IsContentProcess()) {
if (mEventTarget) {
diff --git a/dom/security/nsCSPContext.h b/dom/security/nsCSPContext.h
index e4fe5af315..f957c85e48 100644
--- a/dom/security/nsCSPContext.h
+++ b/dom/security/nsCSPContext.h
@@ -32,6 +32,8 @@ class nsIEventTarget;
struct ConsoleMsgQueueElem;
namespace mozilla {
+template <typename... Ts>
+class Variant;
namespace dom {
class Element;
}
@@ -77,11 +79,23 @@ class nsCSPContext : public nsIContentSecurityPolicy {
uint32_t aLineNumber, uint32_t aColumnNumber,
uint32_t aSeverityFlag);
+ enum BlockedContentSource {
+ eUnknown,
+ eInline,
+ eEval,
+ eSelf,
+ eWasmEval,
+ };
+
+ // Roughly implements a violation's resource
+ // (https://w3c.github.io/webappsec-csp/#framework-violation).
+ using Resource = mozilla::Variant<nsIURI*, BlockedContentSource>;
+
/**
* Construct SecurityPolicyViolationEventInit structure.
*
- * @param aBlockedURI
- * A nsIURI: the source of the violation.
+ * @param aResource
+ * The source of the violation.
* @param aOriginalUri
* The original URI if the blocked content is a redirect, else null
* @param aViolatedDirective
@@ -98,10 +112,10 @@ class nsCSPContext : public nsIContentSecurityPolicy {
* The output
*/
nsresult GatherSecurityPolicyViolationEventData(
- nsIURI* aBlockedURI, const nsACString& aBlockedString,
- nsIURI* aOriginalURI, const nsAString& aViolatedDirective,
- uint32_t aViolatedPolicyIndex, const nsAString& aSourceFile,
- const nsAString& aScriptSample, uint32_t aLineNum, uint32_t aColumnNum,
+ Resource& aResource, nsIURI* aOriginalURI,
+ const nsAString& aViolatedDirective, uint32_t aViolatedPolicyIndex,
+ const nsAString& aSourceFile, const nsAString& aScriptSample,
+ uint32_t aLineNum, uint32_t aColumnNum,
mozilla::dom::SecurityPolicyViolationEventInit& aViolationEventInit);
nsresult SendReports(
@@ -114,20 +128,12 @@ class nsCSPContext : public nsIContentSecurityPolicy {
const mozilla::dom::SecurityPolicyViolationEventInit&
aViolationEventInit);
- enum BlockedContentSource {
- eUnknown,
- eInline,
- eEval,
- eSelf,
- eWasmEval,
- };
-
nsresult AsyncReportViolation(
mozilla::dom::Element* aTriggeringElement,
nsICSPEventListener* aCSPEventListener, nsIURI* aBlockedURI,
BlockedContentSource aBlockedContentSource, nsIURI* aOriginalURI,
- const nsAString& aViolatedDirective,
- const nsAString& aViolatedDirectiveString,
+ const nsAString& aViolatedDirectiveName,
+ const nsAString& aViolatedDirectiveNameAndValue,
const CSPDirective aEffectiveDirective, uint32_t aViolatedPolicyIndex,
const nsAString& aObserverSubject, const nsAString& aSourceFile,
bool aReportSample, const nsAString& aScriptSample, uint32_t aLineNum,
diff --git a/dom/security/nsCSPParser.cpp b/dom/security/nsCSPParser.cpp
index 07812470a3..44c2131640 100644
--- a/dom/security/nsCSPParser.cpp
+++ b/dom/security/nsCSPParser.cpp
@@ -8,6 +8,7 @@
#include "mozilla/TextUtils.h"
#include "mozilla/dom/Document.h"
#include "mozilla/Preferences.h"
+#include "mozilla/StaticPrefs_dom.h"
#include "mozilla/StaticPrefs_security.h"
#include "nsCOMPtr.h"
#include "nsContentUtils.h"
@@ -19,6 +20,9 @@
#include "nsServiceManagerUtils.h"
#include "nsUnicharUtils.h"
+#include <cstdint>
+#include <utility>
+
using namespace mozilla;
using namespace mozilla::dom;
@@ -813,6 +817,142 @@ void nsCSPParser::sandboxFlagList(nsCSPDirective* aDir) {
mPolicy->addDirective(aDir);
}
+// https://w3c.github.io/trusted-types/dist/spec/#integration-with-content-security-policy
+static constexpr nsLiteralString kValidRequireTrustedTypesForDirectiveValue =
+ u"'script'"_ns;
+
+static bool IsValidRequireTrustedTypesForDirectiveValue(
+ const nsAString& aToken) {
+ return aToken.Equals(kValidRequireTrustedTypesForDirectiveValue);
+}
+
+void nsCSPParser::handleRequireTrustedTypesForDirective(nsCSPDirective* aDir) {
+ // "srcs" start at index 1. Here "srcs" should represent Trusted Types' sink
+ // groups
+ // (https://w3c.github.io/trusted-types/dist/spec/#require-trusted-types-for-csp-directive).
+
+ if (mCurDir.Length() != 2) {
+ nsString numberOfTokensStr;
+
+ // Casting is required to avoid ambiguous function calls on some platforms.
+ numberOfTokensStr.AppendInt(static_cast<uint64_t>(mCurDir.Length()));
+
+ AutoTArray<nsString, 1> numberOfTokensArr = {std::move(numberOfTokensStr)};
+ logWarningErrorToConsole(nsIScriptError::errorFlag,
+ "invalidNumberOfTrustedTypesForDirectiveValues",
+ numberOfTokensArr);
+ return;
+ }
+
+ mCurToken = mCurDir.LastElement();
+
+ CSPPARSERLOG(
+ ("nsCSPParser::handleRequireTrustedTypesForDirective, mCurToken: %s",
+ NS_ConvertUTF16toUTF8(mCurToken).get()));
+
+ if (!IsValidRequireTrustedTypesForDirectiveValue(mCurToken)) {
+ AutoTArray<nsString, 1> token = {mCurToken};
+ logWarningErrorToConsole(nsIScriptError::errorFlag,
+ "invalidRequireTrustedTypesForDirectiveValue",
+ token);
+ return;
+ }
+
+ nsTArray<nsCSPBaseSrc*> srcs = {
+ new nsCSPRequireTrustedTypesForDirectiveValue(mCurToken)};
+
+ aDir->addSrcs(srcs);
+ mPolicy->addDirective(aDir);
+}
+
+static constexpr auto kTrustedTypesKeywordAllowDuplicates =
+ u"'allow-duplicates'"_ns;
+static constexpr auto kTrustedTypesKeywordNone = u"'none'"_ns;
+
+static bool IsValidTrustedTypesKeyword(const nsAString& aToken) {
+ // tt-keyword = "'allow-duplicates'" / "'none'"
+ return aToken.Equals(kTrustedTypesKeywordAllowDuplicates) ||
+ aToken.Equals(kTrustedTypesKeywordNone);
+}
+
+static bool IsValidTrustedTypesWildcard(const nsAString& aToken) {
+ // tt-wildcard = "*"
+ return aToken.Length() == 1 && aToken.First() == WILDCARD;
+}
+
+static bool IsValidTrustedTypesPolicyNameChar(char16_t aChar) {
+ // tt-policy-name = 1*( ALPHA / DIGIT / "-" / "#" / "=" / "_" / "/" / "@" /
+ // "." / "%")
+ return nsContentUtils::IsAlphanumeric(aChar) || aChar == DASH ||
+ aChar == NUMBER_SIGN || aChar == EQUALS || aChar == UNDERLINE ||
+ aChar == SLASH || aChar == ATSYMBOL || aChar == DOT ||
+ aChar == PERCENT_SIGN;
+}
+
+static bool IsValidTrustedTypesPolicyName(const nsAString& aToken) {
+ // tt-policy-name = 1*( ALPHA / DIGIT / "-" / "#" / "=" / "_" / "/" / "@" /
+ // "." / "%")
+
+ if (aToken.IsEmpty()) {
+ return false;
+ }
+
+ for (uint32_t i = 0; i < aToken.Length(); ++i) {
+ if (!IsValidTrustedTypesPolicyNameChar(aToken.CharAt(i))) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+// https://w3c.github.io/trusted-types/dist/spec/#trusted-types-csp-directive
+static bool IsValidTrustedTypesExpression(const nsAString& aToken) {
+ // tt-expression = tt-policy-name / tt-keyword / tt-wildcard
+ return IsValidTrustedTypesPolicyName(aToken) ||
+ IsValidTrustedTypesKeyword(aToken) ||
+ IsValidTrustedTypesWildcard(aToken);
+}
+
+void nsCSPParser::handleTrustedTypesDirective(nsCSPDirective* aDir) {
+ CSPPARSERLOG(("nsCSPParser::handleTrustedTypesDirective"));
+
+ nsTArray<nsCSPBaseSrc*> trustedTypesExpressions;
+
+ // "srcs" start and index 1. Here they should represent the tt-expressions
+ // (https://w3c.github.io/trusted-types/dist/spec/#trusted-types-csp-directive).
+ for (uint32_t i = 1; i < mCurDir.Length(); ++i) {
+ mCurToken = mCurDir[i];
+
+ CSPPARSERLOG(("nsCSPParser::handleTrustedTypesDirective, mCurToken: %s",
+ NS_ConvertUTF16toUTF8(mCurToken).get()));
+
+ if (!IsValidTrustedTypesExpression(mCurToken)) {
+ AutoTArray<nsString, 1> token = {mCurToken};
+ logWarningErrorToConsole(nsIScriptError::errorFlag,
+ "invalidTrustedTypesExpression", token);
+
+ for (auto* trustedTypeExpression : trustedTypesExpressions) {
+ delete trustedTypeExpression;
+ }
+
+ return;
+ }
+
+ trustedTypesExpressions.AppendElement(
+ new nsCSPTrustedTypesDirectivePolicyName(mCurToken));
+ }
+
+ if (trustedTypesExpressions.IsEmpty()) {
+ // No tt-expression is equivalent to 'none', see
+ // <https://w3c.github.io/trusted-types/dist/spec/#trusted-types-csp-directive>.
+ trustedTypesExpressions.AppendElement(new nsCSPKeywordSrc(CSP_NONE));
+ }
+
+ aDir->addSrcs(trustedTypesExpressions);
+ mPolicy->addDirective(aDir);
+}
+
// directive-value = *( WSP / <VCHAR except ";" and ","> )
void nsCSPParser::directiveValue(nsTArray<nsCSPBaseSrc*>& outSrcs) {
CSPPARSERLOG(("nsCSPParser::directiveValue"));
@@ -829,7 +969,11 @@ nsCSPDirective* nsCSPParser::directiveName() {
// Check if it is a valid directive
CSPDirective directive = CSP_StringToCSPDirective(mCurToken);
- if (directive == nsIContentSecurityPolicy::NO_DIRECTIVE) {
+ if (directive == nsIContentSecurityPolicy::NO_DIRECTIVE ||
+ (!StaticPrefs::dom_security_trusted_types_enabled() &&
+ (directive ==
+ nsIContentSecurityPolicy::REQUIRE_TRUSTED_TYPES_FOR_DIRECTIVE ||
+ directive == nsIContentSecurityPolicy::TRUSTED_TYPES_DIRECTIVE))) {
AutoTArray<nsString, 1> params = {mCurToken};
logWarningErrorToConsole(nsIScriptError::warningFlag,
"couldNotProcessUnknownDirective", params);
@@ -1008,6 +1152,19 @@ void nsCSPParser::directive() {
return;
}
+ // Special case handling since these directives don't contain source lists.
+ if (CSP_IsDirective(
+ mCurDir[0],
+ nsIContentSecurityPolicy::REQUIRE_TRUSTED_TYPES_FOR_DIRECTIVE)) {
+ handleRequireTrustedTypesForDirective(cspDir);
+ return;
+ }
+
+ if (cspDir->equals(nsIContentSecurityPolicy::TRUSTED_TYPES_DIRECTIVE)) {
+ handleTrustedTypesDirective(cspDir);
+ return;
+ }
+
// make sure to reset cache variables when trying to invalidate unsafe-inline;
// unsafe-inline might not only appear in script-src, but also in default-src
mHasHashOrNonce = false;
diff --git a/dom/security/nsCSPParser.h b/dom/security/nsCSPParser.h
index 28c24440d0..627acc9820 100644
--- a/dom/security/nsCSPParser.h
+++ b/dom/security/nsCSPParser.h
@@ -72,6 +72,8 @@ class nsCSPParser {
void referrerDirectiveValue(nsCSPDirective* aDir);
void reportURIList(nsCSPDirective* aDir);
void sandboxFlagList(nsCSPDirective* aDir);
+ void handleRequireTrustedTypesForDirective(nsCSPDirective* aDir);
+ void handleTrustedTypesDirective(nsCSPDirective* aDir);
void sourceList(nsTArray<nsCSPBaseSrc*>& outSrcs);
nsCSPBaseSrc* sourceExpression();
nsCSPSchemeSrc* schemeSource();
diff --git a/dom/security/nsCSPUtils.cpp b/dom/security/nsCSPUtils.cpp
index 11d09909f7..f4aecfaf44 100644
--- a/dom/security/nsCSPUtils.cpp
+++ b/dom/security/nsCSPUtils.cpp
@@ -23,6 +23,7 @@
#include "nsServiceManagerUtils.h"
#include "nsWhitespaceTokenizer.h"
+#include "mozilla/Assertions.h"
#include "mozilla/Components.h"
#include "mozilla/dom/CSPDictionariesBinding.h"
#include "mozilla/dom/Document.h"
@@ -243,6 +244,20 @@ void CSP_LogMessage(const nsAString& aMessage, const nsAString& aSourceName,
console->LogMessage(error);
}
+CSPDirective CSP_StringToCSPDirective(const nsAString& aDir) {
+ nsString lowerDir = PromiseFlatString(aDir);
+ ToLowerCase(lowerDir);
+
+ uint32_t numDirs = (sizeof(CSPStrDirectives) / sizeof(CSPStrDirectives[0]));
+
+ for (uint32_t i = 1; i < numDirs; i++) {
+ if (lowerDir.EqualsASCII(CSPStrDirectives[i])) {
+ return static_cast<CSPDirective>(i);
+ }
+ }
+ return nsIContentSecurityPolicy::NO_DIRECTIVE;
+}
+
/**
* Combines CSP_LogMessage and CSP_GetLocalizedStr into one call.
*/
@@ -997,6 +1012,41 @@ void nsCSPSandboxFlags::toString(nsAString& outStr) const {
outStr.Append(mFlags);
}
+/* ===== nsCSPRequireTrustedTypesForDirectiveValue ===================== */
+
+nsCSPRequireTrustedTypesForDirectiveValue::
+ nsCSPRequireTrustedTypesForDirectiveValue(const nsAString& aValue)
+ : mValue{aValue} {}
+
+bool nsCSPRequireTrustedTypesForDirectiveValue::visit(
+ nsCSPSrcVisitor* aVisitor) const {
+ MOZ_ASSERT_UNREACHABLE(
+ "This method should only be called for other overloads of this method.");
+ return false;
+}
+
+void nsCSPRequireTrustedTypesForDirectiveValue::toString(
+ nsAString& aOutStr) const {
+ aOutStr.Append(mValue);
+}
+
+/* =============== nsCSPTrustedTypesDirectivePolicyName =============== */
+
+nsCSPTrustedTypesDirectivePolicyName::nsCSPTrustedTypesDirectivePolicyName(
+ const nsAString& aName)
+ : mName{aName} {}
+
+bool nsCSPTrustedTypesDirectivePolicyName::visit(
+ nsCSPSrcVisitor* aVisitor) const {
+ MOZ_ASSERT_UNREACHABLE(
+ "Should only be called for other overloads of this method.");
+ return false;
+}
+
+void nsCSPTrustedTypesDirectivePolicyName::toString(nsAString& aOutStr) const {
+ aOutStr.Append(mName);
+}
+
/* ===== nsCSPDirective ====================== */
nsCSPDirective::nsCSPDirective(CSPDirective aDirective) {
@@ -1279,6 +1329,9 @@ bool nsCSPDirective::allowsAllInlineBehavior(CSPDirective aDir) const {
void nsCSPDirective::toString(nsAString& outStr) const {
// Append directive name
outStr.AppendASCII(CSP_CSPDirectiveToString(mDirective));
+
+ MOZ_ASSERT(!mSrcs.IsEmpty());
+
outStr.AppendLiteral(" ");
// Append srcs
@@ -1414,6 +1467,21 @@ void nsCSPDirective::toDomCSPStruct(mozilla::dom::CSP& outCSP) const {
outCSP.mScript_src_attr.Value() = std::move(srcs);
return;
+ case nsIContentSecurityPolicy::REQUIRE_TRUSTED_TYPES_FOR_DIRECTIVE:
+ outCSP.mRequire_trusted_types_for.Construct();
+
+ // Here, the srcs represent the sink group
+ // (https://w3c.github.io/trusted-types/dist/spec/#integration-with-content-security-policy).
+ outCSP.mRequire_trusted_types_for.Value() = std::move(srcs);
+ return;
+
+ case nsIContentSecurityPolicy::TRUSTED_TYPES_DIRECTIVE:
+ outCSP.mTrusted_types.Construct();
+ // Here, "srcs" represents tt-expressions
+ // (https://w3c.github.io/trusted-types/dist/spec/#trusted-types-csp-directive).
+ outCSP.mTrusted_types.Value() = std::move(srcs);
+ return;
+
default:
NS_ASSERTION(false, "cannot find directive to convert CSP to JSON");
}
@@ -1693,24 +1761,23 @@ bool nsCSPPolicy::allowsAllInlineBehavior(CSPDirective aDir) const {
/*
* Use this function only after ::allows() returned 'false'. Most and
* foremost it's used to get the violated directive before sending reports.
- * The parameter outDirective is the equivalent of 'outViolatedDirective'
+ * The parameter aDirectiveName is the equivalent of 'outViolatedDirective'
* for the ::permits() function family.
*/
-void nsCSPPolicy::getViolatedDirectiveInformation(CSPDirective aDirective,
- nsAString& outDirective,
- nsAString& outDirectiveString,
- bool* aReportSample) const {
+void nsCSPPolicy::getViolatedDirectiveInformation(
+ CSPDirective aDirective, nsAString& aDirectiveName,
+ nsAString& aDirectiveNameAndValue, bool* aReportSample) const {
*aReportSample = false;
nsCSPDirective* directive = matchingOrDefaultDirective(aDirective);
if (!directive) {
MOZ_ASSERT_UNREACHABLE("Can not query violated directive");
- outDirective.AppendLiteral("couldNotQueryViolatedDirective");
- outDirective.Truncate();
+ aDirectiveName.Truncate();
+ aDirectiveNameAndValue.Truncate();
return;
}
- directive->getDirName(outDirective);
- directive->toString(outDirectiveString);
+ directive->getDirName(aDirectiveName);
+ directive->toString(aDirectiveNameAndValue);
*aReportSample = directive->hasReportSampleKeyword();
}
diff --git a/dom/security/nsCSPUtils.h b/dom/security/nsCSPUtils.h
index eeccaf0c4a..b9ef52967e 100644
--- a/dom/security/nsCSPUtils.h
+++ b/dom/security/nsCSPUtils.h
@@ -93,24 +93,15 @@ static const char* CSPStrDirectives[] = {
"script-src-attr", // SCRIPT_SRC_ATTR_DIRECTIVE
"style-src-elem", // STYLE_SRC_ELEM_DIRECTIVE
"style-src-attr", // STYLE_SRC_ATTR_DIRECTIVE
+ "require-trusted-types-for", // REQUIRE_TRUSTED_TYPES_FOR_DIRECTIVE
+ "trusted-types", // TRUSTED_TYPES_DIRECTIVE
};
inline const char* CSP_CSPDirectiveToString(CSPDirective aDir) {
return CSPStrDirectives[static_cast<uint32_t>(aDir)];
}
-inline CSPDirective CSP_StringToCSPDirective(const nsAString& aDir) {
- nsString lowerDir = PromiseFlatString(aDir);
- ToLowerCase(lowerDir);
-
- uint32_t numDirs = (sizeof(CSPStrDirectives) / sizeof(CSPStrDirectives[0]));
- for (uint32_t i = 1; i < numDirs; i++) {
- if (lowerDir.EqualsASCII(CSPStrDirectives[i])) {
- return static_cast<CSPDirective>(i);
- }
- }
- return nsIContentSecurityPolicy::NO_DIRECTIVE;
-}
+CSPDirective CSP_StringToCSPDirective(const nsAString& aDir);
#define FOR_EACH_CSP_KEYWORD(MACRO) \
MACRO(CSP_SELF, "'self'") \
@@ -396,6 +387,34 @@ class nsCSPSandboxFlags : public nsCSPBaseSrc {
nsString mFlags;
};
+/* =============== nsCSPRequireTrustedTypesForDirectiveValue =============== */
+
+class nsCSPRequireTrustedTypesForDirectiveValue : public nsCSPBaseSrc {
+ public:
+ explicit nsCSPRequireTrustedTypesForDirectiveValue(const nsAString& aValue);
+ virtual ~nsCSPRequireTrustedTypesForDirectiveValue() = default;
+
+ bool visit(nsCSPSrcVisitor* aVisitor) const override;
+ void toString(nsAString& aOutStr) const override;
+
+ private:
+ const nsString mValue;
+};
+
+/* =============== nsCSPTrustedTypesDirectiveExpression =============== */
+
+class nsCSPTrustedTypesDirectivePolicyName : public nsCSPBaseSrc {
+ public:
+ explicit nsCSPTrustedTypesDirectivePolicyName(const nsAString& aName);
+ virtual ~nsCSPTrustedTypesDirectivePolicyName() = default;
+
+ bool visit(nsCSPSrcVisitor* aVisitor) const override;
+ void toString(nsAString& aOutStr) const override;
+
+ private:
+ const nsString mName;
+};
+
/* =============== nsCSPSrcVisitor ================== */
class nsCSPSrcVisitor {
@@ -431,6 +450,7 @@ class nsCSPDirective {
virtual void toString(nsAString& outStr) const;
void toDomCSPStruct(mozilla::dom::CSP& outCSP) const;
+ // Takes ownership of the passed sources.
virtual void addSrcs(const nsTArray<nsCSPBaseSrc*>& aSrcs) {
mSrcs = aSrcs.Clone();
}
@@ -652,8 +672,8 @@ class nsCSPPolicy {
void getReportURIs(nsTArray<nsString>& outReportURIs) const;
void getViolatedDirectiveInformation(CSPDirective aDirective,
- nsAString& outDirective,
- nsAString& outDirectiveString,
+ nsAString& aDirectiveName,
+ nsAString& aDirectiveNameAndValue,
bool* aReportSample) const;
uint32_t getSandboxFlags() const;
diff --git a/dom/security/nsHTTPSOnlyUtils.cpp b/dom/security/nsHTTPSOnlyUtils.cpp
index 31c7408a37..0bc99179dc 100644
--- a/dom/security/nsHTTPSOnlyUtils.cpp
+++ b/dom/security/nsHTTPSOnlyUtils.cpp
@@ -601,35 +601,28 @@ nsHTTPSOnlyUtils::PotentiallyDowngradeHttpsFirstRequest(
if (navigationStart) {
mozilla::TimeDuration duration =
mozilla::TimeStamp::Now() - navigationStart;
+
bool isPrivateWin =
loadInfo->GetOriginAttributes().mPrivateBrowsingId > 0;
+ bool isSchemeless =
+ loadInfo->GetWasSchemelessInput() &&
+ !nsHTTPSOnlyUtils::IsHttpsFirstModeEnabled(isPrivateWin);
- if (loadInfo->GetWasSchemelessInput() &&
- !IsHttpsFirstModeEnabled(isPrivateWin)) {
- mozilla::glean::httpsfirst::downgraded_schemeless.Add();
- if (timing) {
- mozilla::glean::httpsfirst::downgrade_time_schemeless
- .AccumulateRawDuration(duration);
- }
- } else {
- mozilla::glean::httpsfirst::downgraded.Add();
- if (timing) {
- mozilla::glean::httpsfirst::downgrade_time.AccumulateRawDuration(
- duration);
- }
- }
+ using namespace mozilla::glean::httpsfirst;
+ auto downgradedMetric = isSchemeless ? downgraded_schemeless : downgraded;
+ auto downgradedOnTimerMetric =
+ isSchemeless ? downgraded_on_timer_schemeless : downgraded_on_timer;
+ auto downgradeTimeMetric =
+ isSchemeless ? downgrade_time_schemeless : downgrade_time;
nsresult channelStatus;
channel->GetStatus(&channelStatus);
if (channelStatus == NS_ERROR_NET_TIMEOUT_EXTERNAL) {
- if (loadInfo->GetWasSchemelessInput() &&
- !nsHTTPSOnlyUtils::IsHttpsFirstModeEnabled(isPrivateWin)) {
- mozilla::glean::httpsfirst::downgraded_on_timer_schemeless
- .AddToNumerator();
- } else {
- mozilla::glean::httpsfirst::downgraded_on_timer.AddToNumerator();
- }
+ downgradedOnTimerMetric.AddToNumerator();
+ } else {
+ downgradeTimeMetric.AccumulateRawDuration(duration);
}
+ downgradedMetric.Add();
}
}
diff --git a/dom/security/test/gtest/TestCSPParser.cpp b/dom/security/test/gtest/TestCSPParser.cpp
index 19ba0548de..388055f388 100644
--- a/dom/security/test/gtest/TestCSPParser.cpp
+++ b/dom/security/test/gtest/TestCSPParser.cpp
@@ -152,9 +152,14 @@ nsresult runTestSuite(const PolicyTest* aPolicies, uint32_t aPolicyCount,
// Add prefs you need to set to parse CSP here, see comments for example
// bool examplePref = false;
+ bool trustedTypesEnabled = false;
+ constexpr auto kTrustedTypesEnabledPrefName =
+ "dom.security.trusted_types.enabled";
if (prefs) {
// prefs->GetBoolPref("security.csp.examplePref", &examplePref);
// prefs->SetBoolPref("security.csp.examplePref", true);
+ prefs->GetBoolPref(kTrustedTypesEnabledPrefName, &trustedTypesEnabled);
+ prefs->SetBoolPref(kTrustedTypesEnabledPrefName, true);
}
for (uint32_t i = 0; i < aPolicyCount; i++) {
@@ -165,6 +170,7 @@ nsresult runTestSuite(const PolicyTest* aPolicies, uint32_t aPolicyCount,
if (prefs) {
// prefs->SetBoolPref("security.csp.examplePref", examplePref);
+ prefs->SetBoolPref(kTrustedTypesEnabledPrefName, trustedTypesEnabled);
}
return NS_OK;
@@ -220,6 +226,11 @@ TEST(CSPParser, Directives)
"worker-src http://worker.com; frame-src http://frame.com; child-src http://child.com" },
{ "script-src 'unsafe-allow-redirects' http://example.com",
"script-src http://example.com"},
+ { "require-trusted-types-for 'script'",
+ "require-trusted-types-for 'script'" },
+ { "trusted-types somePolicyName", "trusted-types somePolicyName" },
+ { "trusted-types somePolicyName anotherPolicyName 1 - # = _ / @ . % *",
+ "trusted-types somePolicyName anotherPolicyName 1 - # = _ / @ . % *" },
// clang-format on
};
@@ -247,6 +258,11 @@ TEST(CSPParser, Keywords)
"script-src 'wasm-unsafe-eval'" },
{ "img-src 'none'; script-src 'unsafe-eval' 'unsafe-inline'; default-src 'self'",
"img-src 'none'; script-src 'unsafe-eval' 'unsafe-inline'; default-src 'self'" },
+ { "trusted-types somePolicyName 'allow-duplicates'",
+ "trusted-types somePolicyName 'allow-duplicates'" },
+ { "trusted-types 'none'", "trusted-types 'none'" },
+ { "trusted-types", "trusted-types 'none'" },
+ { "trusted-types *", "trusted-types *" },
// clang-format on
};
@@ -589,6 +605,7 @@ TEST(CSPParser, BadPolicies)
{ "report-uri http://:foo", ""},
{ "require-sri-for", ""},
{ "require-sri-for style", ""},
+ { "trusted-types $", ""},
// clang-format on
};
diff --git a/dom/security/test/https-first/browser_httpsfirst.js b/dom/security/test/https-first/browser_httpsfirst.js
index c4437f6051..e0bba26f73 100644
--- a/dom/security/test/https-first/browser_httpsfirst.js
+++ b/dom/security/test/https-first/browser_httpsfirst.js
@@ -99,11 +99,10 @@ add_task(async function () {
is(Glean.httpsfirst.downgradedOnTimerSchemeless.testGetValue(), null);
const downgradeSeconds =
Glean.httpsfirst.downgradeTime.testGetValue().sum / 1_000_000_000;
- ok(
- downgradeSeconds > 2 && downgradeSeconds < 30,
- `Summed downgrade time should be above 2 and below 30 seconds (is ${downgradeSeconds.toFixed(
- 2
- )}s)`
+ Assert.less(
+ downgradeSeconds,
+ 10,
+ "Summed downgrade time should be below 10 seconds"
);
is(null, Glean.httpsfirst.downgradeTimeSchemeless.testGetValue());
});