tor-browser

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

FramingChecker.cpp (8376B)


      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 "FramingChecker.h"
      8 
      9 #include <stdint.h>  // uint32_t
     10 
     11 #include "mozilla/Assertions.h"
     12 #include "mozilla/RefPtr.h"
     13 #include "mozilla/Services.h"
     14 #include "mozilla/dom/WindowGlobalParent.h"
     15 #include "mozilla/net/HttpBaseChannel.h"
     16 #include "nsCOMPtr.h"
     17 #include "nsContentSecurityUtils.h"
     18 #include "nsContentUtils.h"
     19 #include "nsDebug.h"
     20 #include "nsError.h"
     21 #include "nsGlobalWindowOuter.h"
     22 #include "nsHttpChannel.h"
     23 #include "nsIContentPolicy.h"
     24 #include "nsIObserverService.h"
     25 #include "nsIScriptError.h"
     26 #include "nsLiteralString.h"
     27 #include "nsStringFwd.h"
     28 #include "nsTArray.h"
     29 
     30 using namespace mozilla;
     31 using namespace mozilla::dom;
     32 
     33 /* static */
     34 void FramingChecker::ReportError(const char* aMessageTag,
     35                                 nsIHttpChannel* aChannel, nsIURI* aURI,
     36                                 const nsAString& aPolicy) {
     37  MOZ_ASSERT(aChannel);
     38  MOZ_ASSERT(aURI);
     39 
     40  nsCOMPtr<net::HttpBaseChannel> httpChannel = do_QueryInterface(aChannel);
     41  if (!httpChannel) {
     42    return;
     43  }
     44 
     45  // Get the URL spec
     46  nsAutoCString spec;
     47  nsresult rv = aURI->GetAsciiSpec(spec);
     48  if (NS_FAILED(rv)) {
     49    return;
     50  }
     51 
     52  nsTArray<nsString> params;
     53  params.AppendElement(aPolicy);
     54  params.AppendElement(NS_ConvertUTF8toUTF16(spec));
     55 
     56  httpChannel->AddConsoleReport(nsIScriptError::errorFlag, "X-Frame-Options"_ns,
     57                                nsContentUtils::eSECURITY_PROPERTIES, spec, 0,
     58                                0, nsDependentCString(aMessageTag), params);
     59 
     60  // we are notifying observers for testing purposes because there is no event
     61  // to gather that an iframe load was blocked or not.
     62  nsCOMPtr<nsIObserverService> observerService =
     63      mozilla::services::GetObserverService();
     64  nsAutoString policy(aPolicy);
     65  observerService->NotifyObservers(aURI, "xfo-on-violate-policy", policy.get());
     66 }
     67 
     68 // Ignore x-frame-options if CSP with frame-ancestors exists
     69 static bool ShouldIgnoreFrameOptions(nsIChannel* aChannel,
     70                                     nsIContentSecurityPolicy* aCSP) {
     71  NS_ENSURE_TRUE(aChannel, false);
     72  if (!aCSP) {
     73    return false;
     74  }
     75 
     76  bool enforcesFrameAncestors = false;
     77  aCSP->GetEnforcesFrameAncestors(&enforcesFrameAncestors);
     78  if (!enforcesFrameAncestors) {
     79    // if CSP does not contain frame-ancestors, then there
     80    // is nothing to do here.
     81    return false;
     82  }
     83 
     84  return true;
     85 }
     86 
     87 // Check if X-Frame-Options permits this document to be loaded as a
     88 // subdocument. This will iterate through and check any number of
     89 // X-Frame-Options policies in the request (comma-separated in a header,
     90 // multiple headers, etc).
     91 // This is based on:
     92 // https://html.spec.whatwg.org/multipage/document-lifecycle.html#the-x-frame-options-header
     93 /* static */
     94 bool FramingChecker::CheckFrameOptions(nsIChannel* aChannel,
     95                                       nsIContentSecurityPolicy* aCsp,
     96                                       bool& outIsFrameCheckingSkipped) {
     97  // Step 1. If navigable is not a child navigable return true
     98  if (!aChannel) {
     99    return true;
    100  }
    101 
    102  // xfo check only makes sense for subdocument and object loads, if this is
    103  // not a load of such type, there is nothing to do here.
    104  nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
    105  ExtContentPolicyType contentType = loadInfo->GetExternalContentPolicyType();
    106  if (contentType != ExtContentPolicy::TYPE_SUBDOCUMENT &&
    107      contentType != ExtContentPolicy::TYPE_OBJECT) {
    108    return true;
    109  }
    110 
    111  // xfo can only hang off an httpchannel, if this is not an httpChannel
    112  // then there is nothing to do here.
    113  nsCOMPtr<nsIHttpChannel> httpChannel;
    114  nsresult rv = nsContentSecurityUtils::GetHttpChannelFromPotentialMultiPart(
    115      aChannel, getter_AddRefs(httpChannel));
    116  if (NS_WARN_IF(NS_FAILED(rv))) {
    117    return true;
    118  }
    119  if (!httpChannel) {
    120    return true;
    121  }
    122 
    123  // ignore XFO checks on channels that will be redirected
    124  uint32_t responseStatus;
    125  rv = httpChannel->GetResponseStatus(&responseStatus);
    126  if (NS_FAILED(rv)) {
    127    // GetResponseStatus returning failure is expected in several situations, so
    128    // do not warn if it fails.
    129    return true;
    130  }
    131  if (mozilla::net::nsHttpChannel::IsRedirectStatus(responseStatus)) {
    132    return true;
    133  }
    134 
    135  nsAutoCString xfoHeaderValue;
    136  (void)httpChannel->GetResponseHeader("X-Frame-Options"_ns, xfoHeaderValue);
    137 
    138  // Step 10. (paritally) if the only header we received was empty, then we
    139  // process it as if it wasn't sent at all.
    140  if (xfoHeaderValue.IsEmpty()) {
    141    return true;
    142  }
    143 
    144  // Step 2. xfo checks are ignored in the case where CSP frame-ancestors is
    145  // present, if so, there is nothing to do here.
    146  if (ShouldIgnoreFrameOptions(aChannel, aCsp)) {
    147    outIsFrameCheckingSkipped = true;
    148    return true;
    149  }
    150 
    151  static const char kASCIIWhitespace[] = "\t ";
    152 
    153  // Step 3-4. reduce the header options to a unique set and count how many
    154  // unique values (that we track) are encountered. this avoids using a set to
    155  // stop attackers from inheriting arbitrary values in memory and reduce the
    156  // complexity of the code.
    157  XFOHeader xfoOptions;
    158  for (const nsACString& next : xfoHeaderValue.Split(',')) {
    159    nsAutoCString option(next);
    160    option.Trim(kASCIIWhitespace);
    161 
    162    if (option.LowerCaseEqualsLiteral("allowall")) {
    163      xfoOptions.ALLOWALL = true;
    164    } else if (option.LowerCaseEqualsLiteral("sameorigin")) {
    165      xfoOptions.SAMEORIGIN = true;
    166    } else if (option.LowerCaseEqualsLiteral("deny")) {
    167      xfoOptions.DENY = true;
    168    } else {
    169      xfoOptions.INVALID = true;
    170    }
    171  }
    172 
    173  nsCOMPtr<nsIURI> uri;
    174  httpChannel->GetURI(getter_AddRefs(uri));
    175 
    176  // Step 6. if header has multiple contradicting directives return early and
    177  // prohibit the load. ALLOWALL is considered here for legacy reasons.
    178  uint32_t xfoUniqueOptions = xfoOptions.DENY + xfoOptions.ALLOWALL +
    179                              xfoOptions.SAMEORIGIN + xfoOptions.INVALID;
    180  if (xfoUniqueOptions > 1 &&
    181      (xfoOptions.DENY || xfoOptions.ALLOWALL || xfoOptions.SAMEORIGIN)) {
    182    ReportError("XFrameOptionsInvalid", httpChannel, uri, u"invalid"_ns);
    183    return false;
    184  }
    185 
    186  // Step 7 (multiple INVALID values) and partially Step 10 (single INVALID
    187  // value). if header has any invalid options, but no valid directives (DENY,
    188  // ALLOWALL, SAMEORIGIN) then allow the load.
    189  if (xfoOptions.INVALID) {
    190    ReportError("XFrameOptionsInvalid", httpChannel, uri, u"invalid"_ns);
    191    return true;
    192  }
    193 
    194  // Step 8. if the value of the header is DENY prohibit the load.
    195  if (xfoOptions.DENY) {
    196    ReportError("XFrameOptionsDeny", httpChannel, uri, u"deny"_ns);
    197    return false;
    198  }
    199 
    200  // Step 9. If the X-Frame-Options value is SAMEORIGIN, then the top frame in
    201  // the parent chain must be from the same origin as this document.
    202  RefPtr<mozilla::dom::BrowsingContext> ctx;
    203  loadInfo->GetBrowsingContext(getter_AddRefs(ctx));
    204 
    205  while (ctx && xfoOptions.SAMEORIGIN) {
    206    nsCOMPtr<nsIPrincipal> principal;
    207    // Generally CheckFrameOptions is consulted from within the
    208    // DocumentLoadListener in the parent process. For loads of type object and
    209    // embed it's called from the Document in the content process.
    210    if (XRE_IsParentProcess()) {
    211      WindowGlobalParent* window = ctx->Canonical()->GetCurrentWindowGlobal();
    212      if (window) {
    213        // Using the URI of the Principal and not the document because
    214        // window.open inherits the principal and hence the URI of the opening
    215        // context needed for same origin checks.
    216        principal = window->DocumentPrincipal();
    217      }
    218    } else if (nsPIDOMWindowOuter* windowOuter = ctx->GetDOMWindow()) {
    219      principal = nsGlobalWindowOuter::Cast(windowOuter)->GetPrincipal();
    220    }
    221 
    222    if (principal && principal->IsSystemPrincipal()) {
    223      return true;
    224    }
    225 
    226    // one of the ancestors is not same origin as this document
    227    if (!principal || !principal->IsSameOrigin(uri)) {
    228      ReportError("XFrameOptionsDeny", httpChannel, uri, u"sameorigin"_ns);
    229      return false;
    230    }
    231    ctx = ctx->GetParent();
    232  }
    233 
    234  // Step 10.
    235  return true;
    236 }