/* -*- 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 "nsString.h" #include "nsCOMPtr.h" #include "nsIURI.h" #include "nsIPrincipal.h" #include "nsIObserver.h" #include "nsIContent.h" #include "nsCSPService.h" #include "nsIContentSecurityPolicy.h" #include "nsError.h" #include "nsIAsyncVerifyRedirectCallback.h" #include "nsAsyncRedirectVerifyHelper.h" #include "mozilla/Preferences.h" #include "nsIScriptError.h" #include "nsContentUtils.h" #include "nsContentPolicyUtils.h" using namespace mozilla; /* Keeps track of whether or not CSP is enabled */ bool CSPService::sCSPEnabled = true; static LazyLogModule gCspPRLog("CSP"); CSPService::CSPService() { Preferences::AddBoolVarCache(&sCSPEnabled, "security.csp.enable"); } CSPService::~CSPService() { mAppStatusCache.Clear(); } NS_IMPL_ISUPPORTS(CSPService, nsIContentPolicy, nsIChannelEventSink) // Helper function to identify protocols and content types not subject to CSP. bool subjectToCSP(nsIURI* aURI, nsContentPolicyType aContentType) { nsContentPolicyType 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_REFRESH -- never passed to ShouldLoad (see nsIContentPolicy.idl) // TYPE_DOCUMENT -- used for frame-ancestors if (contentType == nsIContentPolicy::TYPE_CSP_REPORT || contentType == nsIContentPolicy::TYPE_REFRESH || contentType == nsIContentPolicy::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 bool match = false; nsresult rv = aURI->SchemeIs("data", &match); if (NS_SUCCEEDED(rv) && match) { return true; } rv = aURI->SchemeIs("blob", &match); if (NS_SUCCEEDED(rv) && match) { return true; } rv = aURI->SchemeIs("filesystem", &match); if (NS_SUCCEEDED(rv) && match) { return true; } // Finally we have to whitelist "about:" which does not fall into // the category underneath and also "javascript:" which is not // subject to CSP content loading rules. rv = aURI->SchemeIs("about", &match); if (NS_SUCCEEDED(rv) && match) { return false; } rv = aURI->SchemeIs("javascript", &match); if (NS_SUCCEEDED(rv) && match) { return false; } // Please note that it should be possible for websites to // whitelist 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 and styles using a scheme // of resource: or chrome: bool isImgOrStyle = contentType == nsIContentPolicy::TYPE_IMAGE || contentType == nsIContentPolicy::TYPE_STYLESHEET; rv = aURI->SchemeIs("resource", &match); if (NS_SUCCEEDED(rv) && match && !isImgOrStyle) { return true; } rv = aURI->SchemeIs("chrome", &match); if (NS_SUCCEEDED(rv) && match && !isImgOrStyle) { return true; } rv = aURI->SchemeIs("moz-icon", &match); if (NS_SUCCEEDED(rv) && match) { return true; } 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; } /* nsIContentPolicy implementation */ NS_IMETHODIMP CSPService::ShouldLoad(nsIURI *aContentLocation, nsILoadInfo* aLoadInfo, const nsACString &aMimeTypeGuess, int16_t *aDecision) { if (!aContentLocation) { return NS_ERROR_FAILURE; } uint32_t contentType = aLoadInfo->InternalContentPolicyType(); nsCOMPtr requestContext = aLoadInfo->GetLoadingContext(); nsCOMPtr requestPrincipal = aLoadInfo->TriggeringPrincipal(); nsCOMPtr requestOrigin; nsCOMPtr loadingPrincipal = aLoadInfo->LoadingPrincipal(); if (loadingPrincipal) { loadingPrincipal->GetURI(getter_AddRefs(requestOrigin)); } 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 whitelistet in subjectToCSP() if (!sCSPEnabled || !subjectToCSP(aContentLocation, contentType)) { return NS_OK; } // Find a principal to retrieve the CSP from. If we don't have a context node // (because, for instance, the load originates in a service worker), or the // requesting principal's CSP overrides our document CSP, use the request // principal. Otherwise, use the document principal. nsCOMPtr node(do_QueryInterface(requestContext)); nsCOMPtr principal; if (!node || (requestPrincipal && BasePrincipal::Cast(requestPrincipal)->OverridesCSP(node->NodePrincipal()))) { principal = requestPrincipal; } else { principal = node->NodePrincipal(); } if (!principal) { // if we can't query a principal, then there is nothing to do. return NS_OK; } nsresult rv = NS_OK; // 1) Apply speculate CSP for preloads bool isPreload = nsContentUtils::IsPreloadType(contentType); if (isPreload) { nsCOMPtr preloadCsp; rv = principal->GetPreloadCsp(getter_AddRefs(preloadCsp)); NS_ENSURE_SUCCESS(rv, rv); if (preloadCsp) { // obtain the enforcement decision // (don't pass aExtra, we use that slot for redirects) rv = preloadCsp->ShouldLoad(contentType, aContentLocation, requestOrigin, requestContext, aMimeTypeGuess, nullptr, // aExtra 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)) { return NS_OK; } } } // 2) Apply actual CSP to all loads nsCOMPtr csp; rv = principal->GetCsp(getter_AddRefs(csp)); NS_ENSURE_SUCCESS(rv, rv); if (csp) { // obtain the enforcement decision // (don't pass aExtra, we use that slot for redirects) rv = csp->ShouldLoad(contentType, aContentLocation, requestOrigin, requestContext, aMimeTypeGuess, nullptr, aDecision); NS_ENSURE_SUCCESS(rv, rv); } return NS_OK; } NS_IMETHODIMP CSPService::ShouldProcess(nsIURI *aContentLocation, nsILoadInfo* aLoadInfo, const nsACString &aMimeTypeGuess, int16_t *aDecision) { if (!aContentLocation) { return NS_ERROR_FAILURE; } uint32_t 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(). uint32_t policyType = nsContentUtils::InternalContentPolicyTypeToExternal(contentType); if (policyType != nsIContentPolicy::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); nsCOMPtr newUri; nsresult rv = newChannel->GetURI(getter_AddRefs(newUri)); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr loadInfo = oldChannel->GetLoadInfo(); // if no loadInfo on the channel, nothing for us to do if (!loadInfo) { 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 whitelistet in subjectToCSP() nsContentPolicyType policyType = loadInfo->InternalContentPolicyType(); if (!sCSPEnabled || !subjectToCSP(newUri, policyType)) { return NS_OK; } /* 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 originalUri; rv = oldChannel->GetOriginalURI(getter_AddRefs(originalUri)); if (NS_FAILED(rv)) { autoCallback.DontCallback(); oldChannel->Cancel(NS_ERROR_DOM_BAD_URI); return 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 convert preloads to the actual * type. See Bug 1219453. */ policyType = nsContentUtils::InternalContentPolicyTypeToExternalOrWorker(policyType); int16_t aDecision = nsIContentPolicy::ACCEPT; nsCOMPtr requestContext = loadInfo->GetLoadingContext(); // 1) Apply speculative CSP for preloads if (isPreload) { nsCOMPtr preloadCsp; loadInfo->LoadingPrincipal()->GetPreloadCsp(getter_AddRefs(preloadCsp)); if (preloadCsp) { // Pass originalURI as aExtra to indicate the redirect preloadCsp->ShouldLoad(policyType, // load type per nsIContentPolicy (uint32_t) newUri, // nsIURI nullptr, // nsIURI requestContext, // nsISupports EmptyCString(), // ACString - MIME guess originalUri, // aExtra &aDecision); // if the preload policy already denied the load, then there // is no point in checking the real policy if (NS_CP_REJECTED(aDecision)) { autoCallback.DontCallback(); oldChannel->Cancel(NS_ERROR_DOM_BAD_URI); return NS_BINDING_FAILED; } } } // 2) Apply actual CSP to all loads nsCOMPtr csp; loadInfo->LoadingPrincipal()->GetCsp(getter_AddRefs(csp)); if (csp) { // Pass originalURI as aExtra to indicate the redirect csp->ShouldLoad(policyType, // load type per nsIContentPolicy (uint32_t) newUri, // nsIURI nullptr, // nsIURI requestContext, // nsISupports EmptyCString(), // ACString - MIME guess originalUri, // aExtra &aDecision); } // if ShouldLoad doesn't accept the load, cancel the request if (!NS_CP_ACCEPTED(aDecision)) { autoCallback.DontCallback(); oldChannel->Cancel(NS_ERROR_DOM_BAD_URI); return NS_BINDING_FAILED; } return NS_OK; }