|
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/. */ |
|
5 |
|
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" |
|
26 |
|
27 using namespace mozilla; |
|
28 |
|
29 /* Keeps track of whether or not CSP is enabled */ |
|
30 bool CSPService::sCSPEnabled = true; |
|
31 |
|
32 #ifdef PR_LOGGING |
|
33 static PRLogModuleInfo* gCspPRLog; |
|
34 #endif |
|
35 |
|
36 CSPService::CSPService() |
|
37 { |
|
38 Preferences::AddBoolVarCache(&sCSPEnabled, "security.csp.enable"); |
|
39 |
|
40 #ifdef PR_LOGGING |
|
41 if (!gCspPRLog) |
|
42 gCspPRLog = PR_NewLogModule("CSP"); |
|
43 #endif |
|
44 } |
|
45 |
|
46 CSPService::~CSPService() |
|
47 { |
|
48 mAppStatusCache.Clear(); |
|
49 } |
|
50 |
|
51 NS_IMPL_ISUPPORTS(CSPService, nsIContentPolicy, nsIChannelEventSink) |
|
52 |
|
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) |
|
63 { |
|
64 if (!aContentLocation) |
|
65 return NS_ERROR_FAILURE; |
|
66 |
|
67 #ifdef PR_LOGGING |
|
68 { |
|
69 nsAutoCString location; |
|
70 aContentLocation->GetSpec(location); |
|
71 PR_LOG(gCspPRLog, PR_LOG_DEBUG, |
|
72 ("CSPService::ShouldLoad called for %s", location.get())); |
|
73 } |
|
74 #endif |
|
75 |
|
76 // default decision, CSP can revise it if there's a policy to enforce |
|
77 *aDecision = nsIContentPolicy::ACCEPT; |
|
78 |
|
79 // No need to continue processing if CSP is disabled |
|
80 if (!sCSPEnabled) |
|
81 return NS_OK; |
|
82 |
|
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; |
|
98 |
|
99 |
|
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; |
|
107 } |
|
108 |
|
109 // ----- THIS IS A TEMPORARY FAST PATH FOR CERTIFIED APPS. ----- |
|
110 // ----- PLEASE REMOVE ONCE bug 925004 LANDS. ----- |
|
111 |
|
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); |
|
119 } |
|
120 |
|
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. |
|
128 |
|
129 switch (aContentType) { |
|
130 case nsIContentPolicy::TYPE_SCRIPT: |
|
131 case nsIContentPolicy::TYPE_STYLESHEET: |
|
132 { |
|
133 nsAutoCString sourceOrigin; |
|
134 aRequestOrigin->GetPrePath(sourceOrigin); |
|
135 if (!sourceOrigin.Equals(contentOrigin)) { |
|
136 *aDecision = nsIContentPolicy::REJECT_SERVER; |
|
137 } |
|
138 } |
|
139 break; |
|
140 |
|
141 case nsIContentPolicy::TYPE_OBJECT: |
|
142 *aDecision = nsIContentPolicy::REJECT_SERVER; |
|
143 break; |
|
144 |
|
145 default: |
|
146 *aDecision = nsIContentPolicy::ACCEPT; |
|
147 } |
|
148 |
|
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; |
|
153 } |
|
154 } |
|
155 |
|
156 // ----- END OF TEMPORARY FAST PATH FOR CERTIFIED APPS. ----- |
|
157 |
|
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)); |
|
166 |
|
167 if (csp) { |
|
168 #ifdef PR_LOGGING |
|
169 { |
|
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())); |
|
179 } |
|
180 } |
|
181 } |
|
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); |
|
192 } |
|
193 } |
|
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())); |
|
200 } |
|
201 #endif |
|
202 |
|
203 return NS_OK; |
|
204 } |
|
205 |
|
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) |
|
215 { |
|
216 if (!aContentLocation) |
|
217 return NS_ERROR_FAILURE; |
|
218 |
|
219 // default decision is to accept the item |
|
220 *aDecision = nsIContentPolicy::ACCEPT; |
|
221 |
|
222 // No need to continue processing if CSP is disabled |
|
223 if (!sCSPEnabled) |
|
224 return NS_OK; |
|
225 |
|
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)); |
|
234 |
|
235 if (csp) { |
|
236 #ifdef PR_LOGGING |
|
237 { |
|
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())); |
|
247 } |
|
248 } |
|
249 } |
|
250 #endif |
|
251 // obtain the enforcement decision |
|
252 csp->ShouldProcess(aContentType, |
|
253 aContentLocation, |
|
254 aRequestOrigin, |
|
255 aRequestContext, |
|
256 aMimeTypeGuess, |
|
257 aExtra, |
|
258 aDecision); |
|
259 } |
|
260 } |
|
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())); |
|
267 } |
|
268 #endif |
|
269 return NS_OK; |
|
270 } |
|
271 |
|
272 /* nsIChannelEventSink implementation */ |
|
273 NS_IMETHODIMP |
|
274 CSPService::AsyncOnChannelRedirect(nsIChannel *oldChannel, |
|
275 nsIChannel *newChannel, |
|
276 uint32_t flags, |
|
277 nsIAsyncVerifyRedirectCallback *callback) |
|
278 { |
|
279 nsAsyncRedirectAutoCallback autoCallback(callback); |
|
280 |
|
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; |
|
286 |
|
287 props->GetPropertyAsInterface(NS_CHANNEL_PROP_CHANNEL_POLICY, |
|
288 NS_GET_IID(nsISupports), |
|
289 getter_AddRefs(policyContainer)); |
|
290 |
|
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; |
|
295 |
|
296 nsCOMPtr<nsISupports> supports; |
|
297 nsCOMPtr<nsIContentSecurityPolicy> csp; |
|
298 channelPolicy->GetContentSecurityPolicy(getter_AddRefs(supports)); |
|
299 csp = do_QueryInterface(supports); |
|
300 uint32_t loadType; |
|
301 channelPolicy->GetLoadType(&loadType); |
|
302 |
|
303 // if no CSP in the channelPolicy, nothing for us to add to the channel |
|
304 if (!csp) |
|
305 return NS_OK; |
|
306 |
|
307 /* Since redirecting channels don't call into nsIContentPolicy, we call our |
|
308 * Content Policy implementation directly when redirects occur. When channels |
|
309 * are created using NS_NewChannel(), callers can optionally pass in a |
|
310 * nsIChannelPolicy containing a CSP object and load type, which is placed in |
|
311 * the new channel's property bag. This container is propagated forward when |
|
312 * channels redirect. |
|
313 */ |
|
314 |
|
315 // Does the CSP permit this host for this type of load? |
|
316 // If not, cancel the load now. |
|
317 nsCOMPtr<nsIURI> newUri; |
|
318 newChannel->GetURI(getter_AddRefs(newUri)); |
|
319 nsCOMPtr<nsIURI> originalUri; |
|
320 oldChannel->GetOriginalURI(getter_AddRefs(originalUri)); |
|
321 int16_t aDecision = nsIContentPolicy::ACCEPT; |
|
322 csp->ShouldLoad(loadType, // load type per nsIContentPolicy (uint32_t) |
|
323 newUri, // nsIURI |
|
324 nullptr, // nsIURI |
|
325 nullptr, // nsISupports |
|
326 EmptyCString(), // ACString - MIME guess |
|
327 originalUri, // nsISupports - extra |
|
328 &aDecision); |
|
329 |
|
330 #ifdef PR_LOGGING |
|
331 if (newUri) { |
|
332 nsAutoCString newUriSpec("None"); |
|
333 newUri->GetSpec(newUriSpec); |
|
334 PR_LOG(gCspPRLog, PR_LOG_DEBUG, |
|
335 ("CSPService::AsyncOnChannelRedirect called for %s", |
|
336 newUriSpec.get())); |
|
337 } |
|
338 if (aDecision == 1) |
|
339 PR_LOG(gCspPRLog, PR_LOG_DEBUG, |
|
340 ("CSPService::AsyncOnChannelRedirect ALLOWING request.")); |
|
341 else |
|
342 PR_LOG(gCspPRLog, PR_LOG_DEBUG, |
|
343 ("CSPService::AsyncOnChannelRedirect CANCELLING request.")); |
|
344 #endif |
|
345 |
|
346 // if ShouldLoad doesn't accept the load, cancel the request |
|
347 if (aDecision != 1) { |
|
348 autoCallback.DontCallback(); |
|
349 return NS_BINDING_FAILED; |
|
350 } |
|
351 |
|
352 // the redirect is permitted, so propagate the Content Security Policy |
|
353 // and load type to the redirecting channel |
|
354 nsresult rv; |
|
355 nsCOMPtr<nsIWritablePropertyBag2> props2 = do_QueryInterface(newChannel); |
|
356 if (props2) { |
|
357 rv = props2->SetPropertyAsInterface(NS_CHANNEL_PROP_CHANNEL_POLICY, |
|
358 channelPolicy); |
|
359 if (NS_SUCCEEDED(rv)) { |
|
360 return NS_OK; |
|
361 } |
|
362 } |
|
363 |
|
364 // The redirecting channel isn't a writable property bag, we won't be able |
|
365 // to enforce the load policy if it redirects again, so we stop it now. |
|
366 nsAutoCString newUriSpec; |
|
367 rv = newUri->GetSpec(newUriSpec); |
|
368 const char16_t *formatParams[] = { NS_ConvertUTF8toUTF16(newUriSpec).get() }; |
|
369 if (NS_SUCCEEDED(rv)) { |
|
370 nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, |
|
371 NS_LITERAL_CSTRING("Redirect Error"), nullptr, |
|
372 nsContentUtils::eDOM_PROPERTIES, |
|
373 "InvalidRedirectChannelWarning", |
|
374 formatParams, 1); |
|
375 } |
|
376 |
|
377 return NS_BINDING_FAILED; |
|
378 } |