dom/security/nsCSPService.cpp
author Ryan Hunt <rhunt@eqrion.net>
Fri, 11 Jan 2019 12:21:09 -0600
changeset 453610 717a8fb8b842276af0a62b021ac3cf42833659ca
parent 453241 50f5048d3b938dc22b2f065de9329fd4b9b5cdc6
child 458812 44a945b21f87d60d4cf839ef69ce38b33fa168e3
permissions -rw-r--r--
Bug 1519468 - Consistently check if a frame has content in ScrollAnchorContainer. r=emilio Differential Revision: https://phabricator.services.mozilla.com/D16336

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "mozilla/Logging.h"
#include "mozilla/Preferences.h"
#include "mozilla/StaticPrefs.h"
#include "nsString.h"
#include "nsCOMPtr.h"
#include "nsIURI.h"
#include "nsIPrincipal.h"
#include "nsIObserver.h"
#include "nsIContent.h"
#include "nsCSPService.h"
#include "nsIContentSecurityPolicy.h"
#include "nsError.h"
#include "nsIAsyncVerifyRedirectCallback.h"
#include "nsAsyncRedirectVerifyHelper.h"
#include "nsIScriptError.h"
#include "nsContentUtils.h"
#include "nsContentPolicyUtils.h"
#include "nsNetUtil.h"

using namespace mozilla;

static LazyLogModule gCspPRLog("CSP");

CSPService::CSPService() {}

CSPService::~CSPService() {}

NS_IMPL_ISUPPORTS(CSPService, nsIContentPolicy, nsIChannelEventSink)

// Helper function to identify protocols and content types not subject to CSP.
bool subjectToCSP(nsIURI *aURI, nsContentPolicyType aContentType) {
  nsContentPolicyType contentType =
      nsContentUtils::InternalContentPolicyTypeToExternal(aContentType);

  // These content types are not subject to CSP content policy checks:
  // TYPE_CSP_REPORT -- csp can't block csp reports
  // TYPE_REFRESH    -- never passed to ShouldLoad (see nsIContentPolicy.idl)
  // TYPE_DOCUMENT   -- used for frame-ancestors
  if (contentType == nsIContentPolicy::TYPE_CSP_REPORT ||
      contentType == nsIContentPolicy::TYPE_REFRESH ||
      contentType == nsIContentPolicy::TYPE_DOCUMENT) {
    return false;
  }

  // The three protocols: data:, blob: and filesystem: share the same
  // protocol flag (URI_IS_LOCAL_RESOURCE) with other protocols,
  // but those three protocols get special attention in CSP and
  // are subject to CSP, hence we have to make sure those
  // protocols are subject to CSP, see:
  // http://www.w3.org/TR/CSP2/#source-list-guid-matching
  bool match = false;
  nsresult rv = aURI->SchemeIs("data", &match);
  if (NS_SUCCEEDED(rv) && match) {
    return true;
  }
  rv = aURI->SchemeIs("blob", &match);
  if (NS_SUCCEEDED(rv) && match) {
    return true;
  }
  rv = aURI->SchemeIs("filesystem", &match);
  if (NS_SUCCEEDED(rv) && match) {
    return true;
  }

  // Finally we have to whitelist "about:" which does not fall into
  // the category underneath and also "javascript:" which is not
  // subject to CSP content loading rules.
  rv = aURI->SchemeIs("about", &match);
  if (NS_SUCCEEDED(rv) && match) {
    return false;
  }
  rv = aURI->SchemeIs("javascript", &match);
  if (NS_SUCCEEDED(rv) && match) {
    return false;
  }

  // Please note that it should be possible for websites to
  // whitelist their own protocol handlers with respect to CSP,
  // hence we use protocol flags to accomplish that, but we also
  // want resource:, chrome: and moz-icon to be subject to CSP
  // (which also use URI_IS_LOCAL_RESOURCE).
  // Exception to the rule are images, styles, localization DTDs,
  // and XBLs using a scheme of resource: or chrome:
  bool isImgOrStyleOrDTDorXBL =
      contentType == nsIContentPolicy::TYPE_IMAGE ||
      contentType == nsIContentPolicy::TYPE_STYLESHEET ||
      contentType == nsIContentPolicy::TYPE_DTD ||
      contentType == nsIContentPolicy::TYPE_XBL;
  rv = aURI->SchemeIs("resource", &match);
  if (NS_SUCCEEDED(rv) && match && !isImgOrStyleOrDTDorXBL) {
    return true;
  }
  rv = aURI->SchemeIs("chrome", &match);
  if (NS_SUCCEEDED(rv) && match && !isImgOrStyleOrDTDorXBL) {
    return true;
  }
  rv = aURI->SchemeIs("moz-icon", &match);
  if (NS_SUCCEEDED(rv) && match) {
    return true;
  }
  rv = NS_URIChainHasFlags(aURI, nsIProtocolHandler::URI_IS_LOCAL_RESOURCE,
                           &match);
  if (NS_SUCCEEDED(rv) && match) {
    return false;
  }
  // all other protocols are subject To CSP.
  return true;
}

