diff options
Diffstat (limited to 'dom/security')
-rw-r--r-- | dom/security/FramingChecker.cpp | 4 | ||||
-rw-r--r-- | dom/security/metrics.yaml | 14 | ||||
-rw-r--r-- | dom/security/nsCSPContext.cpp | 82 | ||||
-rw-r--r-- | dom/security/nsCSPContext.h | 38 | ||||
-rw-r--r-- | dom/security/nsCSPParser.cpp | 159 | ||||
-rw-r--r-- | dom/security/nsCSPParser.h | 2 | ||||
-rw-r--r-- | dom/security/nsCSPUtils.cpp | 85 | ||||
-rw-r--r-- | dom/security/nsCSPUtils.h | 48 | ||||
-rw-r--r-- | dom/security/nsHTTPSOnlyUtils.cpp | 35 | ||||
-rw-r--r-- | dom/security/test/gtest/TestCSPParser.cpp | 17 | ||||
-rw-r--r-- | dom/security/test/https-first/browser_httpsfirst.js | 9 |
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()); }); |