/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* 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 "prlog.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" #include "nsPrincipal.h" using namespace mozilla; /* Keeps track of whether or not CSP is enabled */ bool CSPService::sCSPEnabled = true; #ifdef PR_LOGGING static PRLogModuleInfo* gCspPRLog; #endif CSPService::CSPService() { Preferences::AddBoolVarCache(&sCSPEnabled, "security.csp.enable"); #ifdef PR_LOGGING if (!gCspPRLog) gCspPRLog = PR_NewLogModule("CSP"); #endif } CSPService::~CSPService() { mAppStatusCache.Clear(); } NS_IMPL_ISUPPORTS(CSPService, nsIContentPolicy, nsIChannelEventSink) /* nsIContentPolicy implementation */ NS_IMETHODIMP CSPService::ShouldLoad(uint32_t aContentType, nsIURI *aContentLocation, nsIURI *aRequestOrigin, nsISupports *aRequestContext, const nsACString &aMimeTypeGuess, nsISupports *aExtra, nsIPrincipal *aRequestPrincipal, int16_t *aDecision) { if (!aContentLocation) return NS_ERROR_FAILURE; #ifdef PR_LOGGING { nsAutoCString location; aContentLocation->GetSpec(location); PR_LOG(gCspPRLog, PR_LOG_DEBUG, ("CSPService::ShouldLoad called for %s", location.get())); } #endif // 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 if (!sCSPEnabled) return NS_OK; // shortcut for about: chrome: and resource: and javascript: uris since // they're not subject to CSP content policy checks. bool schemeMatch = false; NS_ENSURE_SUCCESS(aContentLocation->SchemeIs("about", &schemeMatch), NS_OK); if (schemeMatch) return NS_OK; NS_ENSURE_SUCCESS(aContentLocation->SchemeIs("chrome", &schemeMatch), NS_OK); if (schemeMatch) return NS_OK; NS_ENSURE_SUCCESS(aContentLocation->SchemeIs("resource", &schemeMatch), NS_OK); if (schemeMatch) return NS_OK; NS_ENSURE_SUCCESS(aContentLocation->SchemeIs("javascript", &schemeMatch), NS_OK); if (schemeMatch) return NS_OK; // 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 (aContentType == nsIContentPolicy::TYPE_CSP_REPORT || aContentType == nsIContentPolicy::TYPE_REFRESH || aContentType == nsIContentPolicy::TYPE_DOCUMENT) { return NS_OK; } // ----- THIS IS A TEMPORARY FAST PATH FOR CERTIFIED APPS. ----- // ----- PLEASE REMOVE ONCE bug 925004 LANDS. ----- // Cache the app status for this origin. uint16_t status = nsIPrincipal::APP_STATUS_NOT_INSTALLED; nsAutoCString contentOrigin; aContentLocation->GetPrePath(contentOrigin); if (aRequestPrincipal && !mAppStatusCache.Get(contentOrigin, &status)) { aRequestPrincipal->GetAppStatus(&status); mAppStatusCache.Put(contentOrigin, status); } if (status == nsIPrincipal::APP_STATUS_CERTIFIED) { // The CSP for certified apps is : // "default-src *; script-src 'self'; object-src 'none'; style-src 'self' app://theme.gaiamobile.org:*" // That means we can optimize for this case by: // - loading same origin scripts and stylesheets, and stylesheets from the // theme url space. // - never loading objects. // - accepting everything else. switch (aContentType) { case nsIContentPolicy::TYPE_SCRIPT: case nsIContentPolicy::TYPE_STYLESHEET: { // Whitelist the theme resources. auto themeOrigin = Preferences::GetCString("b2g.theme.origin"); nsAutoCString sourceOrigin; aRequestOrigin->GetPrePath(sourceOrigin); if (!(sourceOrigin.Equals(contentOrigin) || (themeOrigin && themeOrigin.Equals(contentOrigin)))) { *aDecision = nsIContentPolicy::REJECT_SERVER; } } break; case nsIContentPolicy::TYPE_OBJECT: *aDecision = nsIContentPolicy::REJECT_SERVER; break; default: *aDecision = nsIContentPolicy::ACCEPT; } // Only cache and return if we are successful. If not, we want the error // to be reported, and thus fallback to the slow path. if (*aDecision == nsIContentPolicy::ACCEPT) { return NS_OK; } } // ----- END OF TEMPORARY FAST PATH FOR CERTIFIED APPS. ----- // find the principal of the document that initiated this request and see // if it has a CSP policy object nsCOMPtr node(do_QueryInterface(aRequestContext)); nsCOMPtr principal; nsCOMPtr csp; if (node) { principal = node->NodePrincipal(); principal->GetCsp(getter_AddRefs(csp)); if (csp) { #ifdef PR_LOGGING { uint32_t numPolicies = 0; nsresult rv = csp->GetPolicyCount(&numPolicies); if (NS_SUCCEEDED(rv)) { for (uint32_t i=0; iGetPolicy(i, policy); PR_LOG(gCspPRLog, PR_LOG_DEBUG, ("Document has CSP[%d]: %s", i, NS_ConvertUTF16toUTF8(policy).get())); } } } #endif // obtain the enforcement decision // (don't pass aExtra, we use that slot for redirects) csp->ShouldLoad(aContentType, aContentLocation, aRequestOrigin, aRequestContext, aMimeTypeGuess, nullptr, aDecision); } } #ifdef PR_LOGGING else { nsAutoCString uriSpec; aContentLocation->GetSpec(uriSpec); PR_LOG(gCspPRLog, PR_LOG_DEBUG, ("COULD NOT get nsINode for location: %s", uriSpec.get())); } #endif return NS_OK; } NS_IMETHODIMP CSPService::ShouldProcess(uint32_t aContentType, nsIURI *aContentLocation, nsIURI *aRequestOrigin, nsISupports *aRequestContext, const nsACString &aMimeTypeGuess, nsISupports *aExtra, nsIPrincipal *aRequestPrincipal, int16_t *aDecision) { if (!aContentLocation) return NS_ERROR_FAILURE; *aDecision = nsIContentPolicy::ACCEPT; return NS_OK; } /* nsIChannelEventSink implementation */ NS_IMETHODIMP CSPService::AsyncOnChannelRedirect(nsIChannel *oldChannel, nsIChannel *newChannel, uint32_t flags, nsIAsyncVerifyRedirectCallback *callback) { nsAsyncRedirectAutoCallback autoCallback(callback); nsCOMPtr loadInfo; nsresult rv = oldChannel->GetLoadInfo(getter_AddRefs(loadInfo)); // if no loadInfo on the channel, nothing for us to do if (!loadInfo) { return NS_OK; } // The loadInfo must not necessarily contain a Node, hence we try to query // the CSP in the following order: // a) Get the Node, the Principal of that Node, and the CSP of that Principal // b) Get the Principal and the CSP of that Principal nsCOMPtr loadingNode = loadInfo->LoadingNode(); nsCOMPtr principal = loadingNode ? loadingNode->NodePrincipal() : loadInfo->LoadingPrincipal(); NS_ASSERTION(principal, "Can not evaluate CSP without a principal"); nsCOMPtr csp; rv = principal->GetCsp(getter_AddRefs(csp)); NS_ENSURE_SUCCESS(rv, rv); // if there is no CSP, nothing for us to do if (!csp) { 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 newUri; rv = newChannel->GetURI(getter_AddRefs(newUri)); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr originalUri; rv = oldChannel->GetOriginalURI(getter_AddRefs(originalUri)); NS_ENSURE_SUCCESS(rv, rv); nsContentPolicyType policyType = loadInfo->GetContentPolicyType(); int16_t aDecision = nsIContentPolicy::ACCEPT; csp->ShouldLoad(policyType, // load type per nsIContentPolicy (uint32_t) newUri, // nsIURI nullptr, // nsIURI nullptr, // nsISupports EmptyCString(), // ACString - MIME guess originalUri, // aMimeTypeGuess &aDecision); #ifdef PR_LOGGING if (newUri) { nsAutoCString newUriSpec("None"); newUri->GetSpec(newUriSpec); PR_LOG(gCspPRLog, PR_LOG_DEBUG, ("CSPService::AsyncOnChannelRedirect called for %s", newUriSpec.get())); } if (aDecision == 1) PR_LOG(gCspPRLog, PR_LOG_DEBUG, ("CSPService::AsyncOnChannelRedirect ALLOWING request.")); else PR_LOG(gCspPRLog, PR_LOG_DEBUG, ("CSPService::AsyncOnChannelRedirect CANCELLING request.")); #endif // if ShouldLoad doesn't accept the load, cancel the request if (!NS_CP_ACCEPTED(aDecision)) { autoCallback.DontCallback(); return NS_BINDING_FAILED; } return NS_OK; }