/* nsIContentPolicy implementation */
NS_IMETHODIMP
CSPService::ShouldLoad(nsIURI *aContentLocation, nsILoadInfo *aLoadInfo,
                       const nsACString &aMimeTypeGuess, int16_t *aDecision) {
  if (!aContentLocation) {
    return NS_ERROR_FAILURE;
  }

  uint32_t contentType = aLoadInfo->InternalContentPolicyType();
  nsCOMPtr<nsISupports> requestContext = aLoadInfo->GetLoadingContext();
  nsCOMPtr<nsIPrincipal> requestPrincipal = aLoadInfo->TriggeringPrincipal();
  nsCOMPtr<nsIURI> requestOrigin;
  nsCOMPtr<nsIPrincipal> loadingPrincipal = aLoadInfo->LoadingPrincipal();
  if (loadingPrincipal) {
    loadingPrincipal->GetURI(getter_AddRefs(requestOrigin));
  }

  nsCOMPtr<nsICSPEventListener> cspEventListener;
  nsresult rv =
      aLoadInfo->GetCspEventListener(getter_AddRefs(cspEventListener));
  NS_ENSURE_SUCCESS(rv, rv);

  if (MOZ_LOG_TEST(gCspPRLog, LogLevel::Debug)) {
    MOZ_LOG(gCspPRLog, LogLevel::Debug,
            ("CSPService::ShouldLoad called for %s",
             aContentLocation->GetSpecOrDefault().get()));
  }

  // default decision, CSP can revise it if there's a policy to enforce
  *aDecision = nsIContentPolicy::ACCEPT;

  // No need to continue processing if CSP is disabled or if the protocol
  // or type is *not* subject to CSP.
  // Please note, the correct way to opt-out of CSP using a custom
  // protocolHandler is to set one of the nsIProtocolHandler flags
  // that are whitelistet in subjectToCSP()
  if (!StaticPrefs::security_csp_enable() ||
      !subjectToCSP(aContentLocation, contentType)) {
    return NS_OK;
  }

  // Find a principal to retrieve the CSP from. If we don't have a context node
  // (because, for instance, the load originates in a service worker), or the
  // requesting principal's CSP overrides our document CSP, use the request
  // principal. Otherwise, use the document principal.
  nsCOMPtr<nsINode> node(do_QueryInterface(requestContext));
  nsCOMPtr<nsIPrincipal> principal;
  if (!node ||
      (requestPrincipal && BasePrincipal::Cast(requestPrincipal)
                               ->OverridesCSP(node->NodePrincipal()))) {
    principal = requestPrincipal;
  } else {
    principal = node->NodePrincipal();
  }
  if (!principal) {
    // if we can't query a principal, then there is nothing to do.
    return NS_OK;
  }

  // 1) Apply speculate CSP for preloads
  bool isPreload = nsContentUtils::IsPreloadType(contentType);

  if (isPreload) {
    nsCOMPtr<nsIContentSecurityPolicy> preloadCsp;
    rv = principal->GetPreloadCsp(getter_AddRefs(preloadCsp));
    NS_ENSURE_SUCCESS(rv, rv);

    if (preloadCsp) {
      // obtain the enforcement decision
      rv = preloadCsp->ShouldLoad(
          contentType, cspEventListener, aContentLocation, requestOrigin,
          requestContext, aMimeTypeGuess,
          nullptr,  // no redirect, aOriginal URL is null.
          aLoadInfo->GetSendCSPViolationEvents(), aDecision);
      NS_ENSURE_SUCCESS(rv, rv);

      // if the preload policy already denied the load, then there
      // is no point in checking the real policy
      if (NS_CP_REJECTED(*aDecision)) {
        return NS_OK;
      }
    }
  }

  // 2) Apply actual CSP to all loads
  nsCOMPtr<nsIContentSecurityPolicy> csp;
  rv = principal->GetCsp(getter_AddRefs(csp));
  NS_ENSURE_SUCCESS(rv, rv);

  if (csp) {
    // obtain the enforcement decision
    rv = csp->ShouldLoad(contentType, cspEventListener, aContentLocation,
                         requestOrigin, requestContext, aMimeTypeGuess,
                         nullptr,  // no redirect, aOriginal URL is null.
                         aLoadInfo->GetSendCSPViolationEvents(), aDecision);
    NS_ENSURE_SUCCESS(rv, rv);
  }
  return NS_OK;
}

