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/. */
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"
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;
33 static PRLogModuleInfo
* gCspPRLog
;
36 CSPService::CSPService()
38 Preferences::AddBoolVarCache(&sCSPEnabled
, "security.csp.enable");
42 gCspPRLog
= PR_NewLogModule("CSP");
46 CSPService::~CSPService()
48 mAppStatusCache
.Clear();
51 NS_IMPL_ISUPPORTS2(CSPService
, nsIContentPolicy
, nsIChannelEventSink
)
53 /* nsIContentPolicy implementation */
55 CSPService::ShouldLoad(uint32_t aContentType
,
56 nsIURI
*aContentLocation
,
57 nsIURI
*aRequestOrigin
,
58 nsISupports
*aRequestContext
,
59 const nsACString
&aMimeTypeGuess
,
61 nsIPrincipal
*aRequestPrincipal
,
64 if (!aContentLocation
)
65 return NS_ERROR_FAILURE
;
69 nsAutoCString location
;
70 aContentLocation
->GetSpec(location
);
71 PR_LOG(gCspPRLog
, PR_LOG_DEBUG
,
72 ("CSPService::ShouldLoad called for %s", location
.get()));
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
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
);
89 NS_ENSURE_SUCCESS(aContentLocation
->SchemeIs("chrome", &schemeMatch
), NS_OK
);
92 NS_ENSURE_SUCCESS(aContentLocation
->SchemeIs("resource", &schemeMatch
), NS_OK
);
95 NS_ENSURE_SUCCESS(aContentLocation
->SchemeIs("javascript", &schemeMatch
), 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
) {
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
;
141 case nsIContentPolicy::TYPE_OBJECT
:
142 *aDecision
= nsIContentPolicy::REJECT_SERVER
;
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
) {
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
;
164 principal
= node
->NodePrincipal();
165 principal
->GetCsp(getter_AddRefs(csp
));
171 nsresult rv
= csp
->GetPolicyCount(&numPolicies
);
172 if (NS_SUCCEEDED(rv
)) {
173 for (int i
=0; i
<numPolicies
; i
++) {
175 csp
->GetPolicy(i
, policy
);
176 PR_LOG(gCspPRLog
, PR_LOG_DEBUG
,
177 ("Document has CSP[%d]: %s", i
,
178 NS_ConvertUTF16toUTF8(policy
).get()));
183 // obtain the enforcement decision
184 // (don't pass aExtra, we use that slot for redirects)
185 csp
->ShouldLoad(aContentType
,
196 nsAutoCString uriSpec
;
197 aContentLocation
->GetSpec(uriSpec
);
198 PR_LOG(gCspPRLog
, PR_LOG_DEBUG
,
199 ("COULD NOT get nsINode for location: %s", uriSpec
.get()));
207 CSPService::ShouldProcess(uint32_t aContentType
,
208 nsIURI
*aContentLocation
,
209 nsIURI
*aRequestOrigin
,
210 nsISupports
*aRequestContext
,
211 const nsACString
&aMimeTypeGuess
,
213 nsIPrincipal
*aRequestPrincipal
,
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
226 // find the nsDocument that initiated this request and see if it has a
228 nsCOMPtr
<nsINode
> node(do_QueryInterface(aRequestContext
));
229 nsCOMPtr
<nsIPrincipal
> principal
;
230 nsCOMPtr
<nsIContentSecurityPolicy
> csp
;
232 principal
= node
->NodePrincipal();
233 principal
->GetCsp(getter_AddRefs(csp
));
239 nsresult rv
= csp
->GetPolicyCount(&numPolicies
);
240 if (NS_SUCCEEDED(rv
)) {
241 for (int i
=0; i
<numPolicies
; i
++) {
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()));
251 // obtain the enforcement decision
252 csp
->ShouldProcess(aContentType
,
263 nsAutoCString uriSpec
;
264 aContentLocation
->GetSpec(uriSpec
);
265 PR_LOG(gCspPRLog
, PR_LOG_DEBUG
,
266 ("COULD NOT get nsINode for location: %s", uriSpec
.get()));
272 /* nsIChannelEventSink implementation */
274 CSPService::AsyncOnChannelRedirect(nsIChannel
*oldChannel
,
275 nsIChannel
*newChannel
,
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
));
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
));
296 nsCOMPtr
<nsIContentSecurityPolicy
> csp
;
297 channelPolicy
->GetContentSecurityPolicy(getter_AddRefs(csp
));
299 channelPolicy
->GetLoadType(&loadType
);
301 // if no CSP in the channelPolicy, nothing for us to add to the channel
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
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)
323 nullptr, // nsISupports
324 EmptyCString(), // ACString - MIME guess
325 originalUri
, // nsISupports - extra
330 nsAutoCString
newUriSpec("None");
331 newUri
->GetSpec(newUriSpec
);
332 PR_LOG(gCspPRLog
, PR_LOG_DEBUG
,
333 ("CSPService::AsyncOnChannelRedirect called for %s",
337 PR_LOG(gCspPRLog
, PR_LOG_DEBUG
,
338 ("CSPService::AsyncOnChannelRedirect ALLOWING request."));
340 PR_LOG(gCspPRLog
, PR_LOG_DEBUG
,
341 ("CSPService::AsyncOnChannelRedirect CANCELLING request."));
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
353 nsCOMPtr
<nsIWritablePropertyBag2
> props2
= do_QueryInterface(newChannel
);
355 rv
= props2
->SetPropertyAsInterface(NS_CHANNEL_PROP_CHANNEL_POLICY
,
357 if (NS_SUCCEEDED(rv
)) {
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",
375 return NS_BINDING_FAILED
;