tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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