NS_IMETHODIMP
CSPService::ShouldProcess(nsIURI *aContentLocation, nsILoadInfo *aLoadInfo,
                          const nsACString &aMimeTypeGuess,
                          int16_t *aDecision) {
  if (!aContentLocation) {
    return NS_ERROR_FAILURE;
  }
  uint32_t contentType = aLoadInfo->InternalContentPolicyType();

  if (MOZ_LOG_TEST(gCspPRLog, LogLevel::Debug)) {
    MOZ_LOG(gCspPRLog, LogLevel::Debug,
            ("CSPService::ShouldProcess called for %s",
             aContentLocation->GetSpecOrDefault().get()));
  }

  // ShouldProcess is only relevant to TYPE_OBJECT, so let's convert the
  // internal contentPolicyType to the mapping external one.
  // If it is not TYPE_OBJECT, we can return at this point.
  // Note that we should still pass the internal contentPolicyType
  // (contentType) to ShouldLoad().
  uint32_t policyType =
      nsContentUtils::InternalContentPolicyTypeToExternal(contentType);

  if (policyType != nsIContentPolicy::TYPE_OBJECT) {
    *aDecision = nsIContentPolicy::ACCEPT;
    return NS_OK;
  }

  return ShouldLoad(aContentLocation, aLoadInfo, aMimeTypeGuess, aDecision);
}

