IntegrityPolicyService.cpp (9139B)
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 "IntegrityPolicyService.h" 8 9 #include "mozilla/BasePrincipal.h" 10 #include "mozilla/Logging.h" 11 #include "mozilla/StaticPrefs_security.h" 12 #include "mozilla/dom/Document.h" 13 #include "mozilla/dom/IntegrityPolicy.h" 14 #include "mozilla/dom/PolicyContainer.h" 15 #include "mozilla/dom/RequestBinding.h" 16 #include "mozilla/dom/SRIMetadata.h" 17 #include "mozilla/net/SFVService.h" 18 #include "nsContentSecurityManager.h" 19 #include "nsContentUtils.h" 20 #include "nsILoadInfo.h" 21 #include "nsString.h" 22 23 using namespace mozilla; 24 25 static LazyLogModule sIntegrityPolicyServiceLogModule("IntegrityPolicy"); 26 #define LOG(fmt, ...) \ 27 MOZ_LOG_FMT(sIntegrityPolicyServiceLogModule, LogLevel::Debug, fmt, \ 28 ##__VA_ARGS__) 29 30 namespace mozilla::dom { 31 32 IntegrityPolicyService::~IntegrityPolicyService() = default; 33 34 /* nsIContentPolicy implementation */ 35 NS_IMETHODIMP 36 IntegrityPolicyService::ShouldLoad(nsIURI* aContentLocation, 37 nsILoadInfo* aLoadInfo, int16_t* aDecision) { 38 LOG("ShouldLoad: [{}] Entered ShouldLoad", static_cast<void*>(aLoadInfo)); 39 40 *aDecision = nsIContentPolicy::ACCEPT; 41 42 if (!StaticPrefs::security_integrity_policy_enabled()) { 43 LOG("ShouldLoad: [{}] Integrity policy is disabled", 44 static_cast<void*>(aLoadInfo)); 45 return NS_OK; 46 } 47 48 if (!aContentLocation) { 49 LOG("ShouldLoad: [{}] No content location", static_cast<void*>(aLoadInfo)); 50 return NS_ERROR_FAILURE; 51 } 52 53 bool block = ShouldRequestBeBlocked(aContentLocation, aLoadInfo); 54 *aDecision = 55 block ? nsIContentPolicy::REJECT_SERVER : nsIContentPolicy::ACCEPT; 56 return NS_OK; 57 } 58 59 NS_IMETHODIMP IntegrityPolicyService::ShouldProcess(nsIURI* aContentLocation, 60 nsILoadInfo* aLoadInfo, 61 int16_t* aDecision) { 62 *aDecision = nsIContentPolicy::ACCEPT; 63 return NS_OK; 64 } 65 66 // https://w3c.github.io/webappsec-subresource-integrity/#should-request-be-blocked-by-integrity-policy-section 67 bool IntegrityPolicyService::ShouldRequestBeBlocked(nsIURI* aContentLocation, 68 nsILoadInfo* aLoadInfo) { 69 // Efficiency check: if we don't care about this type, we can skip. 70 auto destination = IntegrityPolicy::ContentTypeToDestinationType( 71 aLoadInfo->InternalContentPolicyType()); 72 if (destination.isNothing()) { 73 LOG("ShouldLoad: [{}] Integrity policy doesn't handle this type={}", 74 static_cast<void*>(aLoadInfo), 75 static_cast<uint8_t>(aLoadInfo->InternalContentPolicyType())); 76 return false; 77 } 78 79 // Exempt addons from integrity policy checks. 80 // Top level document loads have null LoadingPrincipal, but we don't apply 81 // integrity policy to top level document loads right now. 82 if (BasePrincipal::Cast(aLoadInfo->TriggeringPrincipal()) 83 ->OverridesCSP(aLoadInfo->GetLoadingPrincipal())) { 84 LOG("ShouldLoad: [{}] Got a request from an addon, allowing it.", 85 static_cast<void*>(aLoadInfo)); 86 return false; 87 } 88 89 // 2. Let parsedMetadata be the result of calling parse metadata with 90 // request’s integrity metadata. 91 // In our case, parsedMetadata is in loadInfo. 92 Maybe<RequestMode> maybeRequestMode; 93 aLoadInfo->GetRequestMode(&maybeRequestMode); 94 if (maybeRequestMode.isNothing()) { 95 // We don't have a request mode set explicitly, get it from the secFlags. 96 // Just make sure that we aren't trying to get it from a 97 // nsILoadInfo::SEC_ONLY_FOR_EXPLICIT_CONTENTSEC_CHECK loadInfo. In those 98 // cases, we have to set the requestMode explicitly. 99 MOZ_ASSERT(aLoadInfo->GetSecurityFlags() != 100 nsILoadInfo::SEC_ONLY_FOR_EXPLICIT_CONTENTSEC_CHECK); 101 102 maybeRequestMode = Some(nsContentSecurityManager::SecurityModeToRequestMode( 103 aLoadInfo->GetSecurityMode())); 104 } 105 106 RequestMode requestMode = *maybeRequestMode; 107 108 if (MOZ_LOG_TEST(sIntegrityPolicyServiceLogModule, LogLevel::Debug)) { 109 nsAutoString integrityMetadata; 110 aLoadInfo->GetIntegrityMetadata(integrityMetadata); 111 112 LOG("ShouldLoad: [{}] uri={} destination={} " 113 "requestMode={} integrityMetadata={}", 114 static_cast<void*>(aLoadInfo), aContentLocation->GetSpecOrDefault(), 115 static_cast<uint8_t>(*destination), static_cast<uint8_t>(requestMode), 116 NS_ConvertUTF16toUTF8(integrityMetadata).get()); 117 } 118 119 // 3. If parsedMetadata is not the empty set and request’s mode is either 120 // "cors" or "same-origin", return "Allowed". 121 if (requestMode == RequestMode::Cors || 122 requestMode == RequestMode::Same_origin) { 123 nsAutoString integrityMetadata; 124 aLoadInfo->GetIntegrityMetadata(integrityMetadata); 125 126 SRIMetadata outMetadata; 127 dom::SRICheck::IntegrityMetadata(integrityMetadata, 128 aContentLocation->GetSpecOrDefault(), 129 nullptr, &outMetadata); 130 131 if (outMetadata.IsValid()) { 132 LOG("ShouldLoad: [{}] Allowed because we have valid a integrity.", 133 static_cast<void*>(aLoadInfo)); 134 return false; 135 } 136 } 137 138 // 4. If request's url is local, return "Allowed". 139 if (aContentLocation->SchemeIs("data") || 140 aContentLocation->SchemeIs("blob") || 141 aContentLocation->SchemeIs("about")) { 142 LOG("ShouldLoad: [{}] Allowed because we have data or blob.", 143 static_cast<void*>(aLoadInfo)); 144 return false; 145 } 146 147 // We only support integrity policy for documents so far. 148 nsCOMPtr<nsIPolicyContainer> policyContainer = 149 aLoadInfo->GetPolicyContainer(); 150 if (!policyContainer) { 151 LOG("ShouldLoad: [{}] No policy container", static_cast<void*>(aLoadInfo)); 152 return false; 153 } 154 155 // 5. Let policy be policyContainer’s integrity policy. 156 // 6. Let reportPolicy be policyContainer’s report only integrity policy. 157 // Our IntegrityPolicy struct contains both the enforcement and 158 // report-only policies. 159 RefPtr<IntegrityPolicy> policy = IntegrityPolicy::Cast( 160 PolicyContainer::Cast(policyContainer)->GetIntegrityPolicy()); 161 if (!policy) { 162 // 7. If both policy and reportPolicy are empty integrity policy structs, 163 // return "Allowed". 164 LOG("ShouldLoad: [{}] No integrity policy", static_cast<void*>(aLoadInfo)); 165 return false; 166 } 167 168 // TODO: 8. Let global be request’s client’s global object. 169 // TODO: 9. If global is not a Window nor a WorkerGlobalScope, return 170 // "Allowed". 171 172 // Steps 10-13 in policy->PolicyContains(...) 173 bool contains = false; 174 bool roContains = false; 175 policy->PolicyContains(*destination, &contains, &roContains); 176 177 // TODO: 14. If block is true or reportBlock is true, then report violation 178 // with request, block, reportBlock, policy and reportPolicy. 179 MaybeReport(aContentLocation, aLoadInfo, *destination, contains, roContains); 180 181 // 15. If block is true, then return "Blocked"; otherwise "Allowed". 182 return contains; 183 } 184 185 const char* GetReportMessageKey(bool aEnforcing, 186 IntegrityPolicy::DestinationType aDestination) { 187 // If we are not enforcing, we are reporting only. 188 switch (aDestination) { 189 case IntegrityPolicy::DestinationType::Script: 190 return aEnforcing ? "IntegrityPolicyEnforceBlockedScript" 191 : "IntegrityPolicyReportOnlyBlockedScript"; 192 case IntegrityPolicy::DestinationType::Style: 193 return aEnforcing ? "IntegrityPolicyEnforceBlockedStylesheet" 194 : "IntegrityPolicyReportOnlyBlockedStylesheet"; 195 default: 196 MOZ_ASSERT_UNREACHABLE("Unhandled destination type"); 197 return nullptr; 198 } 199 } 200 201 void IntegrityPolicyService::MaybeReport( 202 nsIURI* aContentLocation, nsILoadInfo* aLoadInfo, 203 IntegrityPolicy::DestinationType aDestination, bool aEnforce, 204 bool aReportOnly) { 205 if (!aEnforce && !aReportOnly) { 206 return; 207 } 208 209 if (nsContentUtils::IsPreloadType(aLoadInfo->InternalContentPolicyType())) { 210 return; // Don't report for preloads. 211 } 212 213 const char* messageKey = GetReportMessageKey(aEnforce, aDestination); 214 NS_ENSURE_TRUE_VOID(messageKey); 215 216 // We just report to the console for now. We should use the reporting API 217 // in the future. 218 AutoTArray<nsString, 1> params = { 219 NS_ConvertUTF8toUTF16(aContentLocation->GetSpecOrDefault())}; 220 nsAutoString localizedMsg; 221 nsresult rv = nsContentUtils::FormatLocalizedString( 222 nsContentUtils::eSECURITY_PROPERTIES, messageKey, params, localizedMsg); 223 NS_ENSURE_SUCCESS_VOID(rv); 224 225 uint64_t windowID = aLoadInfo->GetInnerWindowID(); 226 227 nsContentUtils::ReportToConsoleByWindowID( 228 localizedMsg, 229 aEnforce ? nsIScriptError::errorFlag : nsIScriptError::warningFlag, 230 "Security"_ns, windowID); 231 } 232 233 NS_IMPL_ISUPPORTS(IntegrityPolicyService, nsIContentPolicy) 234 235 } // namespace mozilla::dom 236 237 #undef LOG