IntegrityPolicy.cpp (16319B)
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 "IntegrityPolicy.h" 8 9 #include "mozilla/Logging.h" 10 #include "mozilla/StaticPrefs_security.h" 11 #include "mozilla/dom/RequestBinding.h" 12 #include "mozilla/ipc/PBackgroundSharedTypes.h" 13 #include "mozilla/net/SFVService.h" 14 #include "nsCOMPtr.h" 15 #include "nsIClassInfoImpl.h" 16 #include "nsIObjectInputStream.h" 17 #include "nsIObjectOutputStream.h" 18 #include "nsString.h" 19 20 using namespace mozilla; 21 22 static LazyLogModule sIntegrityPolicyLogModule("IntegrityPolicy"); 23 #define LOG(fmt, ...) \ 24 MOZ_LOG_FMT(sIntegrityPolicyLogModule, LogLevel::Debug, fmt, ##__VA_ARGS__) 25 26 namespace mozilla::dom { 27 28 IntegrityPolicy::~IntegrityPolicy() = default; 29 30 RequestDestination ContentTypeToDestination(nsContentPolicyType aType) { 31 // From SecFetch.cpp 32 // https://searchfox.org/mozilla-central/rev/f1e32fa7054859d37eea8804e220dfcc7fb53b03/dom/security/SecFetch.cpp#24-32 33 switch (aType) { 34 case nsIContentPolicy::TYPE_INTERNAL_SCRIPT: 35 case nsIContentPolicy::TYPE_INTERNAL_SCRIPT_PRELOAD: 36 case nsIContentPolicy::TYPE_INTERNAL_MODULE: 37 case nsIContentPolicy::TYPE_INTERNAL_MODULE_PRELOAD: 38 // We currently only support documents. 39 // case nsIContentPolicy::TYPE_INTERNAL_WORKER_IMPORT_SCRIPTS: 40 case nsIContentPolicy::TYPE_INTERNAL_CHROMEUTILS_COMPILED_SCRIPT: 41 case nsIContentPolicy::TYPE_INTERNAL_FRAME_MESSAGEMANAGER_SCRIPT: 42 case nsIContentPolicy::TYPE_SCRIPT: 43 return RequestDestination::Script; 44 45 case nsIContentPolicy::TYPE_STYLESHEET: 46 case nsIContentPolicy::TYPE_INTERNAL_STYLESHEET: 47 case nsIContentPolicy::TYPE_INTERNAL_STYLESHEET_PRELOAD: 48 return RequestDestination::Style; 49 50 default: 51 return RequestDestination::_empty; 52 } 53 } 54 55 Maybe<IntegrityPolicy::DestinationType> DOMRequestDestinationToDestinationType( 56 RequestDestination aDestination) { 57 switch (aDestination) { 58 case RequestDestination::Script: 59 return Some(IntegrityPolicy::DestinationType::Script); 60 case RequestDestination::Style: 61 return StaticPrefs::security_integrity_policy_stylesheet_enabled() 62 ? Some(IntegrityPolicy::DestinationType::Style) 63 : Nothing{}; 64 65 default: 66 return Nothing{}; 67 } 68 } 69 70 Maybe<IntegrityPolicy::DestinationType> 71 IntegrityPolicy::ContentTypeToDestinationType(nsContentPolicyType aType) { 72 return DOMRequestDestinationToDestinationType( 73 ContentTypeToDestination(aType)); 74 } 75 76 nsresult GetStringsFromInnerList(nsISFVInnerList* aList, bool aIsToken, 77 nsTArray<nsCString>& aStrings) { 78 nsTArray<RefPtr<nsISFVItem>> items; 79 nsresult rv = aList->GetItems(items); 80 NS_ENSURE_SUCCESS(rv, rv); 81 82 for (auto& item : items) { 83 nsCOMPtr<nsISFVBareItem> value; 84 rv = item->GetValue(getter_AddRefs(value)); 85 NS_ENSURE_SUCCESS(rv, rv); 86 87 nsAutoCString itemStr; 88 if (aIsToken) { 89 nsCOMPtr<nsISFVToken> itemToken(do_QueryInterface(value)); 90 NS_ENSURE_TRUE(itemToken, NS_ERROR_FAILURE); 91 92 rv = itemToken->GetValue(itemStr); 93 NS_ENSURE_SUCCESS(rv, rv); 94 } else { 95 nsCOMPtr<nsISFVString> itemString(do_QueryInterface(value)); 96 NS_ENSURE_TRUE(itemString, NS_ERROR_FAILURE); 97 98 rv = itemString->GetValue(itemStr); 99 NS_ENSURE_SUCCESS(rv, rv); 100 } 101 102 aStrings.AppendElement(itemStr); 103 } 104 105 return NS_OK; 106 } 107 108 /* static */ 109 Result<IntegrityPolicy::Sources, nsresult> ParseSources( 110 nsISFVDictionary* aDict) { 111 // sources, a list of sources, Initially empty. 112 113 // 3. If dictionary["sources"] does not exist or if its value contains 114 // "inline", append "inline" to integrityPolicy’s sources. 115 nsCOMPtr<nsISFVItemOrInnerList> iil; 116 nsresult rv = aDict->Get("sources"_ns, getter_AddRefs(iil)); 117 if (NS_FAILED(rv)) { 118 // The key doesn't exists, set it to inline as per spec. 119 return IntegrityPolicy::Sources(IntegrityPolicy::SourceType::Inline); 120 } 121 122 nsCOMPtr<nsISFVInnerList> il(do_QueryInterface(iil)); 123 NS_ENSURE_TRUE(il, Err(NS_ERROR_FAILURE)); 124 125 nsTArray<nsCString> sources; 126 rv = GetStringsFromInnerList(il, true, sources); 127 NS_ENSURE_SUCCESS(rv, Err(rv)); 128 129 IntegrityPolicy::Sources result; 130 for (const auto& source : sources) { 131 if (source.EqualsLiteral("inline")) { 132 result += IntegrityPolicy::SourceType::Inline; 133 } else { 134 LOG("ParseSources: Unknown source: {}", source.get()); 135 // Unknown source, we don't know how to handle it 136 continue; 137 } 138 } 139 140 return result; 141 } 142 143 /* static */ 144 Result<IntegrityPolicy::Destinations, nsresult> ParseDestinations( 145 nsISFVDictionary* aDict) { 146 // blocked destinations, a list of destinations, initially empty. 147 148 nsCOMPtr<nsISFVItemOrInnerList> iil; 149 nsresult rv = aDict->Get("blocked-destinations"_ns, getter_AddRefs(iil)); 150 if (NS_FAILED(rv)) { 151 return IntegrityPolicy::Destinations(); 152 } 153 154 // 4. If dictionary["blocked-destinations"] exists: 155 nsCOMPtr<nsISFVInnerList> il(do_QueryInterface(iil)); 156 NS_ENSURE_TRUE(il, Err(NS_ERROR_FAILURE)); 157 158 nsTArray<nsCString> destinations; 159 rv = GetStringsFromInnerList(il, true, destinations); 160 NS_ENSURE_SUCCESS(rv, Err(rv)); 161 162 IntegrityPolicy::Destinations result; 163 for (const auto& destination : destinations) { 164 if (destination.EqualsLiteral("script")) { 165 result += IntegrityPolicy::DestinationType::Script; 166 } else if (destination.EqualsLiteral("style")) { 167 if (StaticPrefs::security_integrity_policy_stylesheet_enabled()) { 168 result += IntegrityPolicy::DestinationType::Style; 169 } 170 } else { 171 LOG("ParseDestinations: Unknown destination: {}", destination.get()); 172 // Unknown destination, we don't know how to handle it 173 continue; 174 } 175 } 176 177 return result; 178 } 179 180 /* static */ 181 Result<nsTArray<nsCString>, nsresult> ParseEndpoints(nsISFVDictionary* aDict) { 182 // endpoints, a list of strings, initially empty. 183 nsCOMPtr<nsISFVItemOrInnerList> iil; 184 nsresult rv = aDict->Get("endpoints"_ns, getter_AddRefs(iil)); 185 if (NS_FAILED(rv)) { 186 // The key doesn't exists, return empty list. 187 return nsTArray<nsCString>(); 188 } 189 190 nsCOMPtr<nsISFVInnerList> il(do_QueryInterface(iil)); 191 NS_ENSURE_TRUE(il, Err(NS_ERROR_FAILURE)); 192 nsTArray<nsCString> endpoints; 193 rv = GetStringsFromInnerList(il, true, endpoints); 194 NS_ENSURE_SUCCESS(rv, Err(rv)); 195 196 return endpoints; 197 } 198 199 /* static */ 200 // https://w3c.github.io/webappsec-subresource-integrity/#processing-an-integrity-policy 201 nsresult IntegrityPolicy::ParseHeaders(const nsACString& aHeader, 202 const nsACString& aHeaderRO, 203 IntegrityPolicy** aPolicy) { 204 if (!StaticPrefs::security_integrity_policy_enabled()) { 205 return NS_OK; 206 } 207 208 // 1. Let integrityPolicy be a new integrity policy struct. 209 // (Our struct contains two entries, one for the enforcement header and one 210 // for report-only) 211 RefPtr<IntegrityPolicy> policy = new IntegrityPolicy(); 212 213 LOG("[{}] Parsing headers: enforcement='{}' report-only='{}'", 214 static_cast<void*>(policy), aHeader.Data(), aHeaderRO.Data()); 215 216 nsCOMPtr<nsISFVService> sfv = net::GetSFVService(); 217 NS_ENSURE_TRUE(sfv, NS_ERROR_FAILURE); 218 219 for (const auto& isROHeader : {false, true}) { 220 const auto& headerString = isROHeader ? aHeaderRO : aHeader; 221 222 if (headerString.IsEmpty()) { 223 LOG("[{}] No {} header.", static_cast<void*>(policy), 224 isROHeader ? "report-only" : "enforcement"); 225 continue; 226 } 227 228 // 2. Let dictionary be the result of getting a structured field value from 229 // headers given headerName and "dictionary". 230 nsCOMPtr<nsISFVDictionary> dict; 231 nsresult rv = sfv->ParseDictionary(headerString, getter_AddRefs(dict)); 232 if (NS_FAILED(rv)) { 233 LOG("[{}] Failed to parse {} header.", static_cast<void*>(policy), 234 isROHeader ? "report-only" : "enforcement"); 235 continue; 236 } 237 238 // 3. If dictionary["sources"] does not exist or if its value contains 239 // "inline", append "inline" to integrityPolicy’s sources. 240 auto sourcesResult = ParseSources(dict); 241 if (sourcesResult.isErr()) { 242 LOG("[{}] Failed to parse sources for {} header.", 243 static_cast<void*>(policy), 244 isROHeader ? "report-only" : "enforcement"); 245 continue; 246 } 247 248 // 4. If dictionary["blocked-destinations"] exists: 249 auto destinationsResult = ParseDestinations(dict); 250 if (destinationsResult.isErr()) { 251 LOG("[{}] Failed to parse destinations for {} header.", 252 static_cast<void*>(policy), 253 isROHeader ? "report-only" : "enforcement"); 254 continue; 255 } 256 257 // 5. If dictionary["endpoints"] exists: 258 auto endpointsResult = ParseEndpoints(dict); 259 if (endpointsResult.isErr()) { 260 LOG("[{}] Failed to parse endpoints for {} header.", 261 static_cast<void*>(policy), 262 isROHeader ? "report-only" : "enforcement"); 263 continue; 264 } 265 266 LOG("[{}] Creating policy for {} header. sources={} destinations={} " 267 "endpoints=[{}]", 268 static_cast<void*>(policy), isROHeader ? "report-only" : "enforcement", 269 sourcesResult.unwrap().serialize(), 270 destinationsResult.unwrap().serialize(), 271 fmt::join(endpointsResult.unwrap(), ", ")); 272 273 Entry entry = Entry(sourcesResult.unwrap(), destinationsResult.unwrap(), 274 endpointsResult.unwrap()); 275 if (isROHeader) { 276 policy->mReportOnly.emplace(entry); 277 } else { 278 policy->mEnforcement.emplace(entry); 279 } 280 } 281 282 // 6. Return integrityPolicy. 283 policy.forget(aPolicy); 284 285 LOG("[{}] Finished parsing headers.", static_cast<void*>(policy)); 286 287 return NS_OK; 288 } 289 290 void IntegrityPolicy::PolicyContains(DestinationType aDestination, 291 bool* aContains, bool* aROContains) const { 292 // 10. Let block be a boolean, initially false. 293 *aContains = false; 294 // 11. Let reportBlock be a boolean, initially false. 295 *aROContains = false; 296 297 // 12. If policy’s sources contains "inline" and policy’s blocked destinations 298 // contains request’s destination, set block to true. 299 if (mEnforcement && mEnforcement->mDestinations.contains(aDestination) && 300 mEnforcement->mSources.contains(SourceType::Inline)) { 301 *aContains = true; 302 } 303 304 // 13. If reportPolicy’s sources contains "inline" and reportPolicy’s blocked 305 // destinations contains request’s destination, set reportBlock to true. 306 if (mReportOnly && mReportOnly->mDestinations.contains(aDestination) && 307 mReportOnly->mSources.contains(SourceType::Inline)) { 308 *aROContains = true; 309 } 310 } 311 312 void IntegrityPolicy::ToArgs(const IntegrityPolicy* aPolicy, 313 mozilla::ipc::IntegrityPolicyArgs& aArgs) { 314 aArgs.enforcement() = Nothing(); 315 aArgs.reportOnly() = Nothing(); 316 317 if (!aPolicy) { 318 return; 319 } 320 321 if (aPolicy->mEnforcement) { 322 mozilla::ipc::IntegrityPolicyEntry entry; 323 entry.sources() = aPolicy->mEnforcement->mSources; 324 entry.destinations() = aPolicy->mEnforcement->mDestinations; 325 entry.endpoints() = aPolicy->mEnforcement->mEndpoints.Clone(); 326 aArgs.enforcement() = Some(entry); 327 } 328 329 if (aPolicy->mReportOnly) { 330 mozilla::ipc::IntegrityPolicyEntry entry; 331 entry.sources() = aPolicy->mReportOnly->mSources; 332 entry.destinations() = aPolicy->mReportOnly->mDestinations; 333 entry.endpoints() = aPolicy->mReportOnly->mEndpoints.Clone(); 334 aArgs.reportOnly() = Some(entry); 335 } 336 } 337 338 void IntegrityPolicy::FromArgs(const mozilla::ipc::IntegrityPolicyArgs& aArgs, 339 IntegrityPolicy** aPolicy) { 340 RefPtr<IntegrityPolicy> policy = new IntegrityPolicy(); 341 342 if (aArgs.enforcement().isSome()) { 343 const auto& entry = *aArgs.enforcement(); 344 policy->mEnforcement.emplace(Entry(entry.sources(), entry.destinations(), 345 entry.endpoints().Clone())); 346 } 347 348 if (aArgs.reportOnly().isSome()) { 349 const auto& entry = *aArgs.reportOnly(); 350 policy->mReportOnly.emplace(Entry(entry.sources(), entry.destinations(), 351 entry.endpoints().Clone())); 352 } 353 354 policy.forget(aPolicy); 355 } 356 357 void IntegrityPolicy::InitFromOther(IntegrityPolicy* aOther) { 358 if (!aOther) { 359 return; 360 } 361 362 if (aOther->mEnforcement) { 363 mEnforcement.emplace(Entry(*aOther->mEnforcement)); 364 } 365 366 if (aOther->mReportOnly) { 367 mReportOnly.emplace(Entry(*aOther->mReportOnly)); 368 } 369 } 370 371 bool IntegrityPolicy::Equals(const IntegrityPolicy* aPolicy, 372 const IntegrityPolicy* aOtherPolicy) { 373 // Do a quick pointer check first, also checks if both are null. 374 if (aPolicy == aOtherPolicy) { 375 return true; 376 } 377 378 // We checked if they were null above, so make sure one of them is not null. 379 if (!aPolicy || !aOtherPolicy) { 380 return false; 381 } 382 383 if (!Entry::Equals(aPolicy->mEnforcement, aOtherPolicy->mEnforcement)) { 384 return false; 385 } 386 387 if (!Entry::Equals(aPolicy->mReportOnly, aOtherPolicy->mReportOnly)) { 388 return false; 389 } 390 391 return true; 392 } 393 394 bool IntegrityPolicy::Entry::Equals(const Maybe<Entry>& aPolicy, 395 const Maybe<Entry>& aOtherPolicy) { 396 // If one is set and the other is not, they are not equal. 397 if (aPolicy.isSome() != aOtherPolicy.isSome()) { 398 return false; 399 } 400 401 // If both are not set, they are equal. 402 if (aPolicy.isNothing() && aOtherPolicy.isNothing()) { 403 return true; 404 } 405 406 if (aPolicy->mSources != aOtherPolicy->mSources) { 407 return false; 408 } 409 410 if (aPolicy->mDestinations != aOtherPolicy->mDestinations) { 411 return false; 412 } 413 414 if (aPolicy->mEndpoints != aOtherPolicy->mEndpoints) { 415 return false; 416 } 417 418 return true; 419 } 420 421 constexpr static const uint32_t kIntegrityPolicySerializationVersion = 1; 422 423 NS_IMETHODIMP 424 IntegrityPolicy::Read(nsIObjectInputStream* aStream) { 425 nsresult rv; 426 427 uint32_t version; 428 rv = aStream->Read32(&version); 429 NS_ENSURE_SUCCESS(rv, rv); 430 431 if (version != kIntegrityPolicySerializationVersion) { 432 LOG("IntegrityPolicy::Read: Unsupported version: {}", version); 433 return NS_ERROR_FAILURE; 434 } 435 436 for (const bool& isRO : {false, true}) { 437 bool hasPolicy; 438 rv = aStream->ReadBoolean(&hasPolicy); 439 NS_ENSURE_SUCCESS(rv, rv); 440 441 if (!hasPolicy) { 442 continue; 443 } 444 445 uint32_t sources; 446 rv = aStream->Read32(&sources); 447 NS_ENSURE_SUCCESS(rv, rv); 448 449 Sources sourcesSet; 450 sourcesSet.deserialize(sources); 451 452 uint32_t destinations; 453 rv = aStream->Read32(&destinations); 454 NS_ENSURE_SUCCESS(rv, rv); 455 456 Destinations destinationsSet; 457 destinationsSet.deserialize(destinations); 458 459 uint32_t endpointsLen; 460 rv = aStream->Read32(&endpointsLen); 461 NS_ENSURE_SUCCESS(rv, rv); 462 463 nsTArray<nsCString> endpoints(endpointsLen); 464 for (size_t endpointI = 0; endpointI < endpointsLen; endpointI++) { 465 nsCString endpoint; 466 rv = aStream->ReadCString(endpoint); 467 NS_ENSURE_SUCCESS(rv, rv); 468 endpoints.AppendElement(std::move(endpoint)); 469 } 470 471 Entry entry = Entry(sourcesSet, destinationsSet, std::move(endpoints)); 472 if (isRO) { 473 mReportOnly.emplace(entry); 474 } else { 475 mEnforcement.emplace(entry); 476 } 477 } 478 479 return NS_OK; 480 } 481 482 NS_IMETHODIMP 483 IntegrityPolicy::Write(nsIObjectOutputStream* aStream) { 484 nsresult rv; 485 486 rv = aStream->Write32(kIntegrityPolicySerializationVersion); 487 NS_ENSURE_SUCCESS(rv, rv); 488 489 for (const auto& entry : {mEnforcement, mReportOnly}) { 490 if (!entry) { 491 aStream->WriteBoolean(false); 492 continue; 493 } 494 495 aStream->WriteBoolean(true); 496 497 rv = aStream->Write32(entry->mSources.serialize()); 498 NS_ENSURE_SUCCESS(rv, rv); 499 500 rv = aStream->Write32(entry->mDestinations.serialize()); 501 NS_ENSURE_SUCCESS(rv, rv); 502 503 rv = aStream->Write32(entry->mEndpoints.Length()); 504 for (const auto& endpoint : entry->mEndpoints) { 505 rv = aStream->WriteCString(endpoint); 506 NS_ENSURE_SUCCESS(rv, rv); 507 } 508 } 509 510 return NS_OK; 511 } 512 513 NS_IMPL_CLASSINFO(IntegrityPolicy, nullptr, 0, NS_IINTEGRITYPOLICY_IID) 514 NS_IMPL_ISUPPORTS_CI(IntegrityPolicy, nsIIntegrityPolicy, nsISerializable) 515 516 } // namespace mozilla::dom 517 518 #undef LOG