tor-browser

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

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