Source code

Revision control

Copy as Markdown

Other Tools

/* -*- 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 "nsScriptSecurityManager.h"
#include "mozilla/ArrayUtils.h"
#include "mozilla/SourceLocation.h"
#include "mozilla/StaticPrefs_extensions.h"
#include "mozilla/StaticPrefs_security.h"
#include "mozilla/StoragePrincipalHelper.h"
#include "xpcpublic.h"
#include "XPCWrapper.h"
#include "nsILoadContext.h"
#include "nsIScriptObjectPrincipal.h"
#include "nsIScriptContext.h"
#include "nsIScriptError.h"
#include "nsINestedURI.h"
#include "nspr.h"
#include "nsJSPrincipals.h"
#include "mozilla/BasePrincipal.h"
#include "mozilla/ContentPrincipal.h"
#include "ExpandedPrincipal.h"
#include "SystemPrincipal.h"
#include "DomainPolicy.h"
#include "nsString.h"
#include "nsCRT.h"
#include "nsCRTGlue.h"
#include "nsContentSecurityUtils.h"
#include "nsDocShell.h"
#include "nsError.h"
#include "nsGlobalWindowInner.h"
#include "nsDOMCID.h"
#include "nsTextFormatter.h"
#include "nsIStringBundle.h"
#include "nsNetUtil.h"
#include "nsIEffectiveTLDService.h"
#include "nsDirectoryServiceDefs.h"
#include "nsIScriptGlobalObject.h"
#include "nsPIDOMWindow.h"
#include "nsIDocShell.h"
#include "nsIConsoleService.h"
#include "nsIOService.h"
#include "nsIContent.h"
#include "nsDOMJSUtils.h"
#include "nsAboutProtocolUtils.h"
#include "nsIClassInfo.h"
#include "nsIURIFixup.h"
#include "nsIURIMutator.h"
#include "nsIChromeRegistry.h"
#include "nsIResProtocolHandler.h"
#include "nsIContentSecurityPolicy.h"
#include "mozilla/Components.h"
#include "mozilla/Preferences.h"
#include "mozilla/dom/BindingUtils.h"
#include "mozilla/NullPrincipal.h"
#include <stdint.h>
#include "mozilla/dom/ContentChild.h"
#include "mozilla/dom/ContentParent.h"
#include "mozilla/dom/Exceptions.h"
#include "mozilla/dom/nsCSPContext.h"
#include "mozilla/dom/ScriptSettings.h"
#include "mozilla/ClearOnShutdown.h"
#include "mozilla/ExtensionPolicyService.h"
#include "mozilla/ResultExtensions.h"
#include "mozilla/StaticPtr.h"
#include "mozilla/dom/TrustedTypeUtils.h"
#include "mozilla/dom/WorkerCommon.h"
#include "mozilla/dom/WorkerPrivate.h"
#include "nsContentUtils.h"
#include "nsJSUtils.h"
#include "nsILoadInfo.h"
#include "js/ColumnNumber.h" // JS::ColumnNumberOneOrigin
#include "js/GCVector.h"
#include "js/Value.h"
// This should be probably defined on some other place... but I couldn't find it
#define WEBAPPS_PERM_NAME "webapps-manage"
using namespace mozilla;
using namespace mozilla::dom;
StaticRefPtr<nsIIOService> nsScriptSecurityManager::sIOService;
std::atomic<bool> nsScriptSecurityManager::sStrictFileOriginPolicy = true;
namespace {
class BundleHelper {
public:
NS_INLINE_DECL_REFCOUNTING(BundleHelper)
static nsIStringBundle* GetOrCreate() {
MOZ_ASSERT(!sShutdown);
// Already shutting down. Nothing should require the use of the string
// bundle when shutting down.
if (sShutdown) {
return nullptr;
}
if (!sSelf) {
sSelf = new BundleHelper();
}
return sSelf->GetOrCreateInternal();
}
static void Shutdown() {
sSelf = nullptr;
sShutdown = true;
}
private:
~BundleHelper() = default;
nsIStringBundle* GetOrCreateInternal() {
if (!mBundle) {
nsCOMPtr<nsIStringBundleService> bundleService =
mozilla::components::StringBundle::Service();
if (NS_WARN_IF(!bundleService)) {
return nullptr;
}
nsresult rv = bundleService->CreateBundle(
"chrome://global/locale/security/caps.properties",
getter_AddRefs(mBundle));
if (NS_WARN_IF(NS_FAILED(rv))) {
return nullptr;
}
}
return mBundle;
}
nsCOMPtr<nsIStringBundle> mBundle;
static StaticRefPtr<BundleHelper> sSelf;
static bool sShutdown;
};
StaticRefPtr<BundleHelper> BundleHelper::sSelf;
bool BundleHelper::sShutdown = false;
} // namespace
///////////////////////////
// Convenience Functions //
///////////////////////////
class nsAutoInPrincipalDomainOriginSetter {
public:
nsAutoInPrincipalDomainOriginSetter() { ++sInPrincipalDomainOrigin; }
~nsAutoInPrincipalDomainOriginSetter() { --sInPrincipalDomainOrigin; }
static uint32_t sInPrincipalDomainOrigin;
};
uint32_t nsAutoInPrincipalDomainOriginSetter::sInPrincipalDomainOrigin;
static nsresult GetOriginFromURI(nsIURI* aURI, nsACString& aOrigin) {
if (!aURI) {
return NS_ERROR_NULL_POINTER;
}
if (nsAutoInPrincipalDomainOriginSetter::sInPrincipalDomainOrigin > 1) {
// Allow a single recursive call to GetPrincipalDomainOrigin, since that
// might be happening on a different principal from the first call. But
// after that, cut off the recursion; it just indicates that something
// we're doing in this method causes us to reenter a security check here.
return NS_ERROR_NOT_AVAILABLE;
}
nsAutoInPrincipalDomainOriginSetter autoSetter;
nsCOMPtr<nsIURI> uri = NS_GetInnermostURI(aURI);
NS_ENSURE_TRUE(uri, NS_ERROR_UNEXPECTED);
nsAutoCString hostPort;
nsresult rv = uri->GetHostPort(hostPort);
if (NS_SUCCEEDED(rv)) {
nsAutoCString scheme;
rv = uri->GetScheme(scheme);
NS_ENSURE_SUCCESS(rv, rv);
aOrigin = scheme + "://"_ns + hostPort;
} else {
// Some URIs (e.g., nsSimpleURI) don't support host. Just
// get the full spec.
rv = uri->GetSpec(aOrigin);
NS_ENSURE_SUCCESS(rv, rv);
}
return NS_OK;
}
static nsresult GetPrincipalDomainOrigin(nsIPrincipal* aPrincipal,
nsACString& aOrigin) {
aOrigin.Truncate();
nsCOMPtr<nsIURI> uri;
aPrincipal->GetDomain(getter_AddRefs(uri));
nsresult rv = GetOriginFromURI(uri, aOrigin);
if (NS_SUCCEEDED(rv)) {
return rv;
}
// If there is no Domain fallback to the Principals Origin
return aPrincipal->GetOriginNoSuffix(aOrigin);
}
inline void SetPendingExceptionASCII(JSContext* cx, const char* aMsg) {
JS_ReportErrorASCII(cx, "%s", aMsg);
}
inline void SetPendingException(JSContext* cx, const char16_t* aMsg) {
NS_ConvertUTF16toUTF8 msg(aMsg);
JS_ReportErrorUTF8(cx, "%s", msg.get());
}
/* static */
bool nsScriptSecurityManager::SecurityCompareURIs(nsIURI* aSourceURI,
nsIURI* aTargetURI) {
return NS_SecurityCompareURIs(aSourceURI, aTargetURI,
sStrictFileOriginPolicy);
}
// SecurityHashURI is consistent with SecurityCompareURIs because
// NS_SecurityHashURI is consistent with NS_SecurityCompareURIs. See
// nsNetUtil.h.
uint32_t nsScriptSecurityManager::SecurityHashURI(nsIURI* aURI) {
return NS_SecurityHashURI(aURI);
}
bool nsScriptSecurityManager::IsHttpOrHttpsAndCrossOrigin(nsIURI* aUriA,
nsIURI* aUriB) {
if (!aUriA || (!net::SchemeIsHTTP(aUriA) && !net::SchemeIsHTTPS(aUriA)) ||
!aUriB || (!net::SchemeIsHTTP(aUriB) && !net::SchemeIsHTTPS(aUriB))) {
return false;
}
if (!SecurityCompareURIs(aUriA, aUriB)) {
return true;
}
return false;
}
/*
* GetChannelResultPrincipal will return the principal that the resource
* returned by this channel will use. For example, if the resource is in
* a sandbox, it will return the nullprincipal. If the resource is forced
* to inherit principal, it will return the principal of its parent. If
* the load doesn't require sandboxing or inheriting, it will return the same
* principal as GetChannelURIPrincipal. Namely the principal of the URI
* that is being loaded.
*/
NS_IMETHODIMP
nsScriptSecurityManager::GetChannelResultPrincipal(nsIChannel* aChannel,
nsIPrincipal** aPrincipal) {
return GetChannelResultPrincipal(aChannel, aPrincipal,
/*aIgnoreSandboxing*/ false);
}
nsresult nsScriptSecurityManager::GetChannelResultPrincipalIfNotSandboxed(
nsIChannel* aChannel, nsIPrincipal** aPrincipal) {
return GetChannelResultPrincipal(aChannel, aPrincipal,
/*aIgnoreSandboxing*/ true);
}
NS_IMETHODIMP
nsScriptSecurityManager::GetChannelResultStoragePrincipal(
nsIChannel* aChannel, nsIPrincipal** aPrincipal) {
nsCOMPtr<nsIPrincipal> principal;
nsresult rv = GetChannelResultPrincipal(aChannel, getter_AddRefs(principal),
/*aIgnoreSandboxing*/ false);
if (NS_WARN_IF(NS_FAILED(rv) || !principal)) {
return rv;
}
if (!(principal->GetIsContentPrincipal())) {
// If for some reason we don't have a content principal here, just reuse our
// principal for the storage principal too, since attempting to create a
// storage principal would fail anyway.
principal.forget(aPrincipal);
return NS_OK;
}
return StoragePrincipalHelper::Create(
aChannel, principal, /* aForceIsolation */ false, aPrincipal);
}
NS_IMETHODIMP
nsScriptSecurityManager::GetChannelResultPrincipals(
nsIChannel* aChannel, nsIPrincipal** aPrincipal,
nsIPrincipal** aPartitionedPrincipal) {
nsresult rv = GetChannelResultPrincipal(aChannel, aPrincipal,
/*aIgnoreSandboxing*/ false);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
if (!(*aPrincipal)->GetIsContentPrincipal()) {
// If for some reason we don't have a content principal here, just reuse our
// principal for the storage principal too, since attempting to create a
// storage principal would fail anyway.
nsCOMPtr<nsIPrincipal> copy = *aPrincipal;
copy.forget(aPartitionedPrincipal);
return NS_OK;
}
return StoragePrincipalHelper::Create(
aChannel, *aPrincipal, /* aForceIsolation */ true, aPartitionedPrincipal);
}
nsresult nsScriptSecurityManager::GetChannelResultPrincipal(
nsIChannel* aChannel, nsIPrincipal** aPrincipal, bool aIgnoreSandboxing) {
MOZ_ASSERT(aChannel, "Must have channel!");
// Check whether we have an nsILoadInfo that says what we should do.
nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
if (loadInfo->GetForceInheritPrincipalOverruleOwner()) {
nsCOMPtr<nsIPrincipal> principalToInherit =
loadInfo->FindPrincipalToInherit(aChannel);
principalToInherit.forget(aPrincipal);
return NS_OK;
}
nsCOMPtr<nsISupports> owner;
aChannel->GetOwner(getter_AddRefs(owner));
if (owner) {
CallQueryInterface(owner, aPrincipal);
if (*aPrincipal) {
return NS_OK;
}
}
if (!aIgnoreSandboxing && loadInfo->GetLoadingSandboxed()) {
// Determine the unsandboxed result principal to use as this null
// principal's precursor. Ignore errors here, as the precursor isn't
// required.
nsCOMPtr<nsIPrincipal> precursor;
GetChannelResultPrincipal(aChannel, getter_AddRefs(precursor),
/*aIgnoreSandboxing*/ true);
// Construct a deterministic null principal URI from the precursor and the
// loadinfo's nullPrincipalID.
nsCOMPtr<nsIURI> nullPrincipalURI = NullPrincipal::CreateURI(
precursor, &loadInfo->GetSandboxedNullPrincipalID());
// Use the URI to construct the sandboxed result principal.
OriginAttributes attrs;
loadInfo->GetOriginAttributes(&attrs);
nsCOMPtr<nsIPrincipal> sandboxedPrincipal =
NullPrincipal::Create(attrs, nullPrincipalURI);
sandboxedPrincipal.forget(aPrincipal);
return NS_OK;
}
bool forceInherit = loadInfo->GetForceInheritPrincipal();
if (aIgnoreSandboxing && !forceInherit) {
// Check if SEC_FORCE_INHERIT_PRINCIPAL was dropped because of
// sandboxing:
if (loadInfo->GetLoadingSandboxed() &&
loadInfo->GetForceInheritPrincipalDropped()) {
forceInherit = true;
}
}
if (forceInherit) {
nsCOMPtr<nsIPrincipal> principalToInherit =
loadInfo->FindPrincipalToInherit(aChannel);
principalToInherit.forget(aPrincipal);
return NS_OK;
}
auto securityMode = loadInfo->GetSecurityMode();
// The data: inheritance flags should only apply to the initial load,
// not to loads that it might have redirected to.
if (loadInfo->RedirectChain().IsEmpty() &&
(securityMode ==
nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_INHERITS_SEC_CONTEXT ||
securityMode ==
nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_INHERITS_SEC_CONTEXT ||
securityMode == nsILoadInfo::SEC_REQUIRE_CORS_INHERITS_SEC_CONTEXT)) {
nsCOMPtr<nsIURI> uri;
nsresult rv = NS_GetFinalChannelURI(aChannel, getter_AddRefs(uri));
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIPrincipal> principalToInherit =
loadInfo->FindPrincipalToInherit(aChannel);
bool inheritForAboutBlank = loadInfo->GetAboutBlankInherits();
if (nsContentUtils::ChannelShouldInheritPrincipal(
principalToInherit, uri, inheritForAboutBlank, false)) {
principalToInherit.forget(aPrincipal);
return NS_OK;
}
}
return GetChannelURIPrincipal(aChannel, aPrincipal);
}
/* The principal of the URI that this channel is loading. This is never
* affected by things like sandboxed loads, or loads where we forcefully
* inherit the principal. Think of this as the principal of the server
* which this channel is loading from. Most callers should use
* GetChannelResultPrincipal instead of GetChannelURIPrincipal. Only
* call GetChannelURIPrincipal if you are sure that you want the
* principal that matches the uri, even in cases when the load is
* sandboxed or when the load could be a blob or data uri (i.e even when
* you encounter loads that may or may not be sandboxed and loads
* that may or may not inherit)."
*/
NS_IMETHODIMP
nsScriptSecurityManager::GetChannelURIPrincipal(nsIChannel* aChannel,
nsIPrincipal** aPrincipal) {
MOZ_ASSERT(aChannel, "Must have channel!");
// Get the principal from the URI. Make sure this does the same thing
// as Document::Reset and PrototypeDocumentContentSink::Init.
nsCOMPtr<nsIURI> uri;
nsresult rv = NS_GetFinalChannelURI(aChannel, getter_AddRefs(uri));
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
// Inherit the origin attributes from loadInfo.
// If this is a top-level document load, the origin attributes of the
// loadInfo will be set from nsDocShell::DoURILoad.
// For subresource loading, the origin attributes of the loadInfo is from
// its loadingPrincipal.
OriginAttributes attrs = loadInfo->GetOriginAttributes();
// If the URI is supposed to inherit the security context of whoever loads it,
// we shouldn't make a content principal for it, so instead return a null
// principal.
bool inheritsPrincipal = false;
rv = NS_URIChainHasFlags(uri,
nsIProtocolHandler::URI_INHERITS_SECURITY_CONTEXT,
&inheritsPrincipal);
if (NS_FAILED(rv) || inheritsPrincipal) {
// Find a precursor principal to credit for the load. This won't impact
// security checks, but makes tracking the source of related loads easier.
nsCOMPtr<nsIPrincipal> precursorPrincipal =
loadInfo->FindPrincipalToInherit(aChannel);
nsCOMPtr<nsIURI> nullPrincipalURI =
NullPrincipal::CreateURI(precursorPrincipal);
*aPrincipal = NullPrincipal::Create(attrs, nullPrincipalURI).take();
return *aPrincipal ? NS_OK : NS_ERROR_FAILURE;
}
nsCOMPtr<nsIPrincipal> prin =
BasePrincipal::CreateContentPrincipal(uri, attrs);
prin.forget(aPrincipal);
return *aPrincipal ? NS_OK : NS_ERROR_FAILURE;
}
/////////////////////////////
// nsScriptSecurityManager //
/////////////////////////////
////////////////////////////////////
// Methods implementing ISupports //
////////////////////////////////////
NS_IMPL_ISUPPORTS(nsScriptSecurityManager, nsIScriptSecurityManager)
///////////////////////////////////////////////////
// Methods implementing nsIScriptSecurityManager //
///////////////////////////////////////////////////
///////////////// Security Checks /////////////////
bool nsScriptSecurityManager::ContentSecurityPolicyPermitsJSAction(
JSContext* cx, JS::RuntimeCode aKind, JS::Handle<JSString*> aCodeString,
JS::CompilationType aCompilationType,
JS::Handle<JS::StackGCVector<JSString*>> aParameterStrings,
JS::Handle<JSString*> aBodyString,
JS::Handle<JS::StackGCVector<JS::Value>> aParameterArgs,
JS::Handle<JS::Value> aBodyArg, bool* aOutCanCompileStrings) {
MOZ_ASSERT(cx == nsContentUtils::GetCurrentJSContext());
nsCOMPtr<nsIPrincipal> subjectPrincipal = nsContentUtils::SubjectPrincipal();
if (aKind == JS::RuntimeCode::JS) {
ErrorResult error;
bool areArgumentsTrusted = TrustedTypeUtils::
AreArgumentsTrustedForEnsureCSPDoesNotBlockStringCompilation(
cx, aCodeString, aCompilationType, aParameterStrings, aBodyString,
aParameterArgs, aBodyArg, error);
if (error.MaybeSetPendingException(cx)) {
return false;
}
if (!areArgumentsTrusted) {
*aOutCanCompileStrings = false;
return true;
}
}
// Check if Eval is allowed per firefox hardening policy
bool contextForbidsEval =
(subjectPrincipal->IsSystemPrincipal() || XRE_IsE10sParentProcess());
#if defined(ANDROID)
contextForbidsEval = false;
#endif
if (contextForbidsEval) {
nsAutoJSString scriptSample;
if (aKind == JS::RuntimeCode::JS &&
NS_WARN_IF(!scriptSample.init(cx, aCodeString))) {
return false;
}
if (!nsContentSecurityUtils::IsEvalAllowed(
cx, subjectPrincipal->IsSystemPrincipal(), scriptSample)) {
*aOutCanCompileStrings = false;
return true;
}
}
// Get the window, if any, corresponding to the current global
nsCOMPtr<nsIContentSecurityPolicy> csp;
if (nsGlobalWindowInner* win = xpc::CurrentWindowOrNull(cx)) {
csp = win->GetCsp();
}
if (!csp) {
// Get the CSP for addon sandboxes. If the principal is expanded and has a
// csp, we're probably in luck.
auto* basePrin = BasePrincipal::Cast(subjectPrincipal);
// TODO bug 1548468: Move CSP off ExpandedPrincipal.
if (basePrin->Is<ExpandedPrincipal>()) {
basePrin->As<ExpandedPrincipal>()->GetCsp(getter_AddRefs(csp));
}
// don't do anything unless there's a CSP
if (!csp) {
*aOutCanCompileStrings = true;
return true;
}
}
nsCOMPtr<nsICSPEventListener> cspEventListener;
if (!NS_IsMainThread()) {
WorkerPrivate* workerPrivate =
mozilla::dom::GetWorkerPrivateFromContext(cx);
if (workerPrivate) {
cspEventListener = workerPrivate->CSPEventListener();
}
}
bool evalOK = true;
bool reportViolation = false;
if (aKind == JS::RuntimeCode::JS) {
nsresult rv = csp->GetAllowsEval(&reportViolation, &evalOK);
if (NS_FAILED(rv)) {
NS_WARNING("CSP: failed to get allowsEval");
*aOutCanCompileStrings = true; // fail open to not break sites.
return true;
}
} else {
if (NS_FAILED(csp->GetAllowsWasmEval(&reportViolation, &evalOK))) {
return false;
}
if (!evalOK) {
// Historically, CSP did not block WebAssembly in Firefox, and some
// add-ons use wasm and a stricter CSP. To avoid breaking them, ignore
// 'wasm-unsafe-eval' violations for MV2 extensions.
// TODO bug 1770909: remove this exception.
auto* addonPolicy = BasePrincipal::Cast(subjectPrincipal)->AddonPolicy();
if (addonPolicy && addonPolicy->ManifestVersion() == 2) {
reportViolation = true;
evalOK = true;
}
}
}
if (reportViolation) {
auto caller = JSCallingLocation::Get(cx);
nsAutoJSString scriptSample;
if (aKind == JS::RuntimeCode::JS &&
NS_WARN_IF(!scriptSample.init(cx, aCodeString))) {
return false;
}
uint16_t violationType =
aKind == JS::RuntimeCode::JS
? nsIContentSecurityPolicy::VIOLATION_TYPE_EVAL
: nsIContentSecurityPolicy::VIOLATION_TYPE_WASM_EVAL;
csp->LogViolationDetails(violationType,
nullptr, // triggering element
cspEventListener, caller.FileName(), scriptSample,
caller.mLine, caller.mColumn, u""_ns, u""_ns);
}
*aOutCanCompileStrings = evalOK;
return true;
}
// static
bool nsScriptSecurityManager::JSPrincipalsSubsume(JSPrincipals* first,
JSPrincipals* second) {
return nsJSPrincipals::get(first)->Subsumes(nsJSPrincipals::get(second));
}
NS_IMETHODIMP
nsScriptSecurityManager::CheckSameOriginURI(nsIURI* aSourceURI,
nsIURI* aTargetURI,
bool reportError,
bool aFromPrivateWindow) {
// Please note that aFromPrivateWindow is only 100% accurate if
// reportError is true.
if (!SecurityCompareURIs(aSourceURI, aTargetURI)) {
if (reportError) {
ReportError("CheckSameOriginError", aSourceURI, aTargetURI,
aFromPrivateWindow);
}
return NS_ERROR_DOM_BAD_URI;
}
return NS_OK;
}
NS_IMETHODIMP
nsScriptSecurityManager::CheckLoadURIFromScript(JSContext* cx, nsIURI* aURI) {
// Get principal of currently executing script.
MOZ_ASSERT(cx == nsContentUtils::GetCurrentJSContext());
nsIPrincipal* principal = nsContentUtils::SubjectPrincipal();
nsresult rv = CheckLoadURIWithPrincipal(
// Passing 0 for the window ID here is OK, because we will report a
// script-visible exception anyway.
principal, aURI, nsIScriptSecurityManager::STANDARD, 0);
if (NS_SUCCEEDED(rv)) {
// OK to load
return NS_OK;
}
// Report error.
nsAutoCString spec;
if (NS_FAILED(aURI->GetAsciiSpec(spec))) return NS_ERROR_FAILURE;
nsAutoCString msg("Access to '");
msg.Append(spec);
msg.AppendLiteral("' from script denied");
SetPendingExceptionASCII(cx, msg.get());
return NS_ERROR_DOM_BAD_URI;
}
/**
* Helper method to handle cases where a flag passed to
* CheckLoadURIWithPrincipal means denying loading if the given URI has certain
* nsIProtocolHandler flags set.
* @return if success, access is allowed. Otherwise, deny access
*/
static nsresult DenyAccessIfURIHasFlags(nsIURI* aURI, uint32_t aURIFlags) {
MOZ_ASSERT(aURI, "Must have URI!");
bool uriHasFlags;
nsresult rv = NS_URIChainHasFlags(aURI, aURIFlags, &uriHasFlags);
NS_ENSURE_SUCCESS(rv, rv);
if (uriHasFlags) {
return NS_ERROR_DOM_BAD_URI;
}
return NS_OK;
}
static bool EqualOrSubdomain(nsIURI* aProbeArg, nsIURI* aBase) {
nsresult rv;
nsCOMPtr<nsIURI> probe = aProbeArg;
nsCOMPtr<nsIEffectiveTLDService> tldService =
do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID);
NS_ENSURE_TRUE(tldService, false);
while (true) {
if (nsScriptSecurityManager::SecurityCompareURIs(probe, aBase)) {
return true;
}
nsAutoCString host, newHost;
rv = probe->GetHost(host);
NS_ENSURE_SUCCESS(rv, false);
rv = tldService->GetNextSubDomain(host, newHost);
if (rv == NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS) {
return false;
}
NS_ENSURE_SUCCESS(rv, false);
rv = NS_MutateURI(probe).SetHost(newHost).Finalize(probe);
NS_ENSURE_SUCCESS(rv, false);
}
}
NS_IMETHODIMP
nsScriptSecurityManager::CheckLoadURIWithPrincipal(nsIPrincipal* aPrincipal,
nsIURI* aTargetURI,
uint32_t aFlags,
uint64_t aInnerWindowID) {
MOZ_ASSERT(aPrincipal, "CheckLoadURIWithPrincipal must have a principal");
// If someone passes a flag that we don't understand, we should
// fail, because they may need a security check that we don't
// provide.
NS_ENSURE_FALSE(
aFlags &
~(nsIScriptSecurityManager::LOAD_IS_AUTOMATIC_DOCUMENT_REPLACEMENT |
nsIScriptSecurityManager::ALLOW_CHROME |
nsIScriptSecurityManager::DISALLOW_SCRIPT |
nsIScriptSecurityManager::DISALLOW_INHERIT_PRINCIPAL |
nsIScriptSecurityManager::DONT_REPORT_ERRORS),
NS_ERROR_UNEXPECTED);
NS_ENSURE_ARG_POINTER(aPrincipal);
NS_ENSURE_ARG_POINTER(aTargetURI);
// If DISALLOW_INHERIT_PRINCIPAL is set, we prevent loading of URIs which
// would do such inheriting. That would be URIs that do not have their own
// security context. We do this even for the system principal.
if (aFlags & nsIScriptSecurityManager::DISALLOW_INHERIT_PRINCIPAL) {
nsresult rv = DenyAccessIfURIHasFlags(
aTargetURI, nsIProtocolHandler::URI_INHERITS_SECURITY_CONTEXT);
NS_ENSURE_SUCCESS(rv, rv);
}
if (aPrincipal == mSystemPrincipal) {
// Allow access
return NS_OK;
}
nsCOMPtr<nsIURI> sourceURI;
auto* basePrin = BasePrincipal::Cast(aPrincipal);
basePrin->GetURI(getter_AddRefs(sourceURI));
if (!sourceURI) {
if (basePrin->Is<ExpandedPrincipal>()) {
// If the target addon is MV3 or the pref is on we require extension
// resources loaded from content to be listed in web_accessible_resources.
auto* targetPolicy =
ExtensionPolicyService::GetSingleton().GetByURL(aTargetURI);
bool contentAccessRequired =
targetPolicy &&
(targetPolicy->ManifestVersion() > 2 ||
StaticPrefs::extensions_content_web_accessible_enabled());
auto expanded = basePrin->As<ExpandedPrincipal>();
const auto& allowList = expanded->AllowList();
// Only report errors when all principals fail.
// With expanded principals, which are used by extension content scripts,
// we check only against non-extension principals for access to extension
// resource to enforce making those resources explicitly web accessible.
uint32_t flags = aFlags | nsIScriptSecurityManager::DONT_REPORT_ERRORS;
for (size_t i = 0; i < allowList.Length() - 1; i++) {
if (contentAccessRequired &&
BasePrincipal::Cast(allowList[i])->AddonPolicy()) {
continue;
}
nsresult rv = CheckLoadURIWithPrincipal(allowList[i], aTargetURI, flags,
aInnerWindowID);
if (NS_SUCCEEDED(rv)) {
// Allow access if it succeeded with one of the allowlisted principals
return NS_OK;
}
}
if (contentAccessRequired &&
BasePrincipal::Cast(allowList.LastElement())->AddonPolicy()) {
bool reportErrors =
!(aFlags & nsIScriptSecurityManager::DONT_REPORT_ERRORS);
if (reportErrors) {
ReportError("CheckLoadURI", sourceURI, aTargetURI,
allowList.LastElement()
->OriginAttributesRef()
.IsPrivateBrowsing(),
aInnerWindowID);
}
return NS_ERROR_DOM_BAD_URI;
}
// Report errors (if requested) for the last principal.
return CheckLoadURIWithPrincipal(allowList.LastElement(), aTargetURI,
aFlags, aInnerWindowID);
}
NS_ERROR(
"Non-system principals or expanded principal passed to "
"CheckLoadURIWithPrincipal "
"must have a URI!");
return NS_ERROR_UNEXPECTED;
}
// Automatic loads are not allowed from certain protocols.
if (aFlags &
nsIScriptSecurityManager::LOAD_IS_AUTOMATIC_DOCUMENT_REPLACEMENT) {
nsresult rv = DenyAccessIfURIHasFlags(
sourceURI,
nsIProtocolHandler::URI_FORBIDS_AUTOMATIC_DOCUMENT_REPLACEMENT);
NS_ENSURE_SUCCESS(rv, rv);
}
// If either URI is a nested URI, get the base URI
nsCOMPtr<nsIURI> sourceBaseURI = NS_GetInnermostURI(sourceURI);
nsCOMPtr<nsIURI> targetBaseURI = NS_GetInnermostURI(aTargetURI);
//-- get the target scheme
nsAutoCString targetScheme;
nsresult rv = targetBaseURI->GetScheme(targetScheme);
if (NS_FAILED(rv)) return rv;
//-- Some callers do not allow loading javascript:
if ((aFlags & nsIScriptSecurityManager::DISALLOW_SCRIPT) &&
targetScheme.EqualsLiteral("javascript")) {
return NS_ERROR_DOM_BAD_URI;
}
// Check for uris that are only loadable by principals that subsume them
bool targetURIIsLoadableBySubsumers = false;
rv = NS_URIChainHasFlags(targetBaseURI,
nsIProtocolHandler::URI_LOADABLE_BY_SUBSUMERS,
&targetURIIsLoadableBySubsumers);
NS_ENSURE_SUCCESS(rv, rv);
if (targetURIIsLoadableBySubsumers) {
// check nothing else in the URI chain has flags that prevent
// access:
rv = CheckLoadURIFlags(
sourceURI, aTargetURI, sourceBaseURI, targetBaseURI, aFlags,
aPrincipal->OriginAttributesRef().IsPrivateBrowsing(), aInnerWindowID);