1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/content/base/src/nsCSPService.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,378 @@ 1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 + 1.9 +#include "prlog.h" 1.10 +#include "nsString.h" 1.11 +#include "nsCOMPtr.h" 1.12 +#include "nsIURI.h" 1.13 +#include "nsIPrincipal.h" 1.14 +#include "nsIObserver.h" 1.15 +#include "nsIContent.h" 1.16 +#include "nsCSPService.h" 1.17 +#include "nsIContentSecurityPolicy.h" 1.18 +#include "nsIChannelPolicy.h" 1.19 +#include "nsIChannelEventSink.h" 1.20 +#include "nsIPropertyBag2.h" 1.21 +#include "nsIWritablePropertyBag2.h" 1.22 +#include "nsError.h" 1.23 +#include "nsChannelProperties.h" 1.24 +#include "nsIAsyncVerifyRedirectCallback.h" 1.25 +#include "nsAsyncRedirectVerifyHelper.h" 1.26 +#include "mozilla/Preferences.h" 1.27 +#include "nsIScriptError.h" 1.28 +#include "nsContentUtils.h" 1.29 + 1.30 +using namespace mozilla; 1.31 + 1.32 +/* Keeps track of whether or not CSP is enabled */ 1.33 +bool CSPService::sCSPEnabled = true; 1.34 + 1.35 +#ifdef PR_LOGGING 1.36 +static PRLogModuleInfo* gCspPRLog; 1.37 +#endif 1.38 + 1.39 +CSPService::CSPService() 1.40 +{ 1.41 + Preferences::AddBoolVarCache(&sCSPEnabled, "security.csp.enable"); 1.42 + 1.43 +#ifdef PR_LOGGING 1.44 + if (!gCspPRLog) 1.45 + gCspPRLog = PR_NewLogModule("CSP"); 1.46 +#endif 1.47 +} 1.48 + 1.49 +CSPService::~CSPService() 1.50 +{ 1.51 + mAppStatusCache.Clear(); 1.52 +} 1.53 + 1.54 +NS_IMPL_ISUPPORTS(CSPService, nsIContentPolicy, nsIChannelEventSink) 1.55 + 1.56 +/* nsIContentPolicy implementation */ 1.57 +NS_IMETHODIMP 1.58 +CSPService::ShouldLoad(uint32_t aContentType, 1.59 + nsIURI *aContentLocation, 1.60 + nsIURI *aRequestOrigin, 1.61 + nsISupports *aRequestContext, 1.62 + const nsACString &aMimeTypeGuess, 1.63 + nsISupports *aExtra, 1.64 + nsIPrincipal *aRequestPrincipal, 1.65 + int16_t *aDecision) 1.66 +{ 1.67 + if (!aContentLocation) 1.68 + return NS_ERROR_FAILURE; 1.69 + 1.70 +#ifdef PR_LOGGING 1.71 + { 1.72 + nsAutoCString location; 1.73 + aContentLocation->GetSpec(location); 1.74 + PR_LOG(gCspPRLog, PR_LOG_DEBUG, 1.75 + ("CSPService::ShouldLoad called for %s", location.get())); 1.76 + } 1.77 +#endif 1.78 + 1.79 + // default decision, CSP can revise it if there's a policy to enforce 1.80 + *aDecision = nsIContentPolicy::ACCEPT; 1.81 + 1.82 + // No need to continue processing if CSP is disabled 1.83 + if (!sCSPEnabled) 1.84 + return NS_OK; 1.85 + 1.86 + // shortcut for about: chrome: and resource: and javascript: uris since 1.87 + // they're not subject to CSP content policy checks. 1.88 + bool schemeMatch = false; 1.89 + NS_ENSURE_SUCCESS(aContentLocation->SchemeIs("about", &schemeMatch), NS_OK); 1.90 + if (schemeMatch) 1.91 + return NS_OK; 1.92 + NS_ENSURE_SUCCESS(aContentLocation->SchemeIs("chrome", &schemeMatch), NS_OK); 1.93 + if (schemeMatch) 1.94 + return NS_OK; 1.95 + NS_ENSURE_SUCCESS(aContentLocation->SchemeIs("resource", &schemeMatch), NS_OK); 1.96 + if (schemeMatch) 1.97 + return NS_OK; 1.98 + NS_ENSURE_SUCCESS(aContentLocation->SchemeIs("javascript", &schemeMatch), NS_OK); 1.99 + if (schemeMatch) 1.100 + return NS_OK; 1.101 + 1.102 + 1.103 + // These content types are not subject to CSP content policy checks: 1.104 + // TYPE_CSP_REPORT, TYPE_REFRESH, TYPE_DOCUMENT 1.105 + // (their mappings are null in contentSecurityPolicy.js) 1.106 + if (aContentType == nsIContentPolicy::TYPE_CSP_REPORT || 1.107 + aContentType == nsIContentPolicy::TYPE_REFRESH || 1.108 + aContentType == nsIContentPolicy::TYPE_DOCUMENT) { 1.109 + return NS_OK; 1.110 + } 1.111 + 1.112 + // ----- THIS IS A TEMPORARY FAST PATH FOR CERTIFIED APPS. ----- 1.113 + // ----- PLEASE REMOVE ONCE bug 925004 LANDS. ----- 1.114 + 1.115 + // Cache the app status for this origin. 1.116 + uint16_t status = nsIPrincipal::APP_STATUS_NOT_INSTALLED; 1.117 + nsAutoCString contentOrigin; 1.118 + aContentLocation->GetPrePath(contentOrigin); 1.119 + if (aRequestPrincipal && !mAppStatusCache.Get(contentOrigin, &status)) { 1.120 + aRequestPrincipal->GetAppStatus(&status); 1.121 + mAppStatusCache.Put(contentOrigin, status); 1.122 + } 1.123 + 1.124 + if (status == nsIPrincipal::APP_STATUS_CERTIFIED) { 1.125 + // The CSP for certified apps is : 1.126 + // "default-src *; script-src 'self'; object-src 'none'; style-src 'self'" 1.127 + // That means we can optimize for this case by: 1.128 + // - loading only same origin scripts and stylesheets. 1.129 + // - never loading objects. 1.130 + // - accepting everything else. 1.131 + 1.132 + switch (aContentType) { 1.133 + case nsIContentPolicy::TYPE_SCRIPT: 1.134 + case nsIContentPolicy::TYPE_STYLESHEET: 1.135 + { 1.136 + nsAutoCString sourceOrigin; 1.137 + aRequestOrigin->GetPrePath(sourceOrigin); 1.138 + if (!sourceOrigin.Equals(contentOrigin)) { 1.139 + *aDecision = nsIContentPolicy::REJECT_SERVER; 1.140 + } 1.141 + } 1.142 + break; 1.143 + 1.144 + case nsIContentPolicy::TYPE_OBJECT: 1.145 + *aDecision = nsIContentPolicy::REJECT_SERVER; 1.146 + break; 1.147 + 1.148 + default: 1.149 + *aDecision = nsIContentPolicy::ACCEPT; 1.150 + } 1.151 + 1.152 + // Only cache and return if we are successful. If not, we want the error 1.153 + // to be reported, and thus fallback to the slow path. 1.154 + if (*aDecision == nsIContentPolicy::ACCEPT) { 1.155 + return NS_OK; 1.156 + } 1.157 + } 1.158 + 1.159 + // ----- END OF TEMPORARY FAST PATH FOR CERTIFIED APPS. ----- 1.160 + 1.161 + // find the principal of the document that initiated this request and see 1.162 + // if it has a CSP policy object 1.163 + nsCOMPtr<nsINode> node(do_QueryInterface(aRequestContext)); 1.164 + nsCOMPtr<nsIPrincipal> principal; 1.165 + nsCOMPtr<nsIContentSecurityPolicy> csp; 1.166 + if (node) { 1.167 + principal = node->NodePrincipal(); 1.168 + principal->GetCsp(getter_AddRefs(csp)); 1.169 + 1.170 + if (csp) { 1.171 +#ifdef PR_LOGGING 1.172 + { 1.173 + int numPolicies = 0; 1.174 + nsresult rv = csp->GetPolicyCount(&numPolicies); 1.175 + if (NS_SUCCEEDED(rv)) { 1.176 + for (int i=0; i<numPolicies; i++) { 1.177 + nsAutoString policy; 1.178 + csp->GetPolicy(i, policy); 1.179 + PR_LOG(gCspPRLog, PR_LOG_DEBUG, 1.180 + ("Document has CSP[%d]: %s", i, 1.181 + NS_ConvertUTF16toUTF8(policy).get())); 1.182 + } 1.183 + } 1.184 + } 1.185 +#endif 1.186 + // obtain the enforcement decision 1.187 + // (don't pass aExtra, we use that slot for redirects) 1.188 + csp->ShouldLoad(aContentType, 1.189 + aContentLocation, 1.190 + aRequestOrigin, 1.191 + aRequestContext, 1.192 + aMimeTypeGuess, 1.193 + nullptr, 1.194 + aDecision); 1.195 + } 1.196 + } 1.197 +#ifdef PR_LOGGING 1.198 + else { 1.199 + nsAutoCString uriSpec; 1.200 + aContentLocation->GetSpec(uriSpec); 1.201 + PR_LOG(gCspPRLog, PR_LOG_DEBUG, 1.202 + ("COULD NOT get nsINode for location: %s", uriSpec.get())); 1.203 + } 1.204 +#endif 1.205 + 1.206 + return NS_OK; 1.207 +} 1.208 + 1.209 +NS_IMETHODIMP 1.210 +CSPService::ShouldProcess(uint32_t aContentType, 1.211 + nsIURI *aContentLocation, 1.212 + nsIURI *aRequestOrigin, 1.213 + nsISupports *aRequestContext, 1.214 + const nsACString &aMimeTypeGuess, 1.215 + nsISupports *aExtra, 1.216 + nsIPrincipal *aRequestPrincipal, 1.217 + int16_t *aDecision) 1.218 +{ 1.219 + if (!aContentLocation) 1.220 + return NS_ERROR_FAILURE; 1.221 + 1.222 + // default decision is to accept the item 1.223 + *aDecision = nsIContentPolicy::ACCEPT; 1.224 + 1.225 + // No need to continue processing if CSP is disabled 1.226 + if (!sCSPEnabled) 1.227 + return NS_OK; 1.228 + 1.229 + // find the nsDocument that initiated this request and see if it has a 1.230 + // CSP policy object 1.231 + nsCOMPtr<nsINode> node(do_QueryInterface(aRequestContext)); 1.232 + nsCOMPtr<nsIPrincipal> principal; 1.233 + nsCOMPtr<nsIContentSecurityPolicy> csp; 1.234 + if (node) { 1.235 + principal = node->NodePrincipal(); 1.236 + principal->GetCsp(getter_AddRefs(csp)); 1.237 + 1.238 + if (csp) { 1.239 +#ifdef PR_LOGGING 1.240 + { 1.241 + int numPolicies = 0; 1.242 + nsresult rv = csp->GetPolicyCount(&numPolicies); 1.243 + if (NS_SUCCEEDED(rv)) { 1.244 + for (int i=0; i<numPolicies; i++) { 1.245 + nsAutoString policy; 1.246 + csp->GetPolicy(i, policy); 1.247 + PR_LOG(gCspPRLog, PR_LOG_DEBUG, 1.248 + ("shouldProcess - document has policy[%d]: %s", i, 1.249 + NS_ConvertUTF16toUTF8(policy).get())); 1.250 + } 1.251 + } 1.252 + } 1.253 +#endif 1.254 + // obtain the enforcement decision 1.255 + csp->ShouldProcess(aContentType, 1.256 + aContentLocation, 1.257 + aRequestOrigin, 1.258 + aRequestContext, 1.259 + aMimeTypeGuess, 1.260 + aExtra, 1.261 + aDecision); 1.262 + } 1.263 + } 1.264 +#ifdef PR_LOGGING 1.265 + else { 1.266 + nsAutoCString uriSpec; 1.267 + aContentLocation->GetSpec(uriSpec); 1.268 + PR_LOG(gCspPRLog, PR_LOG_DEBUG, 1.269 + ("COULD NOT get nsINode for location: %s", uriSpec.get())); 1.270 + } 1.271 +#endif 1.272 + return NS_OK; 1.273 +} 1.274 + 1.275 +/* nsIChannelEventSink implementation */ 1.276 +NS_IMETHODIMP 1.277 +CSPService::AsyncOnChannelRedirect(nsIChannel *oldChannel, 1.278 + nsIChannel *newChannel, 1.279 + uint32_t flags, 1.280 + nsIAsyncVerifyRedirectCallback *callback) 1.281 +{ 1.282 + nsAsyncRedirectAutoCallback autoCallback(callback); 1.283 + 1.284 + // get the Content Security Policy and load type from the property bag 1.285 + nsCOMPtr<nsISupports> policyContainer; 1.286 + nsCOMPtr<nsIPropertyBag2> props(do_QueryInterface(oldChannel)); 1.287 + if (!props) 1.288 + return NS_OK; 1.289 + 1.290 + props->GetPropertyAsInterface(NS_CHANNEL_PROP_CHANNEL_POLICY, 1.291 + NS_GET_IID(nsISupports), 1.292 + getter_AddRefs(policyContainer)); 1.293 + 1.294 + // see if we have a valid nsIChannelPolicy containing CSP and load type 1.295 + nsCOMPtr<nsIChannelPolicy> channelPolicy(do_QueryInterface(policyContainer)); 1.296 + if (!channelPolicy) 1.297 + return NS_OK; 1.298 + 1.299 + nsCOMPtr<nsISupports> supports; 1.300 + nsCOMPtr<nsIContentSecurityPolicy> csp; 1.301 + channelPolicy->GetContentSecurityPolicy(getter_AddRefs(supports)); 1.302 + csp = do_QueryInterface(supports); 1.303 + uint32_t loadType; 1.304 + channelPolicy->GetLoadType(&loadType); 1.305 + 1.306 + // if no CSP in the channelPolicy, nothing for us to add to the channel 1.307 + if (!csp) 1.308 + return NS_OK; 1.309 + 1.310 + /* Since redirecting channels don't call into nsIContentPolicy, we call our 1.311 + * Content Policy implementation directly when redirects occur. When channels 1.312 + * are created using NS_NewChannel(), callers can optionally pass in a 1.313 + * nsIChannelPolicy containing a CSP object and load type, which is placed in 1.314 + * the new channel's property bag. This container is propagated forward when 1.315 + * channels redirect. 1.316 + */ 1.317 + 1.318 + // Does the CSP permit this host for this type of load? 1.319 + // If not, cancel the load now. 1.320 + nsCOMPtr<nsIURI> newUri; 1.321 + newChannel->GetURI(getter_AddRefs(newUri)); 1.322 + nsCOMPtr<nsIURI> originalUri; 1.323 + oldChannel->GetOriginalURI(getter_AddRefs(originalUri)); 1.324 + int16_t aDecision = nsIContentPolicy::ACCEPT; 1.325 + csp->ShouldLoad(loadType, // load type per nsIContentPolicy (uint32_t) 1.326 + newUri, // nsIURI 1.327 + nullptr, // nsIURI 1.328 + nullptr, // nsISupports 1.329 + EmptyCString(), // ACString - MIME guess 1.330 + originalUri, // nsISupports - extra 1.331 + &aDecision); 1.332 + 1.333 +#ifdef PR_LOGGING 1.334 + if (newUri) { 1.335 + nsAutoCString newUriSpec("None"); 1.336 + newUri->GetSpec(newUriSpec); 1.337 + PR_LOG(gCspPRLog, PR_LOG_DEBUG, 1.338 + ("CSPService::AsyncOnChannelRedirect called for %s", 1.339 + newUriSpec.get())); 1.340 + } 1.341 + if (aDecision == 1) 1.342 + PR_LOG(gCspPRLog, PR_LOG_DEBUG, 1.343 + ("CSPService::AsyncOnChannelRedirect ALLOWING request.")); 1.344 + else 1.345 + PR_LOG(gCspPRLog, PR_LOG_DEBUG, 1.346 + ("CSPService::AsyncOnChannelRedirect CANCELLING request.")); 1.347 +#endif 1.348 + 1.349 + // if ShouldLoad doesn't accept the load, cancel the request 1.350 + if (aDecision != 1) { 1.351 + autoCallback.DontCallback(); 1.352 + return NS_BINDING_FAILED; 1.353 + } 1.354 + 1.355 + // the redirect is permitted, so propagate the Content Security Policy 1.356 + // and load type to the redirecting channel 1.357 + nsresult rv; 1.358 + nsCOMPtr<nsIWritablePropertyBag2> props2 = do_QueryInterface(newChannel); 1.359 + if (props2) { 1.360 + rv = props2->SetPropertyAsInterface(NS_CHANNEL_PROP_CHANNEL_POLICY, 1.361 + channelPolicy); 1.362 + if (NS_SUCCEEDED(rv)) { 1.363 + return NS_OK; 1.364 + } 1.365 + } 1.366 + 1.367 + // The redirecting channel isn't a writable property bag, we won't be able 1.368 + // to enforce the load policy if it redirects again, so we stop it now. 1.369 + nsAutoCString newUriSpec; 1.370 + rv = newUri->GetSpec(newUriSpec); 1.371 + const char16_t *formatParams[] = { NS_ConvertUTF8toUTF16(newUriSpec).get() }; 1.372 + if (NS_SUCCEEDED(rv)) { 1.373 + nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, 1.374 + NS_LITERAL_CSTRING("Redirect Error"), nullptr, 1.375 + nsContentUtils::eDOM_PROPERTIES, 1.376 + "InvalidRedirectChannelWarning", 1.377 + formatParams, 1); 1.378 + } 1.379 + 1.380 + return NS_BINDING_FAILED; 1.381 +}