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