зеркало из https://github.com/mozilla/gecko-dev.git
310 строки
10 KiB
C++
310 строки
10 KiB
C++
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* 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 "prlog.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 "mozilla/Preferences.h"
|
|
#include "nsIScriptError.h"
|
|
#include "nsContentUtils.h"
|
|
#include "nsContentPolicyUtils.h"
|
|
#include "nsPrincipal.h"
|
|
|
|
using namespace mozilla;
|
|
|
|
/* Keeps track of whether or not CSP is enabled */
|
|
bool CSPService::sCSPEnabled = true;
|
|
|
|
#ifdef PR_LOGGING
|
|
static PRLogModuleInfo* gCspPRLog;
|
|
#endif
|
|
|
|
CSPService::CSPService()
|
|
{
|
|
Preferences::AddBoolVarCache(&sCSPEnabled, "security.csp.enable");
|
|
|
|
#ifdef PR_LOGGING
|
|
if (!gCspPRLog)
|
|
gCspPRLog = PR_NewLogModule("CSP");
|
|
#endif
|
|
}
|
|
|
|
CSPService::~CSPService()
|
|
{
|
|
mAppStatusCache.Clear();
|
|
}
|
|
|
|
NS_IMPL_ISUPPORTS(CSPService, nsIContentPolicy, nsIChannelEventSink)
|
|
|
|
/* nsIContentPolicy implementation */
|
|
NS_IMETHODIMP
|
|
CSPService::ShouldLoad(uint32_t aContentType,
|
|
nsIURI *aContentLocation,
|
|
nsIURI *aRequestOrigin,
|
|
nsISupports *aRequestContext,
|
|
const nsACString &aMimeTypeGuess,
|
|
nsISupports *aExtra,
|
|
nsIPrincipal *aRequestPrincipal,
|
|
int16_t *aDecision)
|
|
{
|
|
if (!aContentLocation)
|
|
return NS_ERROR_FAILURE;
|
|
|
|
#ifdef PR_LOGGING
|
|
{
|
|
nsAutoCString location;
|
|
aContentLocation->GetSpec(location);
|
|
PR_LOG(gCspPRLog, PR_LOG_DEBUG,
|
|
("CSPService::ShouldLoad called for %s", location.get()));
|
|
}
|
|
#endif
|
|
|
|
// 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
|
|
if (!sCSPEnabled)
|
|
return NS_OK;
|
|
|
|
// shortcut for about: chrome: and resource: and javascript: uris since
|
|
// they're not subject to CSP content policy checks.
|
|
bool schemeMatch = false;
|
|
NS_ENSURE_SUCCESS(aContentLocation->SchemeIs("about", &schemeMatch), NS_OK);
|
|
if (schemeMatch)
|
|
return NS_OK;
|
|
NS_ENSURE_SUCCESS(aContentLocation->SchemeIs("chrome", &schemeMatch), NS_OK);
|
|
if (schemeMatch)
|
|
return NS_OK;
|
|
NS_ENSURE_SUCCESS(aContentLocation->SchemeIs("resource", &schemeMatch), NS_OK);
|
|
if (schemeMatch)
|
|
return NS_OK;
|
|
NS_ENSURE_SUCCESS(aContentLocation->SchemeIs("javascript", &schemeMatch), NS_OK);
|
|
if (schemeMatch)
|
|
return NS_OK;
|
|
|
|
|
|
// 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 (aContentType == nsIContentPolicy::TYPE_CSP_REPORT ||
|
|
aContentType == nsIContentPolicy::TYPE_REFRESH ||
|
|
aContentType == nsIContentPolicy::TYPE_DOCUMENT) {
|
|
return NS_OK;
|
|
}
|
|
|
|
// ----- THIS IS A TEMPORARY FAST PATH FOR CERTIFIED APPS. -----
|
|
// ----- PLEASE REMOVE ONCE bug 925004 LANDS. -----
|
|
|
|
// Cache the app status for this origin.
|
|
uint16_t status = nsIPrincipal::APP_STATUS_NOT_INSTALLED;
|
|
nsAutoCString contentOrigin;
|
|
aContentLocation->GetPrePath(contentOrigin);
|
|
if (aRequestPrincipal && !mAppStatusCache.Get(contentOrigin, &status)) {
|
|
aRequestPrincipal->GetAppStatus(&status);
|
|
mAppStatusCache.Put(contentOrigin, status);
|
|
}
|
|
|
|
if (status == nsIPrincipal::APP_STATUS_CERTIFIED) {
|
|
// The CSP for certified apps is :
|
|
// "default-src *; script-src 'self'; object-src 'none'; style-src 'self' app://theme.gaiamobile.org:*"
|
|
// That means we can optimize for this case by:
|
|
// - loading same origin scripts and stylesheets, and stylesheets from the
|
|
// theme url space.
|
|
// - never loading objects.
|
|
// - accepting everything else.
|
|
|
|
switch (aContentType) {
|
|
case nsIContentPolicy::TYPE_SCRIPT:
|
|
case nsIContentPolicy::TYPE_STYLESHEET:
|
|
{
|
|
// Whitelist the theme resources.
|
|
auto themeOrigin = Preferences::GetCString("b2g.theme.origin");
|
|
nsAutoCString sourceOrigin;
|
|
aRequestOrigin->GetPrePath(sourceOrigin);
|
|
|
|
if (!(sourceOrigin.Equals(contentOrigin) ||
|
|
(themeOrigin && themeOrigin.Equals(contentOrigin)))) {
|
|
*aDecision = nsIContentPolicy::REJECT_SERVER;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case nsIContentPolicy::TYPE_OBJECT:
|
|
*aDecision = nsIContentPolicy::REJECT_SERVER;
|
|
break;
|
|
|
|
default:
|
|
*aDecision = nsIContentPolicy::ACCEPT;
|
|
}
|
|
|
|
// Only cache and return if we are successful. If not, we want the error
|
|
// to be reported, and thus fallback to the slow path.
|
|
if (*aDecision == nsIContentPolicy::ACCEPT) {
|
|
return NS_OK;
|
|
}
|
|
}
|
|
|
|
// ----- END OF TEMPORARY FAST PATH FOR CERTIFIED APPS. -----
|
|
|
|
// find the principal of the document that initiated this request and see
|
|
// if it has a CSP policy object
|
|
nsCOMPtr<nsINode> node(do_QueryInterface(aRequestContext));
|
|
nsCOMPtr<nsIPrincipal> principal;
|
|
nsCOMPtr<nsIContentSecurityPolicy> csp;
|
|
if (node) {
|
|
principal = node->NodePrincipal();
|
|
principal->GetCsp(getter_AddRefs(csp));
|
|
|
|
if (csp) {
|
|
#ifdef PR_LOGGING
|
|
{
|
|
uint32_t numPolicies = 0;
|
|
nsresult rv = csp->GetPolicyCount(&numPolicies);
|
|
if (NS_SUCCEEDED(rv)) {
|
|
for (uint32_t i=0; i<numPolicies; i++) {
|
|
nsAutoString policy;
|
|
csp->GetPolicy(i, policy);
|
|
PR_LOG(gCspPRLog, PR_LOG_DEBUG,
|
|
("Document has CSP[%d]: %s", i,
|
|
NS_ConvertUTF16toUTF8(policy).get()));
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
// obtain the enforcement decision
|
|
// (don't pass aExtra, we use that slot for redirects)
|
|
csp->ShouldLoad(aContentType,
|
|
aContentLocation,
|
|
aRequestOrigin,
|
|
aRequestContext,
|
|
aMimeTypeGuess,
|
|
nullptr,
|
|
aDecision);
|
|
}
|
|
}
|
|
#ifdef PR_LOGGING
|
|
else {
|
|
nsAutoCString uriSpec;
|
|
aContentLocation->GetSpec(uriSpec);
|
|
PR_LOG(gCspPRLog, PR_LOG_DEBUG,
|
|
("COULD NOT get nsINode for location: %s", uriSpec.get()));
|
|
}
|
|
#endif
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
CSPService::ShouldProcess(uint32_t aContentType,
|
|
nsIURI *aContentLocation,
|
|
nsIURI *aRequestOrigin,
|
|
nsISupports *aRequestContext,
|
|
const nsACString &aMimeTypeGuess,
|
|
nsISupports *aExtra,
|
|
nsIPrincipal *aRequestPrincipal,
|
|
int16_t *aDecision)
|
|
{
|
|
if (!aContentLocation)
|
|
return NS_ERROR_FAILURE;
|
|
|
|
*aDecision = nsIContentPolicy::ACCEPT;
|
|
return NS_OK;
|
|
}
|
|
|
|
/* nsIChannelEventSink implementation */
|
|
NS_IMETHODIMP
|
|
CSPService::AsyncOnChannelRedirect(nsIChannel *oldChannel,
|
|
nsIChannel *newChannel,
|
|
uint32_t flags,
|
|
nsIAsyncVerifyRedirectCallback *callback)
|
|
{
|
|
nsAsyncRedirectAutoCallback autoCallback(callback);
|
|
|
|
nsCOMPtr<nsILoadInfo> loadInfo;
|
|
nsresult rv = oldChannel->GetLoadInfo(getter_AddRefs(loadInfo));
|
|
|
|
// if no loadInfo on the channel, nothing for us to do
|
|
if (!loadInfo) {
|
|
return NS_OK;
|
|
}
|
|
|
|
// The loadInfo must not necessarily contain a Node, hence we try to query
|
|
// the CSP in the following order:
|
|
// a) Get the Node, the Principal of that Node, and the CSP of that Principal
|
|
// b) Get the Principal and the CSP of that Principal
|
|
|
|
nsCOMPtr<nsINode> loadingNode = loadInfo->LoadingNode();
|
|
nsCOMPtr<nsIPrincipal> principal = loadingNode ?
|
|
loadingNode->NodePrincipal() :
|
|
loadInfo->LoadingPrincipal();
|
|
NS_ASSERTION(principal, "Can not evaluate CSP without a principal");
|
|
nsCOMPtr<nsIContentSecurityPolicy> csp;
|
|
rv = principal->GetCsp(getter_AddRefs(csp));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
// if there is no CSP, nothing for us to do
|
|
if (!csp) {
|
|
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> newUri;
|
|
rv = newChannel->GetURI(getter_AddRefs(newUri));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
nsCOMPtr<nsIURI> originalUri;
|
|
rv = oldChannel->GetOriginalURI(getter_AddRefs(originalUri));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
nsContentPolicyType policyType = loadInfo->GetContentPolicyType();
|
|
|
|
int16_t aDecision = nsIContentPolicy::ACCEPT;
|
|
csp->ShouldLoad(policyType, // load type per nsIContentPolicy (uint32_t)
|
|
newUri, // nsIURI
|
|
nullptr, // nsIURI
|
|
nullptr, // nsISupports
|
|
EmptyCString(), // ACString - MIME guess
|
|
originalUri, // aMimeTypeGuess
|
|
&aDecision);
|
|
|
|
#ifdef PR_LOGGING
|
|
if (newUri) {
|
|
nsAutoCString newUriSpec("None");
|
|
newUri->GetSpec(newUriSpec);
|
|
PR_LOG(gCspPRLog, PR_LOG_DEBUG,
|
|
("CSPService::AsyncOnChannelRedirect called for %s",
|
|
newUriSpec.get()));
|
|
}
|
|
if (aDecision == 1)
|
|
PR_LOG(gCspPRLog, PR_LOG_DEBUG,
|
|
("CSPService::AsyncOnChannelRedirect ALLOWING request."));
|
|
else
|
|
PR_LOG(gCspPRLog, PR_LOG_DEBUG,
|
|
("CSPService::AsyncOnChannelRedirect CANCELLING request."));
|
|
#endif
|
|
|
|
// if ShouldLoad doesn't accept the load, cancel the request
|
|
if (!NS_CP_ACCEPTED(aDecision)) {
|
|
autoCallback.DontCallback();
|
|
return NS_BINDING_FAILED;
|
|
}
|
|
return NS_OK;
|
|
}
|