/* -*- 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 #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/Variant.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; nsAutoString violatedDirectiveString; for (uint32_t p = 0; p < mPolicies.Length(); p++) { if (!mPolicies[p]->permits(aDir, aLoadInfo, aContentLocation, !!aOriginalURIIfRedirect, aSpecific, violatedDirective, violatedDirectiveString)) { // 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; } // 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, violatedDirectiveString, aDir, // aViolatedDirective 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; if (mSelfURI) { mSelfURI->GetAsciiSpec(selfURIspec); } CSPCONTEXTLOG( ("nsCSPContext::AppendPolicy added UPGRADE_IF_INSECURE_DIRECTIVE " "self-uri=%s referrer=%s", selfURIspec.get(), mReferrer.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& aViolatedDirectiveString, CSPDirective 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 aViolatedDirectiveString, 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 //