Bug 1861467 - [wpt-sync] Update web-platform-tests to eedf737ce39c512d0ca3471f988972e...
[gecko.git] / dom / security / nsCSPService.cpp
blobc7c58a37dc01fa5f90c01bb04761f18e280477a2
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/. */
7 #include "mozilla/Logging.h"
8 #include "mozilla/Preferences.h"
9 #include "mozilla/StaticPrefs_security.h"
10 #include "nsString.h"
11 #include "nsCOMPtr.h"
12 #include "nsIURI.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 "nsContentUtils.h"
20 #include "nsContentPolicyUtils.h"
21 #include "nsNetUtil.h"
22 #include "nsIProtocolHandler.h"
23 #include "nsQueryObject.h"
24 #include "mozilla/net/DocumentLoadListener.h"
25 #include "mozilla/net/DocumentChannel.h"
27 using namespace mozilla;
29 static LazyLogModule gCspPRLog("CSP");
31 CSPService::CSPService() = default;
33 CSPService::~CSPService() = default;
35 NS_IMPL_ISUPPORTS(CSPService, nsIContentPolicy, nsIChannelEventSink)
37 // Helper function to identify protocols and content types not subject to CSP.
38 bool subjectToCSP(nsIURI* aURI, nsContentPolicyType aContentType) {
39 ExtContentPolicyType contentType =
40 nsContentUtils::InternalContentPolicyTypeToExternal(aContentType);
42 // These content types are not subject to CSP content policy checks:
43 // TYPE_CSP_REPORT -- csp can't block csp reports
44 // TYPE_DOCUMENT -- used for frame-ancestors
45 if (contentType == ExtContentPolicy::TYPE_CSP_REPORT ||
46 contentType == ExtContentPolicy::TYPE_DOCUMENT) {
47 return false;
50 // The three protocols: data:, blob: and filesystem: share the same
51 // protocol flag (URI_IS_LOCAL_RESOURCE) with other protocols,
52 // but those three protocols get special attention in CSP and
53 // are subject to CSP, hence we have to make sure those
54 // protocols are subject to CSP, see:
55 // http://www.w3.org/TR/CSP2/#source-list-guid-matching
56 if (aURI->SchemeIs("data") || aURI->SchemeIs("blob") ||
57 aURI->SchemeIs("filesystem")) {
58 return true;
61 // Finally we have to allowlist "about:" which does not fall into
62 // the category underneath and also "javascript:" which is not
63 // subject to CSP content loading rules.
64 if (aURI->SchemeIs("about") || aURI->SchemeIs("javascript")) {
65 return false;
68 // Please note that it should be possible for websites to
69 // allowlist their own protocol handlers with respect to CSP,
70 // hence we use protocol flags to accomplish that, but we also
71 // want resource:, chrome: and moz-icon to be subject to CSP
72 // (which also use URI_IS_LOCAL_RESOURCE).
73 // Exception to the rule are images, styles, and localization
74 // DTDs using a scheme of resource: or chrome:
75 bool isImgOrStyleOrDTD = contentType == ExtContentPolicy::TYPE_IMAGE ||
76 contentType == ExtContentPolicy::TYPE_STYLESHEET ||
77 contentType == ExtContentPolicy::TYPE_DTD;
78 if (aURI->SchemeIs("resource")) {
79 nsAutoCString uriSpec;
80 aURI->GetSpec(uriSpec);
81 // Exempt pdf.js from being subject to a page's CSP.
82 if (StringBeginsWith(uriSpec, "resource://pdf.js/"_ns)) {
83 return false;
85 if (!isImgOrStyleOrDTD) {
86 return true;
89 if (aURI->SchemeIs("chrome") && !isImgOrStyleOrDTD) {
90 return true;
92 if (aURI->SchemeIs("moz-icon")) {
93 return true;
95 bool match;
96 nsresult rv = NS_URIChainHasFlags(
97 aURI, nsIProtocolHandler::URI_IS_LOCAL_RESOURCE, &match);
98 if (NS_SUCCEEDED(rv) && match) {
99 return false;
101 // all other protocols are subject To CSP.
102 return true;
105 /* static */ nsresult CSPService::ConsultCSP(nsIURI* aContentLocation,
106 nsILoadInfo* aLoadInfo,
107 const nsACString& aMimeTypeGuess,
108 int16_t* aDecision) {
109 if (!aContentLocation) {
110 return NS_ERROR_FAILURE;
113 nsContentPolicyType contentType = aLoadInfo->InternalContentPolicyType();
115 nsCOMPtr<nsICSPEventListener> cspEventListener;
116 nsresult rv =
117 aLoadInfo->GetCspEventListener(getter_AddRefs(cspEventListener));
118 NS_ENSURE_SUCCESS(rv, rv);
120 if (MOZ_LOG_TEST(gCspPRLog, LogLevel::Debug)) {
121 MOZ_LOG(gCspPRLog, LogLevel::Debug,
122 ("CSPService::ShouldLoad called for %s",
123 aContentLocation->GetSpecOrDefault().get()));
126 // default decision, CSP can revise it if there's a policy to enforce
127 *aDecision = nsIContentPolicy::ACCEPT;
129 // No need to continue processing if CSP is disabled or if the protocol
130 // or type is *not* subject to CSP.
131 // Please note, the correct way to opt-out of CSP using a custom
132 // protocolHandler is to set one of the nsIProtocolHandler flags
133 // that are allowlistet in subjectToCSP()
134 if (!subjectToCSP(aContentLocation, contentType)) {
135 return NS_OK;
138 // 1) Apply speculate CSP for preloads
139 bool isPreload = nsContentUtils::IsPreloadType(contentType);
141 if (isPreload) {
142 nsCOMPtr<nsIContentSecurityPolicy> preloadCsp = aLoadInfo->GetPreloadCsp();
143 if (preloadCsp) {
144 // obtain the enforcement decision
145 rv = preloadCsp->ShouldLoad(
146 contentType, cspEventListener, aLoadInfo, aContentLocation,
147 nullptr, // no redirect, aOriginal URL is null.
148 false, aDecision);
149 NS_ENSURE_SUCCESS(rv, rv);
151 // if the preload policy already denied the load, then there
152 // is no point in checking the real policy
153 if (NS_CP_REJECTED(*aDecision)) {
154 NS_SetRequestBlockingReason(
155 aLoadInfo, nsILoadInfo::BLOCKING_REASON_CONTENT_POLICY_PRELOAD);
157 return NS_OK;
162 // 2) Apply actual CSP to all loads. Please note that in case
163 // the csp should be overruled (e.g. by an ExpandedPrincipal)
164 // then loadinfo->GetCsp() returns that CSP instead of the
165 // document's CSP.
166 nsCOMPtr<nsIContentSecurityPolicy> csp = aLoadInfo->GetCsp();
168 if (csp) {
169 // Generally aOriginalURI denotes the URI before a redirect and hence
170 // will always be a nullptr here. Only exception are frame navigations
171 // which we want to treat as a redirect for the purpose of CSP reporting
172 // and in particular the `blocked-uri` in the CSP report where we want
173 // to report the prePath information.
174 nsCOMPtr<nsIURI> originalURI = nullptr;
175 ExtContentPolicyType extType =
176 nsContentUtils::InternalContentPolicyTypeToExternal(contentType);
177 if (extType == ExtContentPolicy::TYPE_SUBDOCUMENT &&
178 !aLoadInfo->GetOriginalFrameSrcLoad() &&
179 mozilla::StaticPrefs::
180 security_csp_truncate_blocked_uri_for_frame_navigations()) {
181 nsAutoCString prePathStr;
182 nsresult rv = aContentLocation->GetPrePath(prePathStr);
183 NS_ENSURE_SUCCESS(rv, rv);
184 rv = NS_NewURI(getter_AddRefs(originalURI), prePathStr);
185 NS_ENSURE_SUCCESS(rv, rv);
188 // obtain the enforcement decision
189 rv = csp->ShouldLoad(
190 contentType, cspEventListener, aLoadInfo, aContentLocation,
191 originalURI, // no redirect, unless it's a frame navigation.
192 !isPreload && aLoadInfo->GetSendCSPViolationEvents(), aDecision);
194 if (NS_CP_REJECTED(*aDecision)) {
195 NS_SetRequestBlockingReason(
196 aLoadInfo, nsILoadInfo::BLOCKING_REASON_CONTENT_POLICY_GENERAL);
199 NS_ENSURE_SUCCESS(rv, rv);
201 return NS_OK;
204 /* nsIContentPolicy implementation */
205 NS_IMETHODIMP
206 CSPService::ShouldLoad(nsIURI* aContentLocation, nsILoadInfo* aLoadInfo,
207 const nsACString& aMimeTypeGuess, int16_t* aDecision) {
208 return ConsultCSP(aContentLocation, aLoadInfo, aMimeTypeGuess, aDecision);
211 NS_IMETHODIMP
212 CSPService::ShouldProcess(nsIURI* aContentLocation, nsILoadInfo* aLoadInfo,
213 const nsACString& aMimeTypeGuess,
214 int16_t* aDecision) {
215 if (!aContentLocation) {
216 return NS_ERROR_FAILURE;
218 nsContentPolicyType contentType = aLoadInfo->InternalContentPolicyType();
220 if (MOZ_LOG_TEST(gCspPRLog, LogLevel::Debug)) {
221 MOZ_LOG(gCspPRLog, LogLevel::Debug,
222 ("CSPService::ShouldProcess called for %s",
223 aContentLocation->GetSpecOrDefault().get()));
226 // ShouldProcess is only relevant to TYPE_OBJECT, so let's convert the
227 // internal contentPolicyType to the mapping external one.
228 // If it is not TYPE_OBJECT, we can return at this point.
229 // Note that we should still pass the internal contentPolicyType
230 // (contentType) to ShouldLoad().
231 ExtContentPolicyType policyType =
232 nsContentUtils::InternalContentPolicyTypeToExternal(contentType);
234 if (policyType != ExtContentPolicy::TYPE_OBJECT) {
235 *aDecision = nsIContentPolicy::ACCEPT;
236 return NS_OK;
239 return ShouldLoad(aContentLocation, aLoadInfo, aMimeTypeGuess, aDecision);
242 /* nsIChannelEventSink implementation */
243 NS_IMETHODIMP
244 CSPService::AsyncOnChannelRedirect(nsIChannel* oldChannel,
245 nsIChannel* newChannel, uint32_t flags,
246 nsIAsyncVerifyRedirectCallback* callback) {
247 net::nsAsyncRedirectAutoCallback autoCallback(callback);
249 if (XRE_IsE10sParentProcess()) {
250 nsCOMPtr<nsIParentChannel> parentChannel;
251 NS_QueryNotificationCallbacks(oldChannel, parentChannel);
252 RefPtr<net::DocumentLoadListener> docListener =
253 do_QueryObject(parentChannel);
254 // Since this is an IPC'd channel we do not have access to the request
255 // context. In turn, we do not have an event target for policy violations.
256 // Enforce the CSP check in the content process where we have that info.
257 // We allow redirect checks to run for document loads via
258 // DocumentLoadListener, since these are fully supported and we don't
259 // expose the redirects to the content process. We can't do this for all
260 // request types yet because we don't serialize nsICSPEventListener.
261 if (parentChannel && !docListener) {
262 return NS_OK;
266 // Don't do these checks if we're switching from DocumentChannel
267 // to a real channel. In that case, we should already have done
268 // the checks in the parent process. AsyncOnChannelRedirect
269 // isn't called in the content process if we switch process,
270 // so checking here would just hide bugs in the process switch
271 // cases.
272 if (RefPtr<net::DocumentChannel> docChannel = do_QueryObject(oldChannel)) {
273 return NS_OK;
276 nsCOMPtr<nsIURI> newUri;
277 nsresult rv = newChannel->GetURI(getter_AddRefs(newUri));
278 NS_ENSURE_SUCCESS(rv, rv);
280 nsCOMPtr<nsILoadInfo> loadInfo = oldChannel->LoadInfo();
282 /* Since redirecting channels don't call into nsIContentPolicy, we call our
283 * Content Policy implementation directly when redirects occur using the
284 * information set in the LoadInfo when channels are created.
286 * We check if the CSP permits this host for this type of load, if not,
287 * we cancel the load now.
289 nsCOMPtr<nsIURI> originalUri;
290 rv = oldChannel->GetOriginalURI(getter_AddRefs(originalUri));
291 if (NS_FAILED(rv)) {
292 autoCallback.DontCallback();
293 oldChannel->Cancel(NS_ERROR_DOM_BAD_URI);
294 return rv;
297 Maybe<nsresult> cancelCode;
298 rv = ConsultCSPForRedirect(originalUri, newUri, loadInfo, cancelCode);
299 if (cancelCode) {
300 oldChannel->Cancel(*cancelCode);
302 if (NS_FAILED(rv)) {
303 autoCallback.DontCallback();
306 return rv;
309 nsresult CSPService::ConsultCSPForRedirect(nsIURI* aOriginalURI,
310 nsIURI* aNewURI,
311 nsILoadInfo* aLoadInfo,
312 Maybe<nsresult>& aCancelCode) {
313 // Check CSP navigate-to
314 // We need to enforce the CSP of the document that initiated the load,
315 // which is the CSP to inherit.
316 nsCOMPtr<nsIContentSecurityPolicy> cspToInherit =
317 aLoadInfo->GetCspToInherit();
318 if (cspToInherit) {
319 bool allowsNavigateTo = false;
320 nsresult rv = cspToInherit->GetAllowsNavigateTo(
321 aNewURI, aLoadInfo->GetIsFormSubmission(), true, /* aWasRedirected */
322 false, /* aEnforceAllowlist */
323 &allowsNavigateTo);
324 NS_ENSURE_SUCCESS(rv, rv);
326 if (!allowsNavigateTo) {
327 aCancelCode = Some(NS_ERROR_CSP_NAVIGATE_TO_VIOLATION);
328 return NS_OK;
332 // No need to continue processing if CSP is disabled or if the protocol
333 // is *not* subject to CSP.
334 // Please note, the correct way to opt-out of CSP using a custom
335 // protocolHandler is to set one of the nsIProtocolHandler flags
336 // that are allowlistet in subjectToCSP()
337 nsContentPolicyType policyType = aLoadInfo->InternalContentPolicyType();
338 if (!subjectToCSP(aNewURI, policyType)) {
339 return NS_OK;
342 nsCOMPtr<nsICSPEventListener> cspEventListener;
343 nsresult rv =
344 aLoadInfo->GetCspEventListener(getter_AddRefs(cspEventListener));
345 MOZ_ALWAYS_SUCCEEDS(rv);
347 bool isPreload = nsContentUtils::IsPreloadType(policyType);
349 /* On redirect, if the content policy is a preload type, rejecting the
350 * preload results in the load silently failing, so we pass true to
351 * the aSendViolationReports parameter. See Bug 1219453.
354 int16_t decision = nsIContentPolicy::ACCEPT;
356 // 1) Apply speculative CSP for preloads
357 if (isPreload) {
358 nsCOMPtr<nsIContentSecurityPolicy> preloadCsp = aLoadInfo->GetPreloadCsp();
359 if (preloadCsp) {
360 // Pass originalURI to indicate the redirect
361 preloadCsp->ShouldLoad(
362 policyType, // load type per nsIContentPolicy (uint32_t)
363 cspEventListener, aLoadInfo,
364 aNewURI, // nsIURI
365 aOriginalURI, // Original nsIURI
366 true, // aSendViolationReports
367 &decision);
369 // if the preload policy already denied the load, then there
370 // is no point in checking the real policy
371 if (NS_CP_REJECTED(decision)) {
372 aCancelCode = Some(NS_ERROR_DOM_BAD_URI);
373 return NS_BINDING_FAILED;
378 // 2) Apply actual CSP to all loads
379 nsCOMPtr<nsIContentSecurityPolicy> csp = aLoadInfo->GetCsp();
380 if (csp) {
381 // Pass originalURI to indicate the redirect
382 csp->ShouldLoad(policyType, // load type per nsIContentPolicy (uint32_t)
383 cspEventListener, aLoadInfo,
384 aNewURI, // nsIURI
385 aOriginalURI, // Original nsIURI
386 true, // aSendViolationReports
387 &decision);
388 if (NS_CP_REJECTED(decision)) {
389 aCancelCode = Some(NS_ERROR_DOM_BAD_URI);
390 return NS_BINDING_FAILED;
394 return NS_OK;