/* nsIChannelEventSink implementation */
NS_IMETHODIMP
CSPService::AsyncOnChannelRedirect(nsIChannel *oldChannel,
                                   nsIChannel *newChannel, uint32_t flags,
                                   nsIAsyncVerifyRedirectCallback *callback) {
  net::nsAsyncRedirectAutoCallback autoCallback(callback);

  if (XRE_IsE10sParentProcess()) {
    nsCOMPtr<nsIParentChannel> parentChannel;
    NS_QueryNotificationCallbacks(oldChannel, parentChannel);
    if (parentChannel) {
      // This is an IPC'd channel. Don't check it here, because we won't have
      // access to the request context; we'll check them in the content
      // process instead. Bug 1509738 covers fixing this.
      return NS_OK;
    }
  }

  nsCOMPtr<nsIURI> newUri;
  nsresult rv = newChannel->GetURI(getter_AddRefs(newUri));
  NS_ENSURE_SUCCESS(rv, rv);

  nsCOMPtr<nsILoadInfo> loadInfo = oldChannel->GetLoadInfo();

  // if no loadInfo on the channel, nothing for us to do
  if (!loadInfo) {
    return NS_OK;
  }

  nsCOMPtr<nsICSPEventListener> cspEventListener;
  rv = loadInfo->GetCspEventListener(getter_AddRefs(cspEventListener));
  NS_ENSURE_SUCCESS(rv, rv);

  // No need to continue processing if CSP is disabled or if the protocol
  // is *not* subject to CSP.
  // Please note, the correct way to opt-out of CSP using a custom
  // protocolHandler is to set one of the nsIProtocolHandler flags
  // that are whitelistet in subjectToCSP()
  nsContentPolicyType policyType = loadInfo->InternalContentPolicyType();
  if (!StaticPrefs::security_csp_enable() ||
      !subjectToCSP(newUri, policyType)) {
    return NS_OK;
  }

  /* Since redirecting channels don't call into nsIContentPolicy, we call our
   * Content Policy implementation directly when redirects occur using the
   * information set in the LoadInfo when channels are created.
   *
   * We check if the CSP permits this host for this type of load, if not,
   * we cancel the load now.
   */
  nsCOMPtr<nsIURI> originalUri;
  rv = oldChannel->GetOriginalURI(getter_AddRefs(originalUri));
  if (NS_FAILED(rv)) {
    autoCallback.DontCallback();
    oldChannel->Cancel(NS_ERROR_DOM_BAD_URI);
    return rv;
  }

  bool isPreload = nsContentUtils::IsPreloadType(policyType);

  /* On redirect, if the content policy is a preload type, rejecting the preload
   * results in the load silently failing, so we convert preloads to the actual
   * type. See Bug 1219453.
   */
  policyType =
      nsContentUtils::InternalContentPolicyTypeToExternalOrWorker(policyType);

  int16_t aDecision = nsIContentPolicy::ACCEPT;
  nsCOMPtr<nsISupports> requestContext = loadInfo->GetLoadingContext();
  // 1) Apply speculative CSP for preloads
  if (isPreload) {
    nsCOMPtr<nsIContentSecurityPolicy> preloadCsp;
    loadInfo->LoadingPrincipal()->GetPreloadCsp(getter_AddRefs(preloadCsp));

    if (preloadCsp) {
      // Pass  originalURI to indicate the redirect
      preloadCsp->ShouldLoad(
          policyType,  // load type per nsIContentPolicy (uint32_t)
          cspEventListener,
          newUri,          // nsIURI
          nullptr,         // nsIURI
          requestContext,  // nsISupports
          EmptyCString(),  // ACString - MIME guess
          originalUri,     // Original nsIURI
          true,            // aSendViolationReports
          &aDecision);

      // if the preload policy already denied the load, then there
      // is no point in checking the real policy
      if (NS_CP_REJECTED(aDecision)) {
        autoCallback.DontCallback();
        oldChannel->Cancel(NS_ERROR_DOM_BAD_URI);
        return NS_BINDING_FAILED;
      }
    }
  }

  // 2) Apply actual CSP to all loads
  nsCOMPtr<nsIContentSecurityPolicy> csp;
  loadInfo->LoadingPrincipal()->GetCsp(getter_AddRefs(csp));

  if (csp) {
    // Pass  originalURI to indicate the redirect
    csp->ShouldLoad(policyType,  // load type per nsIContentPolicy (uint32_t)
                    cspEventListener,
                    newUri,          // nsIURI
                    nullptr,         // nsIURI
                    requestContext,  // nsISupports
                    EmptyCString(),  // ACString - MIME guess
                    originalUri,     // Original nsIURI
                    true,            // aSendViolationReports
                    &aDecision);
  }

  // if ShouldLoad doesn't accept the load, cancel the request
  if (!NS_CP_ACCEPTED(aDecision)) {
    autoCallback.DontCallback();
    oldChannel->Cancel(NS_ERROR_DOM_BAD_URI);
    return NS_BINDING_FAILED;
  }
  return NS_OK;
}