Backed out 35 changesets (bug 941158, bug 972518, bug 959520, bug 986063, bug 948895...
[gecko.git] / content / base / src / nsCSPService.cpp
blob5f63d900fbf69096cd92016871b21d3109d8e070
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "prlog.h"
7 #include "nsString.h"
8 #include "nsCOMPtr.h"
9 #include "nsIURI.h"
10 #include "nsIPrincipal.h"
11 #include "nsIObserver.h"
12 #include "nsIContent.h"
13 #include "nsCSPService.h"
14 #include "nsIContentSecurityPolicy.h"
15 #include "nsIChannelPolicy.h"
16 #include "nsIChannelEventSink.h"
17 #include "nsIPropertyBag2.h"
18 #include "nsIWritablePropertyBag2.h"
19 #include "nsError.h"
20 #include "nsChannelProperties.h"
21 #include "nsIAsyncVerifyRedirectCallback.h"
22 #include "nsAsyncRedirectVerifyHelper.h"
23 #include "mozilla/Preferences.h"
24 #include "nsIScriptError.h"
25 #include "nsContentUtils.h"
27 using namespace mozilla;
29 /* Keeps track of whether or not CSP is enabled */
30 bool CSPService::sCSPEnabled = true;
32 #ifdef PR_LOGGING
33 static PRLogModuleInfo* gCspPRLog;
34 #endif
36 CSPService::CSPService()
38 Preferences::AddBoolVarCache(&sCSPEnabled, "security.csp.enable");
40 #ifdef PR_LOGGING
41 if (!gCspPRLog)
42 gCspPRLog = PR_NewLogModule("CSP");
43 #endif
46 CSPService::~CSPService()
48 mAppStatusCache.Clear();
51 NS_IMPL_ISUPPORTS2(CSPService, nsIContentPolicy, nsIChannelEventSink)
53 /* nsIContentPolicy implementation */
54 NS_IMETHODIMP
55 CSPService::ShouldLoad(uint32_t aContentType,
56 nsIURI *aContentLocation,
57 nsIURI *aRequestOrigin,
58 nsISupports *aRequestContext,
59 const nsACString &aMimeTypeGuess,
60 nsISupports *aExtra,
61 nsIPrincipal *aRequestPrincipal,
62 int16_t *aDecision)
64 if (!aContentLocation)
65 return NS_ERROR_FAILURE;
67 #ifdef PR_LOGGING
69 nsAutoCString location;
70 aContentLocation->GetSpec(location);
71 PR_LOG(gCspPRLog, PR_LOG_DEBUG,
72 ("CSPService::ShouldLoad called for %s", location.get()));
74 #endif
76 // default decision, CSP can revise it if there's a policy to enforce
77 *aDecision = nsIContentPolicy::ACCEPT;
79 // No need to continue processing if CSP is disabled
80 if (!sCSPEnabled)
81 return NS_OK;
83 // shortcut for about: chrome: and resource: and javascript: uris since
84 // they're not subject to CSP content policy checks.
85 bool schemeMatch = false;
86 NS_ENSURE_SUCCESS(aContentLocation->SchemeIs("about", &schemeMatch), NS_OK);
87 if (schemeMatch)
88 return NS_OK;
89 NS_ENSURE_SUCCESS(aContentLocation->SchemeIs("chrome", &schemeMatch), NS_OK);
90 if (schemeMatch)
91 return NS_OK;
92 NS_ENSURE_SUCCESS(aContentLocation->SchemeIs("resource", &schemeMatch), NS_OK);
93 if (schemeMatch)
94 return NS_OK;
95 NS_ENSURE_SUCCESS(aContentLocation->SchemeIs("javascript", &schemeMatch), NS_OK);
96 if (schemeMatch)
97 return NS_OK;
100 // These content types are not subject to CSP content policy checks:
101 // TYPE_CSP_REPORT, TYPE_REFRESH, TYPE_DOCUMENT
102 // (their mappings are null in contentSecurityPolicy.js)
103 if (aContentType == nsIContentPolicy::TYPE_CSP_REPORT ||
104 aContentType == nsIContentPolicy::TYPE_REFRESH ||
105 aContentType == nsIContentPolicy::TYPE_DOCUMENT) {
106 return NS_OK;
109 // ----- THIS IS A TEMPORARY FAST PATH FOR CERTIFIED APPS. -----
110 // ----- PLEASE REMOVE ONCE bug 925004 LANDS. -----
112 // Cache the app status for this origin.
113 uint16_t status = nsIPrincipal::APP_STATUS_NOT_INSTALLED;
114 nsAutoCString contentOrigin;
115 aContentLocation->GetPrePath(contentOrigin);
116 if (aRequestPrincipal && !mAppStatusCache.Get(contentOrigin, &status)) {
117 aRequestPrincipal->GetAppStatus(&status);
118 mAppStatusCache.Put(contentOrigin, status);
121 if (status == nsIPrincipal::APP_STATUS_CERTIFIED) {
122 // The CSP for certified apps is :
123 // "default-src *; script-src 'self'; object-src 'none'; style-src 'self'"
124 // That means we can optimize for this case by:
125 // - loading only same origin scripts and stylesheets.
126 // - never loading objects.
127 // - accepting everything else.
129 switch (aContentType) {
130 case nsIContentPolicy::TYPE_SCRIPT:
131 case nsIContentPolicy::TYPE_STYLESHEET:
133 nsAutoCString sourceOrigin;
134 aRequestOrigin->GetPrePath(sourceOrigin);
135 if (!sourceOrigin.Equals(contentOrigin)) {
136 *aDecision = nsIContentPolicy::REJECT_SERVER;
139 break;
141 case nsIContentPolicy::TYPE_OBJECT:
142 *aDecision = nsIContentPolicy::REJECT_SERVER;
143 break;
145 default:
146 *aDecision = nsIContentPolicy::ACCEPT;
149 // Only cache and return if we are successful. If not, we want the error
150 // to be reported, and thus fallback to the slow path.
151 if (*aDecision == nsIContentPolicy::ACCEPT) {
152 return NS_OK;
156 // ----- END OF TEMPORARY FAST PATH FOR CERTIFIED APPS. -----
158 // find the principal of the document that initiated this request and see
159 // if it has a CSP policy object
160 nsCOMPtr<nsINode> node(do_QueryInterface(aRequestContext));
161 nsCOMPtr<nsIPrincipal> principal;
162 nsCOMPtr<nsIContentSecurityPolicy> csp;
163 if (node) {
164 principal = node->NodePrincipal();
165 principal->GetCsp(getter_AddRefs(csp));
167 if (csp) {
168 #ifdef PR_LOGGING
170 int numPolicies = 0;
171 nsresult rv = csp->GetPolicyCount(&numPolicies);
172 if (NS_SUCCEEDED(rv)) {
173 for (int i=0; i<numPolicies; i++) {
174 nsAutoString policy;
175 csp->GetPolicy(i, policy);
176 PR_LOG(gCspPRLog, PR_LOG_DEBUG,
177 ("Document has CSP[%d]: %s", i,
178 NS_ConvertUTF16toUTF8(policy).get()));
182 #endif
183 // obtain the enforcement decision
184 // (don't pass aExtra, we use that slot for redirects)
185 csp->ShouldLoad(aContentType,
186 aContentLocation,
187 aRequestOrigin,
188 aRequestContext,
189 aMimeTypeGuess,
190 nullptr,
191 aDecision);
194 #ifdef PR_LOGGING
195 else {
196 nsAutoCString uriSpec;
197 aContentLocation->GetSpec(uriSpec);
198 PR_LOG(gCspPRLog, PR_LOG_DEBUG,
199 ("COULD NOT get nsINode for location: %s", uriSpec.get()));
201 #endif
203 return NS_OK;
206 NS_IMETHODIMP
207 CSPService::ShouldProcess(uint32_t aContentType,
208 nsIURI *aContentLocation,
209 nsIURI *aRequestOrigin,
210 nsISupports *aRequestContext,
211 const nsACString &aMimeTypeGuess,
212 nsISupports *aExtra,
213 nsIPrincipal *aRequestPrincipal,
214 int16_t *aDecision)
216 if (!aContentLocation)
217 return NS_ERROR_FAILURE;
219 // default decision is to accept the item
220 *aDecision = nsIContentPolicy::ACCEPT;
222 // No need to continue processing if CSP is disabled
223 if (!sCSPEnabled)
224 return NS_OK;
226 // find the nsDocument that initiated this request and see if it has a
227 // CSP policy object
228 nsCOMPtr<nsINode> node(do_QueryInterface(aRequestContext));
229 nsCOMPtr<nsIPrincipal> principal;
230 nsCOMPtr<nsIContentSecurityPolicy> csp;
231 if (node) {
232 principal = node->NodePrincipal();
233 principal->GetCsp(getter_AddRefs(csp));
235 if (csp) {
236 #ifdef PR_LOGGING
238 int numPolicies = 0;
239 nsresult rv = csp->GetPolicyCount(&numPolicies);
240 if (NS_SUCCEEDED(rv)) {
241 for (int i=0; i<numPolicies; i++) {
242 nsAutoString policy;
243 csp->GetPolicy(i, policy);
244 PR_LOG(gCspPRLog, PR_LOG_DEBUG,
245 ("shouldProcess - document has policy[%d]: %s", i,
246 NS_ConvertUTF16toUTF8(policy).get()));
250 #endif
251 // obtain the enforcement decision
252 csp->ShouldProcess(aContentType,
253 aContentLocation,
254 aRequestOrigin,
255 aRequestContext,
256 aMimeTypeGuess,
257 aExtra,
258 aDecision);
261 #ifdef PR_LOGGING
262 else {
263 nsAutoCString uriSpec;
264 aContentLocation->GetSpec(uriSpec);
265 PR_LOG(gCspPRLog, PR_LOG_DEBUG,
266 ("COULD NOT get nsINode for location: %s", uriSpec.get()));
268 #endif
269 return NS_OK;
272 /* nsIChannelEventSink implementation */
273 NS_IMETHODIMP
274 CSPService::AsyncOnChannelRedirect(nsIChannel *oldChannel,
275 nsIChannel *newChannel,
276 uint32_t flags,
277 nsIAsyncVerifyRedirectCallback *callback)
279 nsAsyncRedirectAutoCallback autoCallback(callback);
281 // get the Content Security Policy and load type from the property bag
282 nsCOMPtr<nsISupports> policyContainer;
283 nsCOMPtr<nsIPropertyBag2> props(do_QueryInterface(oldChannel));
284 if (!props)
285 return NS_OK;
287 props->GetPropertyAsInterface(NS_CHANNEL_PROP_CHANNEL_POLICY,
288 NS_GET_IID(nsISupports),
289 getter_AddRefs(policyContainer));
291 // see if we have a valid nsIChannelPolicy containing CSP and load type
292 nsCOMPtr<nsIChannelPolicy> channelPolicy(do_QueryInterface(policyContainer));
293 if (!channelPolicy)
294 return NS_OK;
296 nsCOMPtr<nsIContentSecurityPolicy> csp;
297 channelPolicy->GetContentSecurityPolicy(getter_AddRefs(csp));
298 uint32_t loadType;
299 channelPolicy->GetLoadType(&loadType);
301 // if no CSP in the channelPolicy, nothing for us to add to the channel
302 if (!csp)
303 return NS_OK;
305 /* Since redirecting channels don't call into nsIContentPolicy, we call our
306 * Content Policy implementation directly when redirects occur. When channels
307 * are created using NS_NewChannel(), callers can optionally pass in a
308 * nsIChannelPolicy containing a CSP object and load type, which is placed in
309 * the new channel's property bag. This container is propagated forward when
310 * channels redirect.
313 // Does the CSP permit this host for this type of load?
314 // If not, cancel the load now.
315 nsCOMPtr<nsIURI> newUri;
316 newChannel->GetURI(getter_AddRefs(newUri));
317 nsCOMPtr<nsIURI> originalUri;
318 oldChannel->GetOriginalURI(getter_AddRefs(originalUri));
319 int16_t aDecision = nsIContentPolicy::ACCEPT;
320 csp->ShouldLoad(loadType, // load type per nsIContentPolicy (uint32_t)
321 newUri, // nsIURI
322 nullptr, // nsIURI
323 nullptr, // nsISupports
324 EmptyCString(), // ACString - MIME guess
325 originalUri, // nsISupports - extra
326 &aDecision);
328 #ifdef PR_LOGGING
329 if (newUri) {
330 nsAutoCString newUriSpec("None");
331 newUri->GetSpec(newUriSpec);
332 PR_LOG(gCspPRLog, PR_LOG_DEBUG,
333 ("CSPService::AsyncOnChannelRedirect called for %s",
334 newUriSpec.get()));
336 if (aDecision == 1)
337 PR_LOG(gCspPRLog, PR_LOG_DEBUG,
338 ("CSPService::AsyncOnChannelRedirect ALLOWING request."));
339 else
340 PR_LOG(gCspPRLog, PR_LOG_DEBUG,
341 ("CSPService::AsyncOnChannelRedirect CANCELLING request."));
342 #endif
344 // if ShouldLoad doesn't accept the load, cancel the request
345 if (aDecision != 1) {
346 autoCallback.DontCallback();
347 return NS_BINDING_FAILED;
350 // the redirect is permitted, so propagate the Content Security Policy
351 // and load type to the redirecting channel
352 nsresult rv;
353 nsCOMPtr<nsIWritablePropertyBag2> props2 = do_QueryInterface(newChannel);
354 if (props2) {
355 rv = props2->SetPropertyAsInterface(NS_CHANNEL_PROP_CHANNEL_POLICY,
356 channelPolicy);
357 if (NS_SUCCEEDED(rv)) {
358 return NS_OK;
362 // The redirecting channel isn't a writable property bag, we won't be able
363 // to enforce the load policy if it redirects again, so we stop it now.
364 nsAutoCString newUriSpec;
365 rv = newUri->GetSpec(newUriSpec);
366 const PRUnichar *formatParams[] = { NS_ConvertUTF8toUTF16(newUriSpec).get() };
367 if (NS_SUCCEEDED(rv)) {
368 nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
369 NS_LITERAL_CSTRING("Redirect Error"), nullptr,
370 nsContentUtils::eDOM_PROPERTIES,
371 "InvalidRedirectChannelWarning",
372 formatParams, 1);
375 return NS_BINDING_FAILED;