diff options
Diffstat (limited to '')
-rw-r--r-- | dom/security/nsCSPService.cpp | 408 |
1 files changed, 408 insertions, 0 deletions
diff --git a/dom/security/nsCSPService.cpp b/dom/security/nsCSPService.cpp new file mode 100644 index 0000000000..c5c65ad4d2 --- /dev/null +++ b/dom/security/nsCSPService.cpp @@ -0,0 +1,408 @@ +/* -*- 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 "mozilla/Logging.h" +#include "mozilla/Preferences.h" +#include "mozilla/StaticPrefs_security.h" +#include "nsString.h" +#include "nsCOMPtr.h" +#include "nsIURI.h" +#include "nsIContent.h" +#include "nsCSPService.h" +#include "nsIContentSecurityPolicy.h" +#include "nsError.h" +#include "nsIAsyncVerifyRedirectCallback.h" +#include "nsAsyncRedirectVerifyHelper.h" +#include "nsContentUtils.h" +#include "nsContentPolicyUtils.h" +#include "nsNetUtil.h" +#include "nsIProtocolHandler.h" +#include "nsQueryObject.h" +#include "mozilla/net/DocumentLoadListener.h" +#include "mozilla/net/DocumentChannel.h" + +using namespace mozilla; + +static LazyLogModule gCspPRLog("CSP"); + +CSPService::CSPService() = default; + +CSPService::~CSPService() = default; + +NS_IMPL_ISUPPORTS(CSPService, nsIContentPolicy, nsIChannelEventSink) + +// Helper function to identify protocols and content types not subject to CSP. +bool subjectToCSP(nsIURI* aURI, nsContentPolicyType aContentType) { + ExtContentPolicyType contentType = + nsContentUtils::InternalContentPolicyTypeToExternal(aContentType); + + // These content types are not subject to CSP content policy checks: + // TYPE_CSP_REPORT -- csp can't block csp reports + // TYPE_DOCUMENT -- used for frame-ancestors + if (contentType == ExtContentPolicy::TYPE_CSP_REPORT || + contentType == ExtContentPolicy::TYPE_DOCUMENT) { + return false; + } + + // The three protocols: data:, blob: and filesystem: share the same + // protocol flag (URI_IS_LOCAL_RESOURCE) with other protocols, + // but those three protocols get special attention in CSP and + // are subject to CSP, hence we have to make sure those + // protocols are subject to CSP, see: + // http://www.w3.org/TR/CSP2/#source-list-guid-matching + if (aURI->SchemeIs("data") || aURI->SchemeIs("blob") || + aURI->SchemeIs("filesystem")) { + return true; + } + + // Finally we have to allowlist "about:" which does not fall into + // the category underneath and also "javascript:" which is not + // subject to CSP content loading rules. + if (aURI->SchemeIs("about") || aURI->SchemeIs("javascript")) { + return false; + } + + // Please note that it should be possible for websites to + // allowlist their own protocol handlers with respect to CSP, + // hence we use protocol flags to accomplish that, but we also + // want resource:, chrome: and moz-icon to be subject to CSP + // (which also use URI_IS_LOCAL_RESOURCE). + // Exception to the rule are images, styles, and localization + // DTDs using a scheme of resource: or chrome: + bool isImgOrStyleOrDTD = contentType == ExtContentPolicy::TYPE_IMAGE || + contentType == ExtContentPolicy::TYPE_STYLESHEET || + contentType == ExtContentPolicy::TYPE_DTD; + if (aURI->SchemeIs("resource")) { + nsAutoCString uriSpec; + aURI->GetSpec(uriSpec); + // Exempt pdf.js from being subject to a page's CSP. + if (StringBeginsWith(uriSpec, "resource://pdf.js/"_ns)) { + return false; + } + if (!isImgOrStyleOrDTD) { + return true; + } + } + if (aURI->SchemeIs("chrome") && !isImgOrStyleOrDTD) { + return true; + } + if (aURI->SchemeIs("moz-icon")) { + return true; + } + bool match; + nsresult rv = NS_URIChainHasFlags( + aURI, nsIProtocolHandler::URI_IS_LOCAL_RESOURCE, &match); + if (NS_SUCCEEDED(rv) && match) { + return false; + } + // all other protocols are subject To CSP. + return true; +} + +/* static */ nsresult CSPService::ConsultCSP(nsIURI* aContentLocation, + nsILoadInfo* aLoadInfo, + const nsACString& aMimeTypeGuess, + int16_t* aDecision) { + if (!aContentLocation) { + return NS_ERROR_FAILURE; + } + + nsContentPolicyType contentType = aLoadInfo->InternalContentPolicyType(); + bool parserCreatedScript = aLoadInfo->GetParserCreatedScript(); + + nsCOMPtr<nsICSPEventListener> cspEventListener; + nsresult rv = + aLoadInfo->GetCspEventListener(getter_AddRefs(cspEventListener)); + NS_ENSURE_SUCCESS(rv, rv); + + if (MOZ_LOG_TEST(gCspPRLog, LogLevel::Debug)) { + MOZ_LOG(gCspPRLog, LogLevel::Debug, + ("CSPService::ShouldLoad called for %s", + aContentLocation->GetSpecOrDefault().get())); + } + + // default decision, CSP can revise it if there's a policy to enforce + *aDecision = nsIContentPolicy::ACCEPT; + + // No need to continue processing if CSP is disabled or if the protocol + // or type is *not* subject to CSP. + // Please note, the correct way to opt-out of CSP using a custom + // protocolHandler is to set one of the nsIProtocolHandler flags + // that are allowlistet in subjectToCSP() + if (!subjectToCSP(aContentLocation, contentType)) { + return NS_OK; + } + + nsAutoString cspNonce; + rv = aLoadInfo->GetCspNonce(cspNonce); + NS_ENSURE_SUCCESS(rv, rv); + + // 1) Apply speculate CSP for preloads + bool isPreload = nsContentUtils::IsPreloadType(contentType); + + if (isPreload) { + nsCOMPtr<nsIContentSecurityPolicy> preloadCsp = aLoadInfo->GetPreloadCsp(); + if (preloadCsp) { + // obtain the enforcement decision + rv = preloadCsp->ShouldLoad( + contentType, cspEventListener, aContentLocation, + nullptr, // no redirect, aOriginal URL is null. + false, cspNonce, parserCreatedScript, aDecision); + NS_ENSURE_SUCCESS(rv, rv); + + // if the preload policy already denied the load, then there + // is no point in checking the real policy + if (NS_CP_REJECTED(*aDecision)) { + NS_SetRequestBlockingReason( + aLoadInfo, nsILoadInfo::BLOCKING_REASON_CONTENT_POLICY_PRELOAD); + + return NS_OK; + } + } + } + + // 2) Apply actual CSP to all loads. Please note that in case + // the csp should be overruled (e.g. by an ExpandedPrincipal) + // then loadinfo->GetCsp() returns that CSP instead of the + // document's CSP. + nsCOMPtr<nsIContentSecurityPolicy> csp = aLoadInfo->GetCsp(); + + if (csp) { + // Generally aOriginalURI denotes the URI before a redirect and hence + // will always be a nullptr here. Only exception are frame navigations + // which we want to treat as a redirect for the purpose of CSP reporting + // and in particular the `blocked-uri` in the CSP report where we want + // to report the prePath information. + nsCOMPtr<nsIURI> originalURI = nullptr; + ExtContentPolicyType extType = + nsContentUtils::InternalContentPolicyTypeToExternal(contentType); + if (extType == ExtContentPolicy::TYPE_SUBDOCUMENT && + !aLoadInfo->GetOriginalFrameSrcLoad() && + mozilla::StaticPrefs:: + security_csp_truncate_blocked_uri_for_frame_navigations()) { + nsAutoCString prePathStr; + nsresult rv = aContentLocation->GetPrePath(prePathStr); + NS_ENSURE_SUCCESS(rv, rv); + rv = NS_NewURI(getter_AddRefs(originalURI), prePathStr); + NS_ENSURE_SUCCESS(rv, rv); + } + + // obtain the enforcement decision + rv = csp->ShouldLoad( + contentType, cspEventListener, aContentLocation, + originalURI, // no redirect, unless it's a frame navigation. + !isPreload && aLoadInfo->GetSendCSPViolationEvents(), cspNonce, + parserCreatedScript, aDecision); + + if (NS_CP_REJECTED(*aDecision)) { + NS_SetRequestBlockingReason( + aLoadInfo, nsILoadInfo::BLOCKING_REASON_CONTENT_POLICY_GENERAL); + } + + NS_ENSURE_SUCCESS(rv, rv); + } + return NS_OK; +} + +/* nsIContentPolicy implementation */ +NS_IMETHODIMP +CSPService::ShouldLoad(nsIURI* aContentLocation, nsILoadInfo* aLoadInfo, + const nsACString& aMimeTypeGuess, int16_t* aDecision) { + return ConsultCSP(aContentLocation, aLoadInfo, aMimeTypeGuess, aDecision); +} + +NS_IMETHODIMP +CSPService::ShouldProcess(nsIURI* aContentLocation, nsILoadInfo* aLoadInfo, + const nsACString& aMimeTypeGuess, + int16_t* aDecision) { + if (!aContentLocation) { + return NS_ERROR_FAILURE; + } + nsContentPolicyType contentType = aLoadInfo->InternalContentPolicyType(); + + if (MOZ_LOG_TEST(gCspPRLog, LogLevel::Debug)) { + MOZ_LOG(gCspPRLog, LogLevel::Debug, + ("CSPService::ShouldProcess called for %s", + aContentLocation->GetSpecOrDefault().get())); + } + + // ShouldProcess is only relevant to TYPE_OBJECT, so let's convert the + // internal contentPolicyType to the mapping external one. + // If it is not TYPE_OBJECT, we can return at this point. + // Note that we should still pass the internal contentPolicyType + // (contentType) to ShouldLoad(). + ExtContentPolicyType policyType = + nsContentUtils::InternalContentPolicyTypeToExternal(contentType); + + if (policyType != ExtContentPolicy::TYPE_OBJECT) { + *aDecision = nsIContentPolicy::ACCEPT; + return NS_OK; + } + + return ShouldLoad(aContentLocation, aLoadInfo, aMimeTypeGuess, aDecision); +} + +/* nsIChannelEventSink implementation */ +NS_IMETHODIMP +CSPService::AsyncOnChannelRedirect(nsIChannel* oldChannel, + nsIChannel* newChannel, uint32_t flags, + nsIAsyncVerifyRedirectCallback* callback) { + net::nsAsyncRedirectAutoCallback autoCallback(callback); + + if (XRE_IsE10sParentProcess()) { + nsCOMPtr<nsIParentChannel> parentChannel; + NS_QueryNotificationCallbacks(oldChannel, parentChannel); + RefPtr<net::DocumentLoadListener> docListener = + do_QueryObject(parentChannel); + // Since this is an IPC'd channel we do not have access to the request + // context. In turn, we do not have an event target for policy violations. + // Enforce the CSP check in the content process where we have that info. + // We allow redirect checks to run for document loads via + // DocumentLoadListener, since these are fully supported and we don't + // expose the redirects to the content process. We can't do this for all + // request types yet because we don't serialize nsICSPEventListener. + if (parentChannel && !docListener) { + return NS_OK; + } + } + + // Don't do these checks if we're switching from DocumentChannel + // to a real channel. In that case, we should already have done + // the checks in the parent process. AsyncOnChannelRedirect + // isn't called in the content process if we switch process, + // so checking here would just hide bugs in the process switch + // cases. + if (RefPtr<net::DocumentChannel> docChannel = do_QueryObject(oldChannel)) { + return NS_OK; + } + + nsCOMPtr<nsIURI> newUri; + nsresult rv = newChannel->GetURI(getter_AddRefs(newUri)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsILoadInfo> loadInfo = oldChannel->LoadInfo(); + + /* Since redirecting channels don't call into nsIContentPolicy, we call our + * Content Policy implementation directly when redirects occur using the + * information set in the LoadInfo when channels are created. + * + * We check if the CSP permits this host for this type of load, if not, + * we cancel the load now. + */ + nsCOMPtr<nsIURI> originalUri; + rv = oldChannel->GetOriginalURI(getter_AddRefs(originalUri)); + if (NS_FAILED(rv)) { + autoCallback.DontCallback(); + oldChannel->Cancel(NS_ERROR_DOM_BAD_URI); + return rv; + } + + Maybe<nsresult> cancelCode; + rv = ConsultCSPForRedirect(originalUri, newUri, loadInfo, cancelCode); + if (cancelCode) { + oldChannel->Cancel(*cancelCode); + } + if (NS_FAILED(rv)) { + autoCallback.DontCallback(); + } + + return rv; +} + +nsresult CSPService::ConsultCSPForRedirect(nsIURI* aOriginalURI, + nsIURI* aNewURI, + nsILoadInfo* aLoadInfo, + Maybe<nsresult>& aCancelCode) { + // Check CSP navigate-to + // We need to enforce the CSP of the document that initiated the load, + // which is the CSP to inherit. + nsCOMPtr<nsIContentSecurityPolicy> cspToInherit = + aLoadInfo->GetCspToInherit(); + if (cspToInherit) { + bool allowsNavigateTo = false; + nsresult rv = cspToInherit->GetAllowsNavigateTo( + aNewURI, aLoadInfo->GetIsFormSubmission(), true, /* aWasRedirected */ + false, /* aEnforceAllowlist */ + &allowsNavigateTo); + NS_ENSURE_SUCCESS(rv, rv); + + if (!allowsNavigateTo) { + aCancelCode = Some(NS_ERROR_CSP_NAVIGATE_TO_VIOLATION); + return NS_OK; + } + } + + // No need to continue processing if CSP is disabled or if the protocol + // is *not* subject to CSP. + // Please note, the correct way to opt-out of CSP using a custom + // protocolHandler is to set one of the nsIProtocolHandler flags + // that are allowlistet in subjectToCSP() + nsContentPolicyType policyType = aLoadInfo->InternalContentPolicyType(); + if (!subjectToCSP(aNewURI, policyType)) { + return NS_OK; + } + + nsCOMPtr<nsICSPEventListener> cspEventListener; + nsresult rv = + aLoadInfo->GetCspEventListener(getter_AddRefs(cspEventListener)); + MOZ_ALWAYS_SUCCEEDS(rv); + + nsAutoString cspNonce; + rv = aLoadInfo->GetCspNonce(cspNonce); + MOZ_ALWAYS_SUCCEEDS(rv); + + bool isPreload = nsContentUtils::IsPreloadType(policyType); + + /* On redirect, if the content policy is a preload type, rejecting the + * preload results in the load silently failing, so we pass true to + * the aSendViolationReports parameter. See Bug 1219453. + */ + + int16_t decision = nsIContentPolicy::ACCEPT; + bool parserCreatedScript = aLoadInfo->GetParserCreatedScript(); + + // 1) Apply speculative CSP for preloads + if (isPreload) { + nsCOMPtr<nsIContentSecurityPolicy> preloadCsp = aLoadInfo->GetPreloadCsp(); + if (preloadCsp) { + // Pass originalURI to indicate the redirect + preloadCsp->ShouldLoad( + policyType, // load type per nsIContentPolicy (uint32_t) + cspEventListener, + aNewURI, // nsIURI + aOriginalURI, // Original nsIURI + true, // aSendViolationReports + cspNonce, // nonce + parserCreatedScript, &decision); + + // if the preload policy already denied the load, then there + // is no point in checking the real policy + if (NS_CP_REJECTED(decision)) { + aCancelCode = Some(NS_ERROR_DOM_BAD_URI); + return NS_BINDING_FAILED; + } + } + } + + // 2) Apply actual CSP to all loads + nsCOMPtr<nsIContentSecurityPolicy> csp = aLoadInfo->GetCsp(); + if (csp) { + // Pass originalURI to indicate the redirect + csp->ShouldLoad(policyType, // load type per nsIContentPolicy (uint32_t) + cspEventListener, + aNewURI, // nsIURI + aOriginalURI, // Original nsIURI + true, // aSendViolationReports + cspNonce, // nonce + parserCreatedScript, &decision); + if (NS_CP_REJECTED(decision)) { + aCancelCode = Some(NS_ERROR_DOM_BAD_URI); + return NS_BINDING_FAILED; + } + } + + return NS_OK; +} |