From 26a029d407be480d791972afb5975cf62c9360a6 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 02:47:55 +0200 Subject: Adding upstream version 124.0.1. Signed-off-by: Daniel Baumann --- dom/security/nsCSPContext.cpp | 1974 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1974 insertions(+) create mode 100644 dom/security/nsCSPContext.cpp (limited to 'dom/security/nsCSPContext.cpp') diff --git a/dom/security/nsCSPContext.cpp b/dom/security/nsCSPContext.cpp new file mode 100644 index 0000000000..aafd2b64f2 --- /dev/null +++ b/dom/security/nsCSPContext.cpp @@ -0,0 +1,1974 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include +#include + +#include "nsCOMPtr.h" +#include "nsContentPolicyUtils.h" +#include "nsContentSecurityUtils.h" +#include "nsContentUtils.h" +#include "nsCSPContext.h" +#include "nsCSPParser.h" +#include "nsCSPService.h" +#include "nsGlobalWindowOuter.h" +#include "nsError.h" +#include "nsIAsyncVerifyRedirectCallback.h" +#include "nsIClassInfoImpl.h" +#include "mozilla/dom/Document.h" +#include "nsIHttpChannel.h" +#include "nsIInterfaceRequestor.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsIObjectInputStream.h" +#include "nsIObjectOutputStream.h" +#include "nsIObserver.h" +#include "nsIObserverService.h" +#include "nsIStringStream.h" +#include "nsISupportsPrimitives.h" +#include "nsIUploadChannel.h" +#include "nsIURIMutator.h" +#include "nsIScriptError.h" +#include "nsMimeTypes.h" +#include "nsNetUtil.h" +#include "nsIContentPolicy.h" +#include "nsSupportsPrimitives.h" +#include "nsThreadUtils.h" +#include "nsString.h" +#include "nsScriptSecurityManager.h" +#include "nsStringStream.h" +#include "mozilla/Logging.h" +#include "mozilla/Preferences.h" +#include "mozilla/StaticPrefs_security.h" +#include "mozilla/dom/CSPReportBinding.h" +#include "mozilla/dom/CSPDictionariesBinding.h" +#include "mozilla/ipc/PBackgroundSharedTypes.h" +#include "mozilla/dom/WindowGlobalParent.h" +#include "nsINetworkInterceptController.h" +#include "nsSandboxFlags.h" +#include "nsIScriptElement.h" +#include "nsIEventTarget.h" +#include "mozilla/dom/DocGroup.h" +#include "mozilla/dom/Element.h" +#include "nsXULAppAPI.h" +#include "nsJSUtils.h" + +using namespace mozilla; +using namespace mozilla::dom; +using namespace mozilla::ipc; + +static LogModule* GetCspContextLog() { + static LazyLogModule gCspContextPRLog("CSPContext"); + return gCspContextPRLog; +} + +#define CSPCONTEXTLOG(args) \ + MOZ_LOG(GetCspContextLog(), mozilla::LogLevel::Debug, args) +#define CSPCONTEXTLOGENABLED() \ + MOZ_LOG_TEST(GetCspContextLog(), mozilla::LogLevel::Debug) + +static LogModule* GetCspOriginLogLog() { + static LazyLogModule gCspOriginPRLog("CSPOrigin"); + return gCspOriginPRLog; +} + +#define CSPORIGINLOG(args) \ + MOZ_LOG(GetCspOriginLogLog(), mozilla::LogLevel::Debug, args) +#define CSPORIGINLOGENABLED() \ + MOZ_LOG_TEST(GetCspOriginLogLog(), mozilla::LogLevel::Debug) + +#ifdef DEBUG +/** + * This function is only used for verification purposes within + * GatherSecurityPolicyViolationEventData. + */ +static bool ValidateDirectiveName(const nsAString& aDirective) { + static const auto directives = []() { + std::unordered_set directives; + constexpr size_t dirLen = + sizeof(CSPStrDirectives) / sizeof(CSPStrDirectives[0]); + for (size_t i = 0; i < dirLen; ++i) { + directives.insert(CSPStrDirectives[i]); + } + return directives; + }(); + + nsAutoString directive(aDirective); + auto itr = directives.find(NS_ConvertUTF16toUTF8(directive).get()); + return itr != directives.end(); +} +#endif // DEBUG + +static void BlockedContentSourceToString( + nsCSPContext::BlockedContentSource aSource, nsACString& aString) { + switch (aSource) { + case nsCSPContext::BlockedContentSource::eUnknown: + aString.Truncate(); + break; + + case nsCSPContext::BlockedContentSource::eInline: + aString.AssignLiteral("inline"); + break; + + case nsCSPContext::BlockedContentSource::eEval: + aString.AssignLiteral("eval"); + break; + + case nsCSPContext::BlockedContentSource::eSelf: + aString.AssignLiteral("self"); + break; + + case nsCSPContext::BlockedContentSource::eWasmEval: + aString.AssignLiteral("wasm-eval"); + break; + } +} + +/* ===== nsIContentSecurityPolicy impl ====== */ + +NS_IMETHODIMP +nsCSPContext::ShouldLoad(nsContentPolicyType aContentType, + nsICSPEventListener* aCSPEventListener, + nsILoadInfo* aLoadInfo, nsIURI* aContentLocation, + nsIURI* aOriginalURIIfRedirect, + bool aSendViolationReports, int16_t* outDecision) { + if (CSPCONTEXTLOGENABLED()) { + CSPCONTEXTLOG(("nsCSPContext::ShouldLoad, aContentLocation: %s", + aContentLocation->GetSpecOrDefault().get())); + CSPCONTEXTLOG((">>>> aContentType: %s", + NS_CP_ContentTypeName(aContentType))); + } + + // This ShouldLoad function is called from nsCSPService::ShouldLoad, + // which already checked a number of things, including: + // * aContentLocation is not null; we can consume this without further checks + // * scheme is not a allowlisted scheme (about: chrome:, etc). + // * CSP is enabled + // * Content Type is not allowlisted (CSP Reports, TYPE_DOCUMENT, etc). + // * Fast Path for Apps + + // Default decision, CSP can revise it if there's a policy to enforce + *outDecision = nsIContentPolicy::ACCEPT; + + // If the content type doesn't map to a CSP directive, there's nothing for + // CSP to do. + CSPDirective dir = CSP_ContentTypeToDirective(aContentType); + if (dir == nsIContentSecurityPolicy::NO_DIRECTIVE) { + return NS_OK; + } + + bool permitted = permitsInternal( + dir, + nullptr, // aTriggeringElement + aCSPEventListener, aLoadInfo, aContentLocation, aOriginalURIIfRedirect, + false, // allow fallback to default-src + aSendViolationReports, + true); // send blocked URI in violation reports + + *outDecision = + permitted ? nsIContentPolicy::ACCEPT : nsIContentPolicy::REJECT_SERVER; + + if (CSPCONTEXTLOGENABLED()) { + CSPCONTEXTLOG( + ("nsCSPContext::ShouldLoad, decision: %s, " + "aContentLocation: %s", + *outDecision > 0 ? "load" : "deny", + aContentLocation->GetSpecOrDefault().get())); + } + return NS_OK; +} + +bool nsCSPContext::permitsInternal( + CSPDirective aDir, Element* aTriggeringElement, + nsICSPEventListener* aCSPEventListener, nsILoadInfo* aLoadInfo, + nsIURI* aContentLocation, nsIURI* aOriginalURIIfRedirect, bool aSpecific, + bool aSendViolationReports, bool aSendContentLocationInViolationReports) { + EnsureIPCPoliciesRead(); + bool permits = true; + + nsAutoString violatedDirective; + for (uint32_t p = 0; p < mPolicies.Length(); p++) { + if (!mPolicies[p]->permits(aDir, aLoadInfo, aContentLocation, + !!aOriginalURIIfRedirect, aSpecific, + violatedDirective)) { + // If the policy is violated and not report-only, reject the load and + // report to the console + if (!mPolicies[p]->getReportOnlyFlag()) { + CSPCONTEXTLOG(("nsCSPContext::permitsInternal, false")); + permits = false; + } + + // In CSP 3.0 the effective directive doesn't become the actually used + // directive in case of a fallback from e.g. script-src-elem to + // script-src or default-src. + nsAutoString effectiveDirective; + effectiveDirective.AssignASCII(CSP_CSPDirectiveToString(aDir)); + + // Callers should set |aSendViolationReports| to false if this is a + // preload - the decision may be wrong due to the inability to get the + // nonce, and will incorrectly fail the unit tests. + if (aSendViolationReports) { + uint32_t lineNumber = 0; + uint32_t columnNumber = 1; + nsAutoString spec; + JSContext* cx = nsContentUtils::GetCurrentJSContext(); + if (cx) { + nsJSUtils::GetCallingLocation(cx, spec, &lineNumber, &columnNumber); + // If GetCallingLocation fails linenumber & columnNumber are set to + // (0, 1) anyway so we can skip checking if that is the case. + } + AsyncReportViolation( + aTriggeringElement, aCSPEventListener, + (aSendContentLocationInViolationReports ? aContentLocation + : nullptr), + BlockedContentSource::eUnknown, /* a BlockedContentSource */ + aOriginalURIIfRedirect, /* in case of redirect originalURI is not + null */ + violatedDirective, effectiveDirective, p, /* policy index */ + u""_ns, /* no observer subject */ + spec, /* source file */ + false, // aReportSample (no sample) + u""_ns, /* no script sample */ + lineNumber, /* line number */ + columnNumber); /* column number */ + } + } + } + + return permits; +} + +/* ===== nsISupports implementation ========== */ + +NS_IMPL_CLASSINFO(nsCSPContext, nullptr, 0, NS_CSPCONTEXT_CID) + +NS_IMPL_ISUPPORTS_CI(nsCSPContext, nsIContentSecurityPolicy, nsISerializable) + +nsCSPContext::nsCSPContext() + : mInnerWindowID(0), + mSkipAllowInlineStyleCheck(false), + mLoadingContext(nullptr), + mLoadingPrincipal(nullptr), + mQueueUpMessages(true) { + CSPCONTEXTLOG(("nsCSPContext::nsCSPContext")); +} + +nsCSPContext::~nsCSPContext() { + CSPCONTEXTLOG(("nsCSPContext::~nsCSPContext")); + for (uint32_t i = 0; i < mPolicies.Length(); i++) { + delete mPolicies[i]; + } +} + +/* static */ +bool nsCSPContext::Equals(nsIContentSecurityPolicy* aCSP, + nsIContentSecurityPolicy* aOtherCSP) { + if (aCSP == aOtherCSP) { + // fast path for pointer equality + return true; + } + + uint32_t policyCount = 0; + if (aCSP) { + aCSP->GetPolicyCount(&policyCount); + } + + uint32_t otherPolicyCount = 0; + if (aOtherCSP) { + aOtherCSP->GetPolicyCount(&otherPolicyCount); + } + + if (policyCount != otherPolicyCount) { + return false; + } + + nsAutoString policyStr, otherPolicyStr; + for (uint32_t i = 0; i < policyCount; ++i) { + aCSP->GetPolicyString(i, policyStr); + aOtherCSP->GetPolicyString(i, otherPolicyStr); + if (!policyStr.Equals(otherPolicyStr)) { + return false; + } + } + + return true; +} + +nsresult nsCSPContext::InitFromOther(nsCSPContext* aOtherContext) { + NS_ENSURE_ARG(aOtherContext); + + nsresult rv = NS_OK; + nsCOMPtr doc = do_QueryReferent(aOtherContext->mLoadingContext); + if (doc) { + rv = SetRequestContextWithDocument(doc); + } else { + rv = SetRequestContextWithPrincipal( + aOtherContext->mLoadingPrincipal, aOtherContext->mSelfURI, + aOtherContext->mReferrer, aOtherContext->mInnerWindowID); + } + NS_ENSURE_SUCCESS(rv, rv); + + mSkipAllowInlineStyleCheck = aOtherContext->mSkipAllowInlineStyleCheck; + + // This policy was already parsed somewhere else, don't emit parsing errors. + mSuppressParserLogMessages = true; + for (auto policy : aOtherContext->mPolicies) { + nsAutoString policyStr; + policy->toString(policyStr); + AppendPolicy(policyStr, policy->getReportOnlyFlag(), + policy->getDeliveredViaMetaTagFlag()); + } + + mSuppressParserLogMessages = aOtherContext->mSuppressParserLogMessages; + + mIPCPolicies = aOtherContext->mIPCPolicies.Clone(); + return NS_OK; +} + +NS_IMETHODIMP +nsCSPContext::EnsureIPCPoliciesRead() { + // Most likely the parser errors already happened before serializing + // the policy for IPC. + bool previous = mSuppressParserLogMessages; + mSuppressParserLogMessages = true; + + if (mIPCPolicies.Length() > 0) { + nsresult rv; + for (auto& policy : mIPCPolicies) { + rv = AppendPolicy(policy.policy(), policy.reportOnlyFlag(), + policy.deliveredViaMetaTagFlag()); + Unused << NS_WARN_IF(NS_FAILED(rv)); + } + mIPCPolicies.Clear(); + } + + mSuppressParserLogMessages = previous; + return NS_OK; +} + +NS_IMETHODIMP +nsCSPContext::GetPolicyString(uint32_t aIndex, nsAString& outStr) { + outStr.Truncate(); + EnsureIPCPoliciesRead(); + if (aIndex >= mPolicies.Length()) { + return NS_ERROR_ILLEGAL_VALUE; + } + mPolicies[aIndex]->toString(outStr); + return NS_OK; +} + +const nsCSPPolicy* nsCSPContext::GetPolicy(uint32_t aIndex) { + EnsureIPCPoliciesRead(); + if (aIndex >= mPolicies.Length()) { + return nullptr; + } + return mPolicies[aIndex]; +} + +NS_IMETHODIMP +nsCSPContext::GetPolicyCount(uint32_t* outPolicyCount) { + EnsureIPCPoliciesRead(); + *outPolicyCount = mPolicies.Length(); + return NS_OK; +} + +NS_IMETHODIMP +nsCSPContext::GetUpgradeInsecureRequests(bool* outUpgradeRequest) { + EnsureIPCPoliciesRead(); + *outUpgradeRequest = false; + for (uint32_t i = 0; i < mPolicies.Length(); i++) { + if (mPolicies[i]->hasDirective( + nsIContentSecurityPolicy::UPGRADE_IF_INSECURE_DIRECTIVE) && + !mPolicies[i]->getReportOnlyFlag()) { + *outUpgradeRequest = true; + return NS_OK; + } + } + return NS_OK; +} + +NS_IMETHODIMP +nsCSPContext::GetBlockAllMixedContent(bool* outBlockAllMixedContent) { + EnsureIPCPoliciesRead(); + *outBlockAllMixedContent = false; + for (uint32_t i = 0; i < mPolicies.Length(); i++) { + if (!mPolicies[i]->getReportOnlyFlag() && + mPolicies[i]->hasDirective( + nsIContentSecurityPolicy::BLOCK_ALL_MIXED_CONTENT)) { + *outBlockAllMixedContent = true; + return NS_OK; + } + } + return NS_OK; +} + +NS_IMETHODIMP +nsCSPContext::GetEnforcesFrameAncestors(bool* outEnforcesFrameAncestors) { + EnsureIPCPoliciesRead(); + *outEnforcesFrameAncestors = false; + for (uint32_t i = 0; i < mPolicies.Length(); i++) { + if (!mPolicies[i]->getReportOnlyFlag() && + mPolicies[i]->hasDirective( + nsIContentSecurityPolicy::FRAME_ANCESTORS_DIRECTIVE)) { + *outEnforcesFrameAncestors = true; + return NS_OK; + } + } + return NS_OK; +} + +NS_IMETHODIMP +nsCSPContext::AppendPolicy(const nsAString& aPolicyString, bool aReportOnly, + bool aDeliveredViaMetaTag) { + CSPCONTEXTLOG(("nsCSPContext::AppendPolicy: %s", + NS_ConvertUTF16toUTF8(aPolicyString).get())); + + // Use mSelfURI from setRequestContextWith{Document,Principal} (bug 991474) + MOZ_ASSERT( + mLoadingPrincipal, + "did you forget to call setRequestContextWith{Document,Principal}?"); + MOZ_ASSERT( + mSelfURI, + "did you forget to call setRequestContextWith{Document,Principal}?"); + NS_ENSURE_TRUE(mLoadingPrincipal, NS_ERROR_UNEXPECTED); + NS_ENSURE_TRUE(mSelfURI, NS_ERROR_UNEXPECTED); + + if (CSPORIGINLOGENABLED()) { + nsAutoCString selfURISpec; + mSelfURI->GetSpec(selfURISpec); + CSPORIGINLOG(("CSP - AppendPolicy")); + CSPORIGINLOG((" * selfURI: %s", selfURISpec.get())); + CSPORIGINLOG((" * reportOnly: %s", aReportOnly ? "yes" : "no")); + CSPORIGINLOG( + (" * deliveredViaMetaTag: %s", aDeliveredViaMetaTag ? "yes" : "no")); + CSPORIGINLOG( + (" * policy: %s\n", NS_ConvertUTF16toUTF8(aPolicyString).get())); + } + + nsCSPPolicy* policy = nsCSPParser::parseContentSecurityPolicy( + aPolicyString, mSelfURI, aReportOnly, this, aDeliveredViaMetaTag, + mSuppressParserLogMessages); + if (policy) { + if (policy->hasDirective( + nsIContentSecurityPolicy::UPGRADE_IF_INSECURE_DIRECTIVE)) { + nsAutoCString selfURIspec, referrer; + if (mSelfURI) { + mSelfURI->GetAsciiSpec(selfURIspec); + } + CopyUTF16toUTF8(mReferrer, referrer); + CSPCONTEXTLOG( + ("nsCSPContext::AppendPolicy added UPGRADE_IF_INSECURE_DIRECTIVE " + "self-uri=%s referrer=%s", + selfURIspec.get(), referrer.get())); + } + + mPolicies.AppendElement(policy); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsCSPContext::GetAllowsEval(bool* outShouldReportViolation, + bool* outAllowsEval) { + EnsureIPCPoliciesRead(); + *outShouldReportViolation = false; + *outAllowsEval = true; + + for (uint32_t i = 0; i < mPolicies.Length(); i++) { + if (!mPolicies[i]->allows(SCRIPT_SRC_DIRECTIVE, CSP_UNSAFE_EVAL, u""_ns)) { + // policy is violated: must report the violation and allow the inline + // script if the policy is report-only. + *outShouldReportViolation = true; + if (!mPolicies[i]->getReportOnlyFlag()) { + *outAllowsEval = false; + } + } + } + return NS_OK; +} + +NS_IMETHODIMP +nsCSPContext::GetAllowsWasmEval(bool* outShouldReportViolation, + bool* outAllowsWasmEval) { + EnsureIPCPoliciesRead(); + *outShouldReportViolation = false; + *outAllowsWasmEval = true; + + for (uint32_t i = 0; i < mPolicies.Length(); i++) { + // Either 'unsafe-eval' or 'wasm-unsafe-eval' can allow this + if (!mPolicies[i]->allows(SCRIPT_SRC_DIRECTIVE, CSP_WASM_UNSAFE_EVAL, + u""_ns) && + !mPolicies[i]->allows(SCRIPT_SRC_DIRECTIVE, CSP_UNSAFE_EVAL, u""_ns)) { + // policy is violated: must report the violation and allow the inline + // script if the policy is report-only. + *outShouldReportViolation = true; + if (!mPolicies[i]->getReportOnlyFlag()) { + *outAllowsWasmEval = false; + } + } + } + + return NS_OK; +} + +// Helper function to report inline violations +void nsCSPContext::reportInlineViolation( + CSPDirective aDirective, Element* aTriggeringElement, + nsICSPEventListener* aCSPEventListener, const nsAString& aNonce, + bool aReportSample, const nsAString& aSample, + const nsAString& aViolatedDirective, const nsAString& aEffectiveDirective, + uint32_t aViolatedPolicyIndex, // TODO, use report only flag for that + uint32_t aLineNumber, uint32_t aColumnNumber) { + nsString observerSubject; + // if the nonce is non empty, then we report the nonce error, otherwise + // let's report the hash error; no need to report the unsafe-inline error + // anymore. + if (!aNonce.IsEmpty()) { + observerSubject = (aDirective == SCRIPT_SRC_ELEM_DIRECTIVE || + aDirective == SCRIPT_SRC_ATTR_DIRECTIVE) + ? NS_LITERAL_STRING_FROM_CSTRING( + SCRIPT_NONCE_VIOLATION_OBSERVER_TOPIC) + : NS_LITERAL_STRING_FROM_CSTRING( + STYLE_NONCE_VIOLATION_OBSERVER_TOPIC); + } else { + observerSubject = (aDirective == SCRIPT_SRC_ELEM_DIRECTIVE || + aDirective == SCRIPT_SRC_ATTR_DIRECTIVE) + ? NS_LITERAL_STRING_FROM_CSTRING( + SCRIPT_HASH_VIOLATION_OBSERVER_TOPIC) + : NS_LITERAL_STRING_FROM_CSTRING( + STYLE_HASH_VIOLATION_OBSERVER_TOPIC); + } + + nsAutoString sourceFile; + uint32_t lineNumber; + uint32_t columnNumber; + + JSContext* cx = nsContentUtils::GetCurrentJSContext(); + if (!cx || !nsJSUtils::GetCallingLocation(cx, sourceFile, &lineNumber, + &columnNumber)) { + // use selfURI as the sourceFile + if (mSelfURI) { + nsAutoCString cSourceFile; + mSelfURI->GetSpec(cSourceFile); + sourceFile.Assign(NS_ConvertUTF8toUTF16(cSourceFile)); + } + lineNumber = aLineNumber; + columnNumber = aColumnNumber; + } + + AsyncReportViolation(aTriggeringElement, aCSPEventListener, + nullptr, // aBlockedURI + BlockedContentSource::eInline, // aBlockedSource + mSelfURI, // aOriginalURI + aViolatedDirective, // aViolatedDirective + aEffectiveDirective, // aEffectiveDirective + aViolatedPolicyIndex, // aViolatedPolicyIndex + observerSubject, // aObserverSubject + sourceFile, // aSourceFile + aReportSample, // aReportSample + aSample, // aScriptSample + lineNumber, // aLineNum + columnNumber); // aColumnNum +} + +NS_IMETHODIMP +nsCSPContext::GetAllowsInline(CSPDirective aDirective, bool aHasUnsafeHash, + const nsAString& aNonce, bool aParserCreated, + Element* aTriggeringElement, + nsICSPEventListener* aCSPEventListener, + const nsAString& aContentOfPseudoScript, + uint32_t aLineNumber, uint32_t aColumnNumber, + bool* outAllowsInline) { + *outAllowsInline = true; + + if (aDirective != SCRIPT_SRC_ELEM_DIRECTIVE && + aDirective != SCRIPT_SRC_ATTR_DIRECTIVE && + aDirective != STYLE_SRC_ELEM_DIRECTIVE && + aDirective != STYLE_SRC_ATTR_DIRECTIVE) { + MOZ_ASSERT(false, + "can only allow inline for (script/style)-src-(attr/elem)"); + return NS_OK; + } + + EnsureIPCPoliciesRead(); + nsAutoString content; + + // always iterate all policies, otherwise we might not send out all reports + for (uint32_t i = 0; i < mPolicies.Length(); i++) { + // https://w3c.github.io/webappsec-csp/#match-element-to-source-list + + // Step 1. If §6.7.3.2 Does a source list allow all inline behavior for + // type? returns "Allows" given list and type, return "Matches". + if (mPolicies[i]->allowsAllInlineBehavior(aDirective)) { + continue; + } + + // Step 2. If type is "script" or "style", and §6.7.3.1 Is element + // nonceable? returns "Nonceable" when executed upon element: + if ((aDirective == SCRIPT_SRC_ELEM_DIRECTIVE || + aDirective == STYLE_SRC_ELEM_DIRECTIVE) && + aTriggeringElement && !aNonce.IsEmpty()) { +#ifdef DEBUG + // NOTE: Folllowing Chrome "Is element nonceable?" doesn't apply to + //