michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "prlog.h" michael@0: #include "nsString.h" michael@0: #include "nsCOMPtr.h" michael@0: #include "nsIURI.h" michael@0: #include "nsIPrincipal.h" michael@0: #include "nsIObserver.h" michael@0: #include "nsIContent.h" michael@0: #include "nsCSPService.h" michael@0: #include "nsIContentSecurityPolicy.h" michael@0: #include "nsIChannelPolicy.h" michael@0: #include "nsIChannelEventSink.h" michael@0: #include "nsIPropertyBag2.h" michael@0: #include "nsIWritablePropertyBag2.h" michael@0: #include "nsError.h" michael@0: #include "nsChannelProperties.h" michael@0: #include "nsIAsyncVerifyRedirectCallback.h" michael@0: #include "nsAsyncRedirectVerifyHelper.h" michael@0: #include "mozilla/Preferences.h" michael@0: #include "nsIScriptError.h" michael@0: #include "nsContentUtils.h" michael@0: michael@0: using namespace mozilla; michael@0: michael@0: /* Keeps track of whether or not CSP is enabled */ michael@0: bool CSPService::sCSPEnabled = true; michael@0: michael@0: #ifdef PR_LOGGING michael@0: static PRLogModuleInfo* gCspPRLog; michael@0: #endif michael@0: michael@0: CSPService::CSPService() michael@0: { michael@0: Preferences::AddBoolVarCache(&sCSPEnabled, "security.csp.enable"); michael@0: michael@0: #ifdef PR_LOGGING michael@0: if (!gCspPRLog) michael@0: gCspPRLog = PR_NewLogModule("CSP"); michael@0: #endif michael@0: } michael@0: michael@0: CSPService::~CSPService() michael@0: { michael@0: mAppStatusCache.Clear(); michael@0: } michael@0: michael@0: NS_IMPL_ISUPPORTS(CSPService, nsIContentPolicy, nsIChannelEventSink) michael@0: michael@0: /* nsIContentPolicy implementation */ michael@0: NS_IMETHODIMP michael@0: CSPService::ShouldLoad(uint32_t aContentType, michael@0: nsIURI *aContentLocation, michael@0: nsIURI *aRequestOrigin, michael@0: nsISupports *aRequestContext, michael@0: const nsACString &aMimeTypeGuess, michael@0: nsISupports *aExtra, michael@0: nsIPrincipal *aRequestPrincipal, michael@0: int16_t *aDecision) michael@0: { michael@0: if (!aContentLocation) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: #ifdef PR_LOGGING michael@0: { michael@0: nsAutoCString location; michael@0: aContentLocation->GetSpec(location); michael@0: PR_LOG(gCspPRLog, PR_LOG_DEBUG, michael@0: ("CSPService::ShouldLoad called for %s", location.get())); michael@0: } michael@0: #endif michael@0: michael@0: // default decision, CSP can revise it if there's a policy to enforce michael@0: *aDecision = nsIContentPolicy::ACCEPT; michael@0: michael@0: // No need to continue processing if CSP is disabled michael@0: if (!sCSPEnabled) michael@0: return NS_OK; michael@0: michael@0: // shortcut for about: chrome: and resource: and javascript: uris since michael@0: // they're not subject to CSP content policy checks. michael@0: bool schemeMatch = false; michael@0: NS_ENSURE_SUCCESS(aContentLocation->SchemeIs("about", &schemeMatch), NS_OK); michael@0: if (schemeMatch) michael@0: return NS_OK; michael@0: NS_ENSURE_SUCCESS(aContentLocation->SchemeIs("chrome", &schemeMatch), NS_OK); michael@0: if (schemeMatch) michael@0: return NS_OK; michael@0: NS_ENSURE_SUCCESS(aContentLocation->SchemeIs("resource", &schemeMatch), NS_OK); michael@0: if (schemeMatch) michael@0: return NS_OK; michael@0: NS_ENSURE_SUCCESS(aContentLocation->SchemeIs("javascript", &schemeMatch), NS_OK); michael@0: if (schemeMatch) michael@0: return NS_OK; michael@0: michael@0: michael@0: // These content types are not subject to CSP content policy checks: michael@0: // TYPE_CSP_REPORT, TYPE_REFRESH, TYPE_DOCUMENT michael@0: // (their mappings are null in contentSecurityPolicy.js) michael@0: if (aContentType == nsIContentPolicy::TYPE_CSP_REPORT || michael@0: aContentType == nsIContentPolicy::TYPE_REFRESH || michael@0: aContentType == nsIContentPolicy::TYPE_DOCUMENT) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: // ----- THIS IS A TEMPORARY FAST PATH FOR CERTIFIED APPS. ----- michael@0: // ----- PLEASE REMOVE ONCE bug 925004 LANDS. ----- michael@0: michael@0: // Cache the app status for this origin. michael@0: uint16_t status = nsIPrincipal::APP_STATUS_NOT_INSTALLED; michael@0: nsAutoCString contentOrigin; michael@0: aContentLocation->GetPrePath(contentOrigin); michael@0: if (aRequestPrincipal && !mAppStatusCache.Get(contentOrigin, &status)) { michael@0: aRequestPrincipal->GetAppStatus(&status); michael@0: mAppStatusCache.Put(contentOrigin, status); michael@0: } michael@0: michael@0: if (status == nsIPrincipal::APP_STATUS_CERTIFIED) { michael@0: // The CSP for certified apps is : michael@0: // "default-src *; script-src 'self'; object-src 'none'; style-src 'self'" michael@0: // That means we can optimize for this case by: michael@0: // - loading only same origin scripts and stylesheets. michael@0: // - never loading objects. michael@0: // - accepting everything else. michael@0: michael@0: switch (aContentType) { michael@0: case nsIContentPolicy::TYPE_SCRIPT: michael@0: case nsIContentPolicy::TYPE_STYLESHEET: michael@0: { michael@0: nsAutoCString sourceOrigin; michael@0: aRequestOrigin->GetPrePath(sourceOrigin); michael@0: if (!sourceOrigin.Equals(contentOrigin)) { michael@0: *aDecision = nsIContentPolicy::REJECT_SERVER; michael@0: } michael@0: } michael@0: break; michael@0: michael@0: case nsIContentPolicy::TYPE_OBJECT: michael@0: *aDecision = nsIContentPolicy::REJECT_SERVER; michael@0: break; michael@0: michael@0: default: michael@0: *aDecision = nsIContentPolicy::ACCEPT; michael@0: } michael@0: michael@0: // Only cache and return if we are successful. If not, we want the error michael@0: // to be reported, and thus fallback to the slow path. michael@0: if (*aDecision == nsIContentPolicy::ACCEPT) { michael@0: return NS_OK; michael@0: } michael@0: } michael@0: michael@0: // ----- END OF TEMPORARY FAST PATH FOR CERTIFIED APPS. ----- michael@0: michael@0: // find the principal of the document that initiated this request and see michael@0: // if it has a CSP policy object michael@0: nsCOMPtr node(do_QueryInterface(aRequestContext)); michael@0: nsCOMPtr principal; michael@0: nsCOMPtr csp; michael@0: if (node) { michael@0: principal = node->NodePrincipal(); michael@0: principal->GetCsp(getter_AddRefs(csp)); michael@0: michael@0: if (csp) { michael@0: #ifdef PR_LOGGING michael@0: { michael@0: int numPolicies = 0; michael@0: nsresult rv = csp->GetPolicyCount(&numPolicies); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: for (int i=0; iGetPolicy(i, policy); michael@0: PR_LOG(gCspPRLog, PR_LOG_DEBUG, michael@0: ("Document has CSP[%d]: %s", i, michael@0: NS_ConvertUTF16toUTF8(policy).get())); michael@0: } michael@0: } michael@0: } michael@0: #endif michael@0: // obtain the enforcement decision michael@0: // (don't pass aExtra, we use that slot for redirects) michael@0: csp->ShouldLoad(aContentType, michael@0: aContentLocation, michael@0: aRequestOrigin, michael@0: aRequestContext, michael@0: aMimeTypeGuess, michael@0: nullptr, michael@0: aDecision); michael@0: } michael@0: } michael@0: #ifdef PR_LOGGING michael@0: else { michael@0: nsAutoCString uriSpec; michael@0: aContentLocation->GetSpec(uriSpec); michael@0: PR_LOG(gCspPRLog, PR_LOG_DEBUG, michael@0: ("COULD NOT get nsINode for location: %s", uriSpec.get())); michael@0: } michael@0: #endif michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: CSPService::ShouldProcess(uint32_t aContentType, michael@0: nsIURI *aContentLocation, michael@0: nsIURI *aRequestOrigin, michael@0: nsISupports *aRequestContext, michael@0: const nsACString &aMimeTypeGuess, michael@0: nsISupports *aExtra, michael@0: nsIPrincipal *aRequestPrincipal, michael@0: int16_t *aDecision) michael@0: { michael@0: if (!aContentLocation) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: // default decision is to accept the item michael@0: *aDecision = nsIContentPolicy::ACCEPT; michael@0: michael@0: // No need to continue processing if CSP is disabled michael@0: if (!sCSPEnabled) michael@0: return NS_OK; michael@0: michael@0: // find the nsDocument that initiated this request and see if it has a michael@0: // CSP policy object michael@0: nsCOMPtr node(do_QueryInterface(aRequestContext)); michael@0: nsCOMPtr principal; michael@0: nsCOMPtr csp; michael@0: if (node) { michael@0: principal = node->NodePrincipal(); michael@0: principal->GetCsp(getter_AddRefs(csp)); michael@0: michael@0: if (csp) { michael@0: #ifdef PR_LOGGING michael@0: { michael@0: int numPolicies = 0; michael@0: nsresult rv = csp->GetPolicyCount(&numPolicies); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: for (int i=0; iGetPolicy(i, policy); michael@0: PR_LOG(gCspPRLog, PR_LOG_DEBUG, michael@0: ("shouldProcess - document has policy[%d]: %s", i, michael@0: NS_ConvertUTF16toUTF8(policy).get())); michael@0: } michael@0: } michael@0: } michael@0: #endif michael@0: // obtain the enforcement decision michael@0: csp->ShouldProcess(aContentType, michael@0: aContentLocation, michael@0: aRequestOrigin, michael@0: aRequestContext, michael@0: aMimeTypeGuess, michael@0: aExtra, michael@0: aDecision); michael@0: } michael@0: } michael@0: #ifdef PR_LOGGING michael@0: else { michael@0: nsAutoCString uriSpec; michael@0: aContentLocation->GetSpec(uriSpec); michael@0: PR_LOG(gCspPRLog, PR_LOG_DEBUG, michael@0: ("COULD NOT get nsINode for location: %s", uriSpec.get())); michael@0: } michael@0: #endif michael@0: return NS_OK; michael@0: } michael@0: michael@0: /* nsIChannelEventSink implementation */ michael@0: NS_IMETHODIMP michael@0: CSPService::AsyncOnChannelRedirect(nsIChannel *oldChannel, michael@0: nsIChannel *newChannel, michael@0: uint32_t flags, michael@0: nsIAsyncVerifyRedirectCallback *callback) michael@0: { michael@0: nsAsyncRedirectAutoCallback autoCallback(callback); michael@0: michael@0: // get the Content Security Policy and load type from the property bag michael@0: nsCOMPtr policyContainer; michael@0: nsCOMPtr props(do_QueryInterface(oldChannel)); michael@0: if (!props) michael@0: return NS_OK; michael@0: michael@0: props->GetPropertyAsInterface(NS_CHANNEL_PROP_CHANNEL_POLICY, michael@0: NS_GET_IID(nsISupports), michael@0: getter_AddRefs(policyContainer)); michael@0: michael@0: // see if we have a valid nsIChannelPolicy containing CSP and load type michael@0: nsCOMPtr channelPolicy(do_QueryInterface(policyContainer)); michael@0: if (!channelPolicy) michael@0: return NS_OK; michael@0: michael@0: nsCOMPtr supports; michael@0: nsCOMPtr csp; michael@0: channelPolicy->GetContentSecurityPolicy(getter_AddRefs(supports)); michael@0: csp = do_QueryInterface(supports); michael@0: uint32_t loadType; michael@0: channelPolicy->GetLoadType(&loadType); michael@0: michael@0: // if no CSP in the channelPolicy, nothing for us to add to the channel michael@0: if (!csp) michael@0: return NS_OK; michael@0: michael@0: /* Since redirecting channels don't call into nsIContentPolicy, we call our michael@0: * Content Policy implementation directly when redirects occur. When channels michael@0: * are created using NS_NewChannel(), callers can optionally pass in a michael@0: * nsIChannelPolicy containing a CSP object and load type, which is placed in michael@0: * the new channel's property bag. This container is propagated forward when michael@0: * channels redirect. michael@0: */ michael@0: michael@0: // Does the CSP permit this host for this type of load? michael@0: // If not, cancel the load now. michael@0: nsCOMPtr newUri; michael@0: newChannel->GetURI(getter_AddRefs(newUri)); michael@0: nsCOMPtr originalUri; michael@0: oldChannel->GetOriginalURI(getter_AddRefs(originalUri)); michael@0: int16_t aDecision = nsIContentPolicy::ACCEPT; michael@0: csp->ShouldLoad(loadType, // load type per nsIContentPolicy (uint32_t) michael@0: newUri, // nsIURI michael@0: nullptr, // nsIURI michael@0: nullptr, // nsISupports michael@0: EmptyCString(), // ACString - MIME guess michael@0: originalUri, // nsISupports - extra michael@0: &aDecision); michael@0: michael@0: #ifdef PR_LOGGING michael@0: if (newUri) { michael@0: nsAutoCString newUriSpec("None"); michael@0: newUri->GetSpec(newUriSpec); michael@0: PR_LOG(gCspPRLog, PR_LOG_DEBUG, michael@0: ("CSPService::AsyncOnChannelRedirect called for %s", michael@0: newUriSpec.get())); michael@0: } michael@0: if (aDecision == 1) michael@0: PR_LOG(gCspPRLog, PR_LOG_DEBUG, michael@0: ("CSPService::AsyncOnChannelRedirect ALLOWING request.")); michael@0: else michael@0: PR_LOG(gCspPRLog, PR_LOG_DEBUG, michael@0: ("CSPService::AsyncOnChannelRedirect CANCELLING request.")); michael@0: #endif michael@0: michael@0: // if ShouldLoad doesn't accept the load, cancel the request michael@0: if (aDecision != 1) { michael@0: autoCallback.DontCallback(); michael@0: return NS_BINDING_FAILED; michael@0: } michael@0: michael@0: // the redirect is permitted, so propagate the Content Security Policy michael@0: // and load type to the redirecting channel michael@0: nsresult rv; michael@0: nsCOMPtr props2 = do_QueryInterface(newChannel); michael@0: if (props2) { michael@0: rv = props2->SetPropertyAsInterface(NS_CHANNEL_PROP_CHANNEL_POLICY, michael@0: channelPolicy); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: return NS_OK; michael@0: } michael@0: } michael@0: michael@0: // The redirecting channel isn't a writable property bag, we won't be able michael@0: // to enforce the load policy if it redirects again, so we stop it now. michael@0: nsAutoCString newUriSpec; michael@0: rv = newUri->GetSpec(newUriSpec); michael@0: const char16_t *formatParams[] = { NS_ConvertUTF8toUTF16(newUriSpec).get() }; michael@0: if (NS_SUCCEEDED(rv)) { michael@0: nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, michael@0: NS_LITERAL_CSTRING("Redirect Error"), nullptr, michael@0: nsContentUtils::eDOM_PROPERTIES, michael@0: "InvalidRedirectChannelWarning", michael@0: formatParams, 1); michael@0: } michael@0: michael@0: return NS_BINDING_FAILED; michael@0: }