Line data Source code
1 : /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 : /* This Source Code Form is subject to the terms of the Mozilla Public
4 : * License, v. 2.0. If a copy of the MPL was not distributed with this
5 : * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 :
7 : #include "mozilla/Logging.h"
8 : #include "nsString.h"
9 : #include "nsCOMPtr.h"
10 : #include "nsIURI.h"
11 : #include "nsIPrincipal.h"
12 : #include "nsIObserver.h"
13 : #include "nsIContent.h"
14 : #include "nsCSPService.h"
15 : #include "nsIContentSecurityPolicy.h"
16 : #include "nsError.h"
17 : #include "nsIAsyncVerifyRedirectCallback.h"
18 : #include "nsAsyncRedirectVerifyHelper.h"
19 : #include "mozilla/Preferences.h"
20 : #include "nsIScriptError.h"
21 : #include "nsContentUtils.h"
22 : #include "nsContentPolicyUtils.h"
23 :
24 : using namespace mozilla;
25 :
26 : /* Keeps track of whether or not CSP is enabled */
27 : bool CSPService::sCSPEnabled = true;
28 :
29 : static LazyLogModule gCspPRLog("CSP");
30 :
31 3 : CSPService::CSPService()
32 : {
33 3 : Preferences::AddBoolVarCache(&sCSPEnabled, "security.csp.enable");
34 3 : }
35 :
36 0 : CSPService::~CSPService()
37 : {
38 0 : mAppStatusCache.Clear();
39 0 : }
40 :
41 36 : NS_IMPL_ISUPPORTS(CSPService, nsIContentPolicy, nsIChannelEventSink)
42 :
43 : // Helper function to identify protocols and content types not subject to CSP.
44 : bool
45 23 : subjectToCSP(nsIURI* aURI, nsContentPolicyType aContentType) {
46 : // These content types are not subject to CSP content policy checks:
47 : // TYPE_CSP_REPORT -- csp can't block csp reports
48 : // TYPE_REFRESH -- never passed to ShouldLoad (see nsIContentPolicy.idl)
49 : // TYPE_DOCUMENT -- used for frame-ancestors
50 23 : if (aContentType == nsIContentPolicy::TYPE_CSP_REPORT ||
51 23 : aContentType == nsIContentPolicy::TYPE_REFRESH ||
52 : aContentType == nsIContentPolicy::TYPE_DOCUMENT) {
53 10 : return false;
54 : }
55 :
56 : // The three protocols: data:, blob: and filesystem: share the same
57 : // protocol flag (URI_IS_LOCAL_RESOURCE) with other protocols, like
58 : // chrome:, resource:, moz-icon:, but those three protocols get
59 : // special attention in CSP and are subject to CSP, hence we have
60 : // to make sure those protocols are subject to CSP, see:
61 : // http://www.w3.org/TR/CSP2/#source-list-guid-matching
62 13 : bool match = false;
63 13 : nsresult rv = aURI->SchemeIs("data", &match);
64 13 : if (NS_SUCCEEDED(rv) && match) {
65 0 : return true;
66 : }
67 13 : rv = aURI->SchemeIs("blob", &match);
68 13 : if (NS_SUCCEEDED(rv) && match) {
69 0 : return true;
70 : }
71 13 : rv = aURI->SchemeIs("filesystem", &match);
72 13 : if (NS_SUCCEEDED(rv) && match) {
73 0 : return true;
74 : }
75 :
76 : // Finally we have to whitelist "about:" which does not fall into
77 : // the category underneath and also "javascript:" which is not
78 : // subject to CSP content loading rules.
79 13 : rv = aURI->SchemeIs("about", &match);
80 13 : if (NS_SUCCEEDED(rv) && match) {
81 0 : return false;
82 : }
83 13 : rv = aURI->SchemeIs("javascript", &match);
84 13 : if (NS_SUCCEEDED(rv) && match) {
85 0 : return false;
86 : }
87 :
88 : // Other protocols are not subject to CSP and can be whitelisted:
89 : // * URI_IS_LOCAL_RESOURCE
90 : // e.g. chrome:, data:, blob:, resource:, moz-icon:
91 : // Please note that it should be possible for websites to
92 : // whitelist their own protocol handlers with respect to CSP,
93 : // hence we use protocol flags to accomplish that.
94 13 : rv = NS_URIChainHasFlags(aURI, nsIProtocolHandler::URI_IS_LOCAL_RESOURCE, &match);
95 13 : if (NS_SUCCEEDED(rv) && match) {
96 8 : return false;
97 : }
98 : // all other protocols are subject To CSP.
99 5 : return true;
100 : }
101 :
102 : /* nsIContentPolicy implementation */
103 : NS_IMETHODIMP
104 23 : CSPService::ShouldLoad(uint32_t aContentType,
105 : nsIURI *aContentLocation,
106 : nsIURI *aRequestOrigin,
107 : nsISupports *aRequestContext,
108 : const nsACString &aMimeTypeGuess,
109 : nsISupports *aExtra,
110 : nsIPrincipal *aRequestPrincipal,
111 : int16_t *aDecision)
112 : {
113 23 : if (!aContentLocation) {
114 0 : return NS_ERROR_FAILURE;
115 : }
116 :
117 23 : if (MOZ_LOG_TEST(gCspPRLog, LogLevel::Debug)) {
118 0 : MOZ_LOG(gCspPRLog, LogLevel::Debug,
119 : ("CSPService::ShouldLoad called for %s",
120 : aContentLocation->GetSpecOrDefault().get()));
121 : }
122 :
123 : // default decision, CSP can revise it if there's a policy to enforce
124 23 : *aDecision = nsIContentPolicy::ACCEPT;
125 :
126 : // No need to continue processing if CSP is disabled or if the protocol
127 : // or type is *not* subject to CSP.
128 : // Please note, the correct way to opt-out of CSP using a custom
129 : // protocolHandler is to set one of the nsIProtocolHandler flags
130 : // that are whitelistet in subjectToCSP()
131 23 : if (!sCSPEnabled || !subjectToCSP(aContentLocation, aContentType)) {
132 18 : return NS_OK;
133 : }
134 :
135 : // query the principal of the document; if no document is passed, then
136 : // fall back to using the requestPrincipal (e.g. service workers do not
137 : // pass a document).
138 10 : nsCOMPtr<nsINode> node(do_QueryInterface(aRequestContext));
139 4 : nsCOMPtr<nsIPrincipal> principal = node ? node->NodePrincipal()
140 14 : : aRequestPrincipal;
141 5 : if (!principal) {
142 : // if we can't query a principal, then there is nothing to do.
143 0 : return NS_OK;
144 : }
145 5 : nsresult rv = NS_OK;
146 :
147 : // 1) Apply speculate CSP for preloads
148 5 : bool isPreload = nsContentUtils::IsPreloadType(aContentType);
149 :
150 5 : if (isPreload) {
151 4 : nsCOMPtr<nsIContentSecurityPolicy> preloadCsp;
152 2 : rv = principal->GetPreloadCsp(getter_AddRefs(preloadCsp));
153 2 : NS_ENSURE_SUCCESS(rv, rv);
154 :
155 2 : if (preloadCsp) {
156 : // obtain the enforcement decision
157 : // (don't pass aExtra, we use that slot for redirects)
158 0 : rv = preloadCsp->ShouldLoad(aContentType,
159 : aContentLocation,
160 : aRequestOrigin,
161 : aRequestContext,
162 : aMimeTypeGuess,
163 : nullptr, // aExtra
164 0 : aDecision);
165 0 : NS_ENSURE_SUCCESS(rv, rv);
166 :
167 : // if the preload policy already denied the load, then there
168 : // is no point in checking the real policy
169 0 : if (NS_CP_REJECTED(*aDecision)) {
170 0 : return NS_OK;
171 : }
172 : }
173 : }
174 :
175 : // 2) Apply actual CSP to all loads
176 10 : nsCOMPtr<nsIContentSecurityPolicy> csp;
177 5 : rv = principal->GetCsp(getter_AddRefs(csp));
178 5 : NS_ENSURE_SUCCESS(rv, rv);
179 :
180 5 : if (csp) {
181 : // obtain the enforcement decision
182 : // (don't pass aExtra, we use that slot for redirects)
183 0 : rv = csp->ShouldLoad(aContentType,
184 : aContentLocation,
185 : aRequestOrigin,
186 : aRequestContext,
187 : aMimeTypeGuess,
188 : nullptr,
189 0 : aDecision);
190 0 : NS_ENSURE_SUCCESS(rv, rv);
191 : }
192 5 : return NS_OK;
193 : }
194 :
195 : NS_IMETHODIMP
196 0 : CSPService::ShouldProcess(uint32_t aContentType,
197 : nsIURI *aContentLocation,
198 : nsIURI *aRequestOrigin,
199 : nsISupports *aRequestContext,
200 : const nsACString &aMimeTypeGuess,
201 : nsISupports *aExtra,
202 : nsIPrincipal *aRequestPrincipal,
203 : int16_t *aDecision)
204 : {
205 0 : if (!aContentLocation) {
206 0 : return NS_ERROR_FAILURE;
207 : }
208 :
209 0 : if (MOZ_LOG_TEST(gCspPRLog, LogLevel::Debug)) {
210 0 : MOZ_LOG(gCspPRLog, LogLevel::Debug,
211 : ("CSPService::ShouldProcess called for %s",
212 : aContentLocation->GetSpecOrDefault().get()));
213 : }
214 :
215 : // ShouldProcess is only relevant to TYPE_OBJECT, so let's convert the
216 : // internal contentPolicyType to the mapping external one.
217 : // If it is not TYPE_OBJECT, we can return at this point.
218 : // Note that we should still pass the internal contentPolicyType
219 : // (aContentType) to ShouldLoad().
220 : uint32_t policyType =
221 0 : nsContentUtils::InternalContentPolicyTypeToExternal(aContentType);
222 :
223 0 : if (policyType != nsIContentPolicy::TYPE_OBJECT) {
224 0 : *aDecision = nsIContentPolicy::ACCEPT;
225 0 : return NS_OK;
226 : }
227 :
228 : return ShouldLoad(aContentType,
229 : aContentLocation,
230 : aRequestOrigin,
231 : aRequestContext,
232 : aMimeTypeGuess,
233 : aExtra,
234 : aRequestPrincipal,
235 0 : aDecision);
236 : }
237 :
238 : /* nsIChannelEventSink implementation */
239 : NS_IMETHODIMP
240 0 : CSPService::AsyncOnChannelRedirect(nsIChannel *oldChannel,
241 : nsIChannel *newChannel,
242 : uint32_t flags,
243 : nsIAsyncVerifyRedirectCallback *callback)
244 : {
245 0 : net::nsAsyncRedirectAutoCallback autoCallback(callback);
246 :
247 0 : nsCOMPtr<nsIURI> newUri;
248 0 : nsresult rv = newChannel->GetURI(getter_AddRefs(newUri));
249 0 : NS_ENSURE_SUCCESS(rv, rv);
250 :
251 0 : nsCOMPtr<nsILoadInfo> loadInfo = oldChannel->GetLoadInfo();
252 :
253 : // if no loadInfo on the channel, nothing for us to do
254 0 : if (!loadInfo) {
255 0 : return NS_OK;
256 : }
257 :
258 : // No need to continue processing if CSP is disabled or if the protocol
259 : // is *not* subject to CSP.
260 : // Please note, the correct way to opt-out of CSP using a custom
261 : // protocolHandler is to set one of the nsIProtocolHandler flags
262 : // that are whitelistet in subjectToCSP()
263 0 : nsContentPolicyType policyType = loadInfo->InternalContentPolicyType();
264 0 : if (!sCSPEnabled || !subjectToCSP(newUri, policyType)) {
265 0 : return NS_OK;
266 : }
267 :
268 : /* Since redirecting channels don't call into nsIContentPolicy, we call our
269 : * Content Policy implementation directly when redirects occur using the
270 : * information set in the LoadInfo when channels are created.
271 : *
272 : * We check if the CSP permits this host for this type of load, if not,
273 : * we cancel the load now.
274 : */
275 0 : nsCOMPtr<nsIURI> originalUri;
276 0 : rv = oldChannel->GetOriginalURI(getter_AddRefs(originalUri));
277 0 : if (NS_FAILED(rv)) {
278 0 : autoCallback.DontCallback();
279 0 : oldChannel->Cancel(NS_ERROR_DOM_BAD_URI);
280 0 : return rv;
281 : }
282 :
283 0 : bool isPreload = nsContentUtils::IsPreloadType(policyType);
284 :
285 : /* On redirect, if the content policy is a preload type, rejecting the preload
286 : * results in the load silently failing, so we convert preloads to the actual
287 : * type. See Bug 1219453.
288 : */
289 : policyType =
290 0 : nsContentUtils::InternalContentPolicyTypeToExternalOrWorker(policyType);
291 :
292 0 : int16_t aDecision = nsIContentPolicy::ACCEPT;
293 : // 1) Apply speculative CSP for preloads
294 0 : if (isPreload) {
295 0 : nsCOMPtr<nsIContentSecurityPolicy> preloadCsp;
296 0 : loadInfo->LoadingPrincipal()->GetPreloadCsp(getter_AddRefs(preloadCsp));
297 :
298 0 : if (preloadCsp) {
299 : // Pass originalURI as aExtra to indicate the redirect
300 0 : preloadCsp->ShouldLoad(policyType, // load type per nsIContentPolicy (uint32_t)
301 : newUri, // nsIURI
302 : nullptr, // nsIURI
303 : nullptr, // nsISupports
304 0 : EmptyCString(), // ACString - MIME guess
305 : originalUri, // aExtra
306 0 : &aDecision);
307 :
308 : // if the preload policy already denied the load, then there
309 : // is no point in checking the real policy
310 0 : if (NS_CP_REJECTED(aDecision)) {
311 0 : autoCallback.DontCallback();
312 0 : oldChannel->Cancel(NS_ERROR_DOM_BAD_URI);
313 0 : return NS_BINDING_FAILED;
314 : }
315 : }
316 : }
317 :
318 : // 2) Apply actual CSP to all loads
319 0 : nsCOMPtr<nsIContentSecurityPolicy> csp;
320 0 : loadInfo->LoadingPrincipal()->GetCsp(getter_AddRefs(csp));
321 :
322 0 : if (csp) {
323 : // Pass originalURI as aExtra to indicate the redirect
324 0 : csp->ShouldLoad(policyType, // load type per nsIContentPolicy (uint32_t)
325 : newUri, // nsIURI
326 : nullptr, // nsIURI
327 : nullptr, // nsISupports
328 0 : EmptyCString(), // ACString - MIME guess
329 : originalUri, // aExtra
330 0 : &aDecision);
331 : }
332 :
333 : // if ShouldLoad doesn't accept the load, cancel the request
334 0 : if (!NS_CP_ACCEPTED(aDecision)) {
335 0 : autoCallback.DontCallback();
336 0 : oldChannel->Cancel(NS_ERROR_DOM_BAD_URI);
337 0 : return NS_BINDING_FAILED;
338 : }
339 0 : return NS_OK;
340 : }
|