/* -*- 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/StoragePrincipalHelper.h" #include "xpcpublic.h" #include "XPCWrapper.h" #include "nsIInputStreamChannel.h" #include "nsILoadContext.h" #include "nsIServiceManager.h" #include "nsIScriptObjectPrincipal.h" #include "nsIScriptContext.h" #include "nsIScriptError.h" #include "nsIURL.h" #include "nsIURIMutator.h" #include "nsINestedURI.h" #include "nspr.h" #include "nsJSPrincipals.h" #include "mozilla/BasePrincipal.h" #include "ExpandedPrincipal.h" #include "SystemPrincipal.h" #include "DomainPolicy.h" #include "nsString.h" #include "nsCRT.h" #include "nsCRTGlue.h" #include "nsContentSecurityManager.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 "nsIProperties.h" #include "nsDirectoryServiceDefs.h" #include "nsIFile.h" #include "nsIFileURL.h" #include "nsIZipReader.h" #include "nsIScriptGlobalObject.h" #include "nsPIDOMWindow.h" #include "nsIDocShell.h" #include "nsIPrompt.h" #include "nsIWindowWatcher.h" #include "nsIConsoleService.h" #include "nsIOService.h" #include "nsIContent.h" #include "nsDOMJSUtils.h" #include "nsAboutProtocolUtils.h" #include "nsIClassInfo.h" #include "nsIURIFixup.h" #include "nsIChromeRegistry.h" #include "nsIResProtocolHandler.h" #include "nsIContentSecurityPolicy.h" #include "nsIAsyncVerifyRedirectCallback.h" #include "mozilla/Components.h" #include "mozilla/Preferences.h" #include "mozilla/dom/BindingUtils.h" #include "mozilla/NullPrincipal.h" #include #include "mozilla/dom/nsCSPContext.h" #include "mozilla/dom/ScriptSettings.h" #include "mozilla/ClearOnShutdown.h" #include "mozilla/StaticPtr.h" #include "mozilla/dom/WorkerCommon.h" #include "mozilla/dom/WorkerPrivate.h" #include "nsContentUtils.h" #include "nsJSUtils.h" #include "nsILoadInfo.h" #include "nsIDOMXULCommandDispatcher.h" #include "nsITreeSelection.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; nsIIOService* nsScriptSecurityManager::sIOService = nullptr; JSContext* nsScriptSecurityManager::sContext = nullptr; 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 bundleService = mozilla::services::GetStringBundleService(); 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 mBundle; static StaticRefPtr sSelf; static bool sShutdown; }; StaticRefPtr 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 (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 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_LITERAL_CSTRING("://") + 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) { nsCOMPtr uri; aPrincipal->GetDomain(getter_AddRefs(uri)); if (!uri) { aPrincipal->GetURI(getter_AddRefs(uri)); } NS_ENSURE_TRUE(uri, NS_ERROR_UNEXPECTED); return GetOriginFromURI(uri, 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); } /* * 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 principal; nsresult rv = GetChannelResultPrincipal(aChannel, getter_AddRefs(principal), /*aIgnoreSandboxing*/ false); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } return StoragePrincipalHelper::Create(aChannel, principal, aPrincipal); } NS_IMETHODIMP nsScriptSecurityManager::GetChannelResultPrincipals( nsIChannel* aChannel, nsIPrincipal** aPrincipal, nsIPrincipal** aStoragePrincipal) { nsresult rv = GetChannelResultPrincipal(aChannel, aPrincipal, /*aIgnoreSandboxing*/ false); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } return StoragePrincipalHelper::Create(aChannel, *aPrincipal, aStoragePrincipal); } static void InheritAndSetCSPOnPrincipalIfNeeded(nsIChannel* aChannel, nsIPrincipal* aPrincipal) { // loading a data: URI into an iframe, or loading frame[srcdoc] need // to inherit the CSP (see Bug 1073952, 1381761). MOZ_ASSERT(aChannel && aPrincipal, "need a valid channel and principal"); if (!aChannel) { return; } nsCOMPtr loadInfo = aChannel->LoadInfo(); if (loadInfo->GetExternalContentPolicyType() != nsIContentPolicy::TYPE_SUBDOCUMENT) { return; } nsCOMPtr uri; nsresult rv = aChannel->GetURI(getter_AddRefs(uri)); NS_ENSURE_SUCCESS_VOID(rv); nsAutoCString URISpec; rv = uri->GetSpec(URISpec); NS_ENSURE_SUCCESS_VOID(rv); bool isSrcDoc = URISpec.EqualsLiteral("about:srcdoc"); bool isData = (NS_SUCCEEDED(uri->SchemeIs("data", &isData)) && isData); if (!isSrcDoc && !isData) { return; } nsCOMPtr principalToInherit = loadInfo->FindPrincipalToInherit(aChannel); nsCOMPtr originalCSP; principalToInherit->GetCsp(getter_AddRefs(originalCSP)); if (!originalCSP) { return; } // if the principalToInherit had a CSP, add it to the before // created NullPrincipal (unless it already has one) MOZ_ASSERT(aPrincipal->GetIsNullPrincipal(), "inheriting the CSP only valid for NullPrincipal"); nsCOMPtr nullPrincipalCSP; aPrincipal->GetCsp(getter_AddRefs(nullPrincipalCSP)); if (nullPrincipalCSP) { MOZ_ASSERT(nsCSPContext::Equals(originalCSP, nullPrincipalCSP)); // CSPs are equal, no need to set it again. return; } // After 965637 all that magical CSP inheritance goes away. For now, // we have to create a clone of the current CSP and have to manually // set it on the Principal. uint32_t count = 0; rv = originalCSP->GetPolicyCount(&count); if (NS_WARN_IF(NS_FAILED(rv))) { return; } if (count == 0) { // fast path: if there is nothing to inherit, we can return here. return; } RefPtr newCSP = new nsCSPContext(); nsWeakPtr loadingContext = static_cast(originalCSP.get())->GetLoadingContext(); nsCOMPtr doc = do_QueryReferent(loadingContext); rv = doc ? newCSP->SetRequestContext(doc, nullptr) : newCSP->SetRequestContext(nullptr, aPrincipal); if (NS_WARN_IF(NS_FAILED(rv))) { return; } for (uint32_t i = 0; i < count; ++i) { const nsCSPPolicy* policy = originalCSP->GetPolicy(i); MOZ_ASSERT(policy); nsAutoString policyString; policy->toString(policyString); rv = newCSP->AppendPolicy(policyString, policy->getReportOnlyFlag(), policy->getDeliveredViaMetaTagFlag()); if (NS_WARN_IF(NS_FAILED(rv))) { return; } } aPrincipal->SetCsp(newCSP); } 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 loadInfo = aChannel->LoadInfo(); if (loadInfo->GetForceInheritPrincipalOverruleOwner()) { nsCOMPtr principalToInherit = loadInfo->FindPrincipalToInherit(aChannel); principalToInherit.forget(aPrincipal); return NS_OK; } nsCOMPtr owner; aChannel->GetOwner(getter_AddRefs(owner)); if (owner) { CallQueryInterface(owner, aPrincipal); if (*aPrincipal) { return NS_OK; } } if (!aIgnoreSandboxing && loadInfo->GetLoadingSandboxed()) { nsCOMPtr sandboxedLoadingPrincipal = loadInfo->GetSandboxedLoadingPrincipal(); MOZ_ASSERT(sandboxedLoadingPrincipal); InheritAndSetCSPOnPrincipalIfNeeded(aChannel, sandboxedLoadingPrincipal); sandboxedLoadingPrincipal.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 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_DATA_INHERITS || securityMode == nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS || securityMode == nsILoadInfo::SEC_REQUIRE_CORS_DATA_INHERITS)) { nsCOMPtr uri; nsresult rv = NS_GetFinalChannelURI(aChannel, getter_AddRefs(uri)); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr principalToInherit = loadInfo->FindPrincipalToInherit(aChannel); bool inheritForAboutBlank = loadInfo->GetAboutBlankInherits(); if (nsContentUtils::ChannelShouldInheritPrincipal( principalToInherit, uri, inheritForAboutBlank, false)) { principalToInherit.forget(aPrincipal); return NS_OK; } } nsresult rv = GetChannelURIPrincipal(aChannel, aPrincipal); NS_ENSURE_SUCCESS(rv, rv); InheritAndSetCSPOnPrincipalIfNeeded(aChannel, *aPrincipal); return NS_OK; } /* 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 XULDocument::StartDocumentLoad. nsCOMPtr uri; nsresult rv = NS_GetFinalChannelURI(aChannel, getter_AddRefs(uri)); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr 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(); nsCOMPtr prin = BasePrincipal::CreateCodebasePrincipal(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::HandleValue aValue) { MOZ_ASSERT(cx == nsContentUtils::GetCurrentJSContext()); nsCOMPtr subjectPrincipal = nsContentUtils::SubjectPrincipal(); #if defined(DEBUG) && !defined(ANDROID) nsContentSecurityManager::AssertEvalNotUsingSystemPrincipal(subjectPrincipal, cx); #endif nsCOMPtr csp; nsresult rv = subjectPrincipal->GetCsp(getter_AddRefs(csp)); NS_ASSERTION(NS_SUCCEEDED(rv), "CSP: Failed to get CSP from principal."); // don't do anything unless there's a CSP if (!csp) return true; nsCOMPtr cspEventListener; if (!NS_IsMainThread()) { WorkerPrivate* workerPrivate = mozilla::dom::GetWorkerPrivateFromContext(cx); if (workerPrivate) { cspEventListener = workerPrivate->CSPEventListener(); } } bool evalOK = true; bool reportViolation = false; rv = csp->GetAllowsEval(&reportViolation, &evalOK); if (NS_FAILED(rv)) { NS_WARNING("CSP: failed to get allowsEval"); return true; // fail open to not break sites. } if (reportViolation) { JS::Rooted jsString(cx, JS::ToString(cx, aValue)); if (NS_WARN_IF(!jsString)) { JS_ClearPendingException(cx); return false; } nsAutoJSString scriptSample; if (NS_WARN_IF(!scriptSample.init(cx, jsString))) { JS_ClearPendingException(cx); return false; } JS::AutoFilename scriptFilename; nsAutoString fileName; unsigned lineNum = 0; unsigned columnNum = 0; if (JS::DescribeScriptedCaller(cx, &scriptFilename, &lineNum, &columnNum)) { if (const char* file = scriptFilename.get()) { CopyUTF8toUTF16(nsDependentCString(file), fileName); } } else { MOZ_ASSERT(!JS_IsExceptionPending(cx)); } csp->LogViolationDetails(nsIContentSecurityPolicy::VIOLATION_TYPE_EVAL, nullptr, // triggering element cspEventListener, fileName, scriptSample, lineNum, columnNum, EmptyString(), EmptyString()); } return evalOK; } // 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; } /*static*/ uint32_t nsScriptSecurityManager::HashPrincipalByOrigin( nsIPrincipal* aPrincipal) { nsCOMPtr uri; aPrincipal->GetDomain(getter_AddRefs(uri)); if (!uri) aPrincipal->GetURI(getter_AddRefs(uri)); return SecurityHashURI(uri); } 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(principal, aURI, nsIScriptSecurityManager::STANDARD); 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 probe = aProbeArg; nsCOMPtr 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) { 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 sourceURI; aPrincipal->GetURI(getter_AddRefs(sourceURI)); if (!sourceURI) { auto* basePrin = BasePrincipal::Cast(aPrincipal); if (basePrin->Is()) { auto expanded = basePrin->As(); for (auto& prin : expanded->AllowList()) { nsresult rv = CheckLoadURIWithPrincipal(prin, aTargetURI, aFlags); if (NS_SUCCEEDED(rv)) { // Allow access if it succeeded with one of the allowlisted principals return NS_OK; } } // None of our allowlisted principals worked. return NS_ERROR_DOM_BAD_URI; } 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 sourceBaseURI = NS_GetInnermostURI(sourceURI); nsCOMPtr 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 hasFlags; rv = NS_URIChainHasFlags( targetBaseURI, nsIProtocolHandler::URI_LOADABLE_BY_SUBSUMERS, &hasFlags); NS_ENSURE_SUCCESS(rv, rv); if (hasFlags) { // check nothing else in the URI chain has flags that prevent // access: rv = CheckLoadURIFlags( sourceURI, aTargetURI, sourceBaseURI, targetBaseURI, aFlags, aPrincipal->OriginAttributesRef().mPrivateBrowsingId > 0); NS_ENSURE_SUCCESS(rv, rv); // Check the principal is allowed to load the target. return aPrincipal->CheckMayLoad(targetBaseURI, true, false); } //-- get the source scheme nsAutoCString sourceScheme; rv = sourceBaseURI->GetScheme(sourceScheme); if (NS_FAILED(rv)) return rv; // When comparing schemes, if the relevant pref is set, view-source URIs // are reachable from same-protocol (so e.g. file: can link to // view-source:file). This is required for reftests. static bool sViewSourceReachableFromInner = false; static bool sCachedViewSourcePref = false; if (!sCachedViewSourcePref) { sCachedViewSourcePref = true; mozilla::Preferences::AddBoolVarCache( &sViewSourceReachableFromInner, "security.view-source.reachable-from-inner-protocol"); } bool targetIsViewSource = false; if (sourceScheme.LowerCaseEqualsLiteral(NS_NULLPRINCIPAL_SCHEME)) { // A null principal can target its own URI. if (sourceURI == aTargetURI) { return NS_OK; } } else if (sViewSourceReachableFromInner && sourceScheme.EqualsIgnoreCase(targetScheme.get()) && NS_SUCCEEDED( aTargetURI->SchemeIs("view-source", &targetIsViewSource)) && targetIsViewSource) { // exception for foo: linking to view-source:foo for reftests... return NS_OK; } else if (sourceScheme.EqualsIgnoreCase("file") && targetScheme.EqualsIgnoreCase("moz-icon")) { // exception for file: linking to moz-icon://.ext?size=... // Note that because targetScheme is the base (innermost) URI scheme, // this does NOT allow file -> moz-icon:file:///... links. // This is intentional. return NS_OK; } // Check for webextension rv = NS_URIChainHasFlags( aTargetURI, nsIProtocolHandler::URI_LOADABLE_BY_EXTENSIONS, &hasFlags); NS_ENSURE_SUCCESS(rv, rv); if (hasFlags && BasePrincipal::Cast(aPrincipal)->AddonPolicy()) { return NS_OK; } // If we get here, check all the schemes can link to each other, from the top // down: nsCaseInsensitiveCStringComparator stringComparator; nsCOMPtr currentURI = sourceURI; nsCOMPtr currentOtherURI = aTargetURI; bool denySameSchemeLinks = false; rv = NS_URIChainHasFlags(aTargetURI, nsIProtocolHandler::URI_SCHEME_NOT_SELF_LINKABLE, &denySameSchemeLinks); if (NS_FAILED(rv)) return rv; while (currentURI && currentOtherURI) { nsAutoCString scheme, otherScheme; currentURI->GetScheme(scheme); currentOtherURI->GetScheme(otherScheme); bool schemesMatch = scheme.Equals(otherScheme, stringComparator); bool isSamePage = false; // about: URIs are special snowflakes. if (scheme.EqualsLiteral("about") && schemesMatch) { nsAutoCString moduleName, otherModuleName; // about: pages can always link to themselves: isSamePage = NS_SUCCEEDED(NS_GetAboutModuleName(currentURI, moduleName)) && NS_SUCCEEDED( NS_GetAboutModuleName(currentOtherURI, otherModuleName)) && moduleName.Equals(otherModuleName); if (!isSamePage) { // We will have allowed the load earlier if the source page has // system principal. So we know the source has a content // principal, and it's trying to link to something else. // Linkable about: pages are always reachable, even if we hit // the CheckLoadURIFlags call below. // We punch only 1 other hole: iff the source is unlinkable, // we let them link to other pages explicitly marked SAFE // for content. This avoids world-linkable about: pages linking // to non-world-linkable about: pages. nsCOMPtr module, otherModule; bool knowBothModules = NS_SUCCEEDED( NS_GetAboutModule(currentURI, getter_AddRefs(module))) && NS_SUCCEEDED(NS_GetAboutModule(currentOtherURI, getter_AddRefs(otherModule))); uint32_t aboutModuleFlags = 0; uint32_t otherAboutModuleFlags = 0; knowBothModules = knowBothModules && NS_SUCCEEDED(module->GetURIFlags(currentURI, &aboutModuleFlags)) && NS_SUCCEEDED(otherModule->GetURIFlags(currentOtherURI, &otherAboutModuleFlags)); if (knowBothModules) { isSamePage = !(aboutModuleFlags & nsIAboutModule::MAKE_LINKABLE) && (otherAboutModuleFlags & nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT); if (isSamePage && otherAboutModuleFlags & nsIAboutModule::MAKE_LINKABLE) { // XXXgijs: this is a hack. The target will be nested // (with innerURI of moz-safe-about:whatever), and // the source isn't, so we won't pass if we finish // the loop. We *should* pass, though, so return here. // This hack can go away when bug 1228118 is fixed. return NS_OK; } } } } else { bool equalExceptRef = false; rv = currentURI->EqualsExceptRef(currentOtherURI, &equalExceptRef); isSamePage = NS_SUCCEEDED(rv) && equalExceptRef; } // If schemes are not equal, or they're equal but the target URI // is different from the source URI and doesn't always allow linking // from the same scheme, check if the URI flags of the current target // URI allow the current source URI to link to it. // The policy is specified by the protocol flags on both URIs. if (!schemesMatch || (denySameSchemeLinks && !isSamePage)) { return CheckLoadURIFlags( currentURI, currentOtherURI, sourceBaseURI, targetBaseURI, aFlags, aPrincipal->OriginAttributesRef().mPrivateBrowsingId > 0); } // Otherwise... check if we can nest another level: nsCOMPtr nestedURI = do_QueryInterface(currentURI); nsCOMPtr nestedOtherURI = do_QueryInterface(currentOtherURI); // If schemes match and neither URI is nested further, we're OK. if (!nestedURI && !nestedOtherURI) { return NS_OK; } // If one is nested and the other isn't, something is wrong. if (!nestedURI != !nestedOtherURI) { return NS_ERROR_DOM_BAD_URI; } // Otherwise, both should be nested and we'll go through the loop again. nestedURI->GetInnerURI(getter_AddRefs(currentURI)); nestedOtherURI->GetInnerURI(getter_AddRefs(currentOtherURI)); } // We should never get here. We should always return from inside the loop. return NS_ERROR_DOM_BAD_URI; } /** * Helper method to check whether the target URI and its innermost ("base") URI * has protocol flags that should stop it from being loaded by the source URI * (and/or the source URI's innermost ("base") URI), taking into account any * nsIScriptSecurityManager flags originally passed to * CheckLoadURIWithPrincipal and friends. * * @return if success, access is allowed. Otherwise, deny access */ nsresult nsScriptSecurityManager::CheckLoadURIFlags( nsIURI* aSourceURI, nsIURI* aTargetURI, nsIURI* aSourceBaseURI, nsIURI* aTargetBaseURI, uint32_t aFlags, bool aFromPrivateWindow) { // Note that the order of policy checks here is very important! // We start from most restrictive and work our way down. bool reportErrors = !(aFlags & nsIScriptSecurityManager::DONT_REPORT_ERRORS); const char* errorTag = "CheckLoadURIError"; nsAutoCString targetScheme; nsresult rv = aTargetBaseURI->GetScheme(targetScheme); if (NS_FAILED(rv)) return rv; // Check for system target URI rv = DenyAccessIfURIHasFlags(aTargetURI, nsIProtocolHandler::URI_DANGEROUS_TO_LOAD); if (NS_FAILED(rv)) { // Deny access, since the origin principal is not system if (reportErrors) { ReportError(errorTag, aSourceURI, aTargetURI, aFromPrivateWindow); } return rv; } // Used by ExtensionProtocolHandler to prevent loading extension resources // in private contexts if the extension does not have permission. if (aFromPrivateWindow) { rv = DenyAccessIfURIHasFlags( aTargetURI, nsIProtocolHandler::URI_DISALLOW_IN_PRIVATE_CONTEXT); if (NS_FAILED(rv)) { if (reportErrors) { ReportError(errorTag, aSourceURI, aTargetURI, aFromPrivateWindow); } return rv; } } // Check for chrome target URI bool hasFlags = false; rv = NS_URIChainHasFlags(aTargetURI, nsIProtocolHandler::URI_IS_UI_RESOURCE, &hasFlags); NS_ENSURE_SUCCESS(rv, rv); if (hasFlags) { if (aFlags & nsIScriptSecurityManager::ALLOW_CHROME) { // Allow a URI_IS_UI_RESOURCE source to link to a URI_IS_UI_RESOURCE // target if ALLOW_CHROME is set. // // ALLOW_CHROME is a flag that we pass on all loads _except_ docshell // loads (since docshell loads run the loaded content with its origin // principal). So we're effectively allowing resource://, chrome://, // and moz-icon:// source URIs to load resource://, chrome://, and // moz-icon:// files, so long as they're not loading it as a document. bool sourceIsUIResource; rv = NS_URIChainHasFlags(aSourceBaseURI, nsIProtocolHandler::URI_IS_UI_RESOURCE, &sourceIsUIResource); NS_ENSURE_SUCCESS(rv, rv); if (sourceIsUIResource) { return NS_OK; } if (targetScheme.EqualsLiteral("resource")) { // Mochitests that need to load resource:// URIs not declared // content-accessible in manifests should set the preference // "security.all_resource_uri_content_accessible" true. static bool sSecurityPrefCached = false; static bool sAllResourceUriContentAccessible = false; if (!sSecurityPrefCached) { sSecurityPrefCached = true; Preferences::AddBoolVarCache( &sAllResourceUriContentAccessible, "security.all_resource_uri_content_accessible", false); } if (sAllResourceUriContentAccessible) { return NS_OK; } nsCOMPtr ph; rv = sIOService->GetProtocolHandler("resource", getter_AddRefs(ph)); NS_ENSURE_SUCCESS(rv, rv); if (!ph) { return NS_ERROR_DOM_BAD_URI; } nsCOMPtr rph = do_QueryInterface(ph); if (!rph) { return NS_ERROR_DOM_BAD_URI; } bool accessAllowed = false; rph->AllowContentToAccess(aTargetBaseURI, &accessAllowed); if (accessAllowed) { return NS_OK; } } else if (targetScheme.EqualsLiteral("chrome")) { // Allow the load only if the chrome package is allowlisted. nsCOMPtr reg( do_GetService(NS_CHROMEREGISTRY_CONTRACTID)); if (reg) { bool accessAllowed = false; reg->AllowContentToAccess(aTargetBaseURI, &accessAllowed); if (accessAllowed) { return NS_OK; } } } } if (reportErrors) { ReportError(errorTag, aSourceURI, aTargetURI, aFromPrivateWindow); } return NS_ERROR_DOM_BAD_URI; } // Check for target URI pointing to a file rv = NS_URIChainHasFlags(aTargetURI, nsIProtocolHandler::URI_IS_LOCAL_FILE, &hasFlags); NS_ENSURE_SUCCESS(rv, rv); if (hasFlags) { // Allow domains that were allowlisted in the prefs. In 99.9% of cases, // this array is empty. bool isAllowlisted; MOZ_ALWAYS_SUCCEEDS(InFileURIAllowlist(aSourceURI, &isAllowlisted)); if (isAllowlisted) { return NS_OK; } // Allow chrome:// bool isChrome = false; if (NS_SUCCEEDED(aSourceBaseURI->SchemeIs("chrome", &isChrome)) && isChrome) { return NS_OK; } // Nothing else. if (reportErrors) { ReportError(errorTag, aSourceURI, aTargetURI, aFromPrivateWindow); } return NS_ERROR_DOM_BAD_URI; } // OK, everyone is allowed to load this, since unflagged handlers are // deprecated but treated as URI_LOADABLE_BY_ANYONE. But check whether we // need to warn. At some point we'll want to make this warning into an // error and treat unflagged handlers as URI_DANGEROUS_TO_LOAD. rv = NS_URIChainHasFlags( aTargetBaseURI, nsIProtocolHandler::URI_LOADABLE_BY_ANYONE, &hasFlags); NS_ENSURE_SUCCESS(rv, rv); // NB: we also get here if the base URI is URI_LOADABLE_BY_SUBSUMERS, // and none of the rest of the nested chain of URIs for aTargetURI // prohibits the load, so avoid warning in that case: bool hasSubsumersFlag = false; rv = NS_URIChainHasFlags(aTargetBaseURI, nsIProtocolHandler::URI_LOADABLE_BY_SUBSUMERS, &hasSubsumersFlag); NS_ENSURE_SUCCESS(rv, rv); if (!hasFlags && !hasSubsumersFlag) { nsCOMPtr bundle = BundleHelper::GetOrCreate(); if (bundle) { nsAutoString message; NS_ConvertASCIItoUTF16 ucsTargetScheme(targetScheme); const char16_t* formatStrings[] = {ucsTargetScheme.get()}; rv = bundle->FormatStringFromName("ProtocolFlagError", formatStrings, ArrayLength(formatStrings), message); if (NS_SUCCEEDED(rv)) { nsCOMPtr console( do_GetService("@mozilla.org/consoleservice;1")); NS_ENSURE_TRUE(console, NS_ERROR_FAILURE); console->LogStringMessage(message.get()); } } } return NS_OK; } nsresult nsScriptSecurityManager::ReportError(const char* aMessageTag, nsIURI* aSource, nsIURI* aTarget, bool aFromPrivateWindow) { nsresult rv; NS_ENSURE_TRUE(aSource && aTarget, NS_ERROR_NULL_POINTER); // Get the source URL spec nsAutoCString sourceSpec; rv = aSource->GetAsciiSpec(sourceSpec); NS_ENSURE_SUCCESS(rv, rv); // Get the target URL spec nsAutoCString targetSpec; rv = aTarget->GetAsciiSpec(targetSpec); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr bundle = BundleHelper::GetOrCreate(); if (NS_WARN_IF(!bundle)) { return NS_OK; } // Localize the error message nsAutoString message; NS_ConvertASCIItoUTF16 ucsSourceSpec(sourceSpec); NS_ConvertASCIItoUTF16 ucsTargetSpec(targetSpec); const char16_t* formatStrings[] = {ucsSourceSpec.get(), ucsTargetSpec.get()}; rv = bundle->FormatStringFromName(aMessageTag, formatStrings, ArrayLength(formatStrings), message); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr console( do_GetService(NS_CONSOLESERVICE_CONTRACTID)); NS_ENSURE_TRUE(console, NS_ERROR_FAILURE); nsCOMPtr error(do_CreateInstance(NS_SCRIPTERROR_CONTRACTID)); NS_ENSURE_TRUE(error, NS_ERROR_FAILURE); // using category of "SOP" so we can link to MDN rv = error->Init(message, EmptyString(), EmptyString(), 0, 0, nsIScriptError::errorFlag, "SOP", aFromPrivateWindow, true /* From chrome context */); NS_ENSURE_SUCCESS(rv, rv); console->LogMessage(error); return NS_OK; } NS_IMETHODIMP nsScriptSecurityManager::CheckLoadURIStrWithPrincipal( nsIPrincipal* aPrincipal, const nsACString& aTargetURIStr, uint32_t aFlags) { nsresult rv; nsCOMPtr target; rv = NS_NewURI(getter_AddRefs(target), aTargetURIStr, nullptr, nullptr, sIOService); NS_ENSURE_SUCCESS(rv, rv); rv = CheckLoadURIWithPrincipal(aPrincipal, target, aFlags); if (rv == NS_ERROR_DOM_BAD_URI) { // Don't warn because NS_ERROR_DOM_BAD_URI is one of the expected // return values. return rv; } NS_ENSURE_SUCCESS(rv, rv); // Now start testing fixup -- since aTargetURIStr is a string, not // an nsIURI, we may well end up fixing it up before loading. // Note: This needs to stay in sync with the nsIURIFixup api. nsCOMPtr fixup = components::URIFixup::Service(); if (!fixup) { return rv; } uint32_t flags[] = {nsIURIFixup::FIXUP_FLAG_NONE, nsIURIFixup::FIXUP_FLAG_FIX_SCHEME_TYPOS, nsIURIFixup::FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP, nsIURIFixup::FIXUP_FLAGS_MAKE_ALTERNATE_URI, nsIURIFixup::FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP | nsIURIFixup::FIXUP_FLAGS_MAKE_ALTERNATE_URI}; for (uint32_t i = 0; i < ArrayLength(flags); ++i) { rv = fixup->CreateFixupURI(aTargetURIStr, flags[i], nullptr, getter_AddRefs(target)); NS_ENSURE_SUCCESS(rv, rv); rv = CheckLoadURIWithPrincipal(aPrincipal, target, aFlags); if (rv == NS_ERROR_DOM_BAD_URI) { // Don't warn because NS_ERROR_DOM_BAD_URI is one of the expected // return values. return rv; } NS_ENSURE_SUCCESS(rv, rv); } return rv; } NS_IMETHODIMP nsScriptSecurityManager::InFileURIAllowlist(nsIURI* aUri, bool* aResult) { MOZ_ASSERT(aUri); MOZ_ASSERT(aResult); *aResult = false; for (nsIURI* uri : EnsureFileURIAllowlist()) { if (EqualOrSubdomain(aUri, uri)) { *aResult = true; return NS_OK; } } return NS_OK; } ///////////////// Principals /////////////////////// NS_IMETHODIMP nsScriptSecurityManager::GetSystemPrincipal(nsIPrincipal** result) { NS_ADDREF(*result = mSystemPrincipal); return NS_OK; } NS_IMETHODIMP nsScriptSecurityManager::CreateCodebasePrincipal( nsIURI* aURI, JS::Handle aOriginAttributes, JSContext* aCx, nsIPrincipal** aPrincipal) { OriginAttributes attrs; if (!aOriginAttributes.isObject() || !attrs.Init(aCx, aOriginAttributes)) { return NS_ERROR_INVALID_ARG; } nsCOMPtr prin = BasePrincipal::CreateCodebasePrincipal(aURI, attrs); prin.forget(aPrincipal); return *aPrincipal ? NS_OK : NS_ERROR_FAILURE; } NS_IMETHODIMP nsScriptSecurityManager::CreateCodebasePrincipalFromOrigin( const nsACString& aOrigin, nsIPrincipal** aPrincipal) { if (StringBeginsWith(aOrigin, NS_LITERAL_CSTRING("["))) { return NS_ERROR_INVALID_ARG; } if (StringBeginsWith(aOrigin, NS_LITERAL_CSTRING(NS_NULLPRINCIPAL_SCHEME ":"))) { return NS_ERROR_INVALID_ARG; } nsCOMPtr prin = BasePrincipal::CreateCodebasePrincipal(aOrigin); prin.forget(aPrincipal); return *aPrincipal ? NS_OK : NS_ERROR_FAILURE; } NS_IMETHODIMP nsScriptSecurityManager::CreateNullPrincipal( JS::Handle aOriginAttributes, JSContext* aCx, nsIPrincipal** aPrincipal) { OriginAttributes attrs; if (!aOriginAttributes.isObject() || !attrs.Init(aCx, aOriginAttributes)) { return NS_ERROR_INVALID_ARG; } nsCOMPtr prin = NullPrincipal::Create(attrs); prin.forget(aPrincipal); return NS_OK; } NS_IMETHODIMP nsScriptSecurityManager::GetLoadContextCodebasePrincipal( nsIURI* aURI, nsILoadContext* aLoadContext, nsIPrincipal** aPrincipal) { NS_ENSURE_STATE(aLoadContext); OriginAttributes docShellAttrs; aLoadContext->GetOriginAttributes(docShellAttrs); nsCOMPtr prin = BasePrincipal::CreateCodebasePrincipal(aURI, docShellAttrs); prin.forget(aPrincipal); return *aPrincipal ? NS_OK : NS_ERROR_FAILURE; } NS_IMETHODIMP nsScriptSecurityManager::GetDocShellCodebasePrincipal( nsIURI* aURI, nsIDocShell* aDocShell, nsIPrincipal** aPrincipal) { nsCOMPtr prin = BasePrincipal::CreateCodebasePrincipal( aURI, nsDocShell::Cast(aDocShell)->GetOriginAttributes()); prin.forget(aPrincipal); return *aPrincipal ? NS_OK : NS_ERROR_FAILURE; } NS_IMETHODIMP nsScriptSecurityManager::PrincipalWithOA( nsIPrincipal* aPrincipal, JS::Handle aOriginAttributes, JSContext* aCx, nsIPrincipal** aReturnPrincipal) { if (!aPrincipal) { return NS_OK; } if (aPrincipal->GetIsCodebasePrincipal()) { OriginAttributes attrs; if (!aOriginAttributes.isObject() || !attrs.Init(aCx, aOriginAttributes)) { return NS_ERROR_INVALID_ARG; } RefPtr copy = new ContentPrincipal(); ContentPrincipal* contentPrincipal = static_cast(aPrincipal); nsresult rv = copy->Init(contentPrincipal, attrs); NS_ENSURE_SUCCESS(rv, rv); copy.forget(aReturnPrincipal); } else { // We do this for null principals, system principals (both fine) // ... and expanded principals, where we should probably do something // cleverer, but I also don't think we care too much. nsCOMPtr prin = aPrincipal; prin.forget(aReturnPrincipal); } return *aReturnPrincipal ? NS_OK : NS_ERROR_FAILURE; } NS_IMETHODIMP nsScriptSecurityManager::CanCreateWrapper(JSContext* cx, const nsIID& aIID, nsISupports* aObj, nsIClassInfo* aClassInfo) { // XXX Special case for Exception ? // We give remote-XUL allowlisted domains a free pass here. See bug 932906. JS::Rooted contextRealm(cx, JS::GetCurrentRealmOrNull(cx)); MOZ_RELEASE_ASSERT(contextRealm); if (!xpc::AllowContentXBLScope(contextRealm)) { return NS_OK; } if (nsContentUtils::IsCallerChrome()) { return NS_OK; } //-- Access denied, report an error nsAutoCString originUTF8; nsIPrincipal* subjectPrincipal = nsContentUtils::SubjectPrincipal(); GetPrincipalDomainOrigin(subjectPrincipal, originUTF8); NS_ConvertUTF8toUTF16 originUTF16(originUTF8); nsAutoCString classInfoNameUTF8; if (aClassInfo) { aClassInfo->GetClassDescription(classInfoNameUTF8); } if (classInfoNameUTF8.IsEmpty()) { classInfoNameUTF8.AssignLiteral("UnnamedClass"); } nsCOMPtr bundle = BundleHelper::GetOrCreate(); if (NS_WARN_IF(!bundle)) { return NS_OK; } NS_ConvertUTF8toUTF16 classInfoUTF16(classInfoNameUTF8); nsresult rv; nsAutoString errorMsg; if (originUTF16.IsEmpty()) { const char16_t* formatStrings[] = {classInfoUTF16.get()}; rv = bundle->FormatStringFromName("CreateWrapperDenied", formatStrings, 1, errorMsg); } else { const char16_t* formatStrings[] = {classInfoUTF16.get(), originUTF16.get()}; rv = bundle->FormatStringFromName("CreateWrapperDeniedForOrigin", formatStrings, 2, errorMsg); } NS_ENSURE_SUCCESS(rv, rv); SetPendingException(cx, errorMsg.get()); return NS_ERROR_DOM_XPCONNECT_ACCESS_DENIED; } NS_IMETHODIMP nsScriptSecurityManager::CanCreateInstance(JSContext* cx, const nsCID& aCID) { if (nsContentUtils::IsCallerChrome()) { return NS_OK; } //-- Access denied, report an error nsAutoCString errorMsg("Permission denied to create instance of class. CID="); char cidStr[NSID_LENGTH]; aCID.ToProvidedString(cidStr); errorMsg.Append(cidStr); SetPendingExceptionASCII(cx, errorMsg.get()); return NS_ERROR_DOM_XPCONNECT_ACCESS_DENIED; } NS_IMETHODIMP nsScriptSecurityManager::CanGetService(JSContext* cx, const nsCID& aCID) { if (nsContentUtils::IsCallerChrome()) { return NS_OK; } //-- Access denied, report an error nsAutoCString errorMsg("Permission denied to get service. CID="); char cidStr[NSID_LENGTH]; aCID.ToProvidedString(cidStr); errorMsg.Append(cidStr); SetPendingExceptionASCII(cx, errorMsg.get()); return NS_ERROR_DOM_XPCONNECT_ACCESS_DENIED; } const char sJSEnabledPrefName[] = "javascript.enabled"; const char sFileOriginPolicyPrefName[] = "security.fileuri.strict_origin_policy"; static const char* kObservedPrefs[] = {sJSEnabledPrefName, sFileOriginPolicyPrefName, "capability.policy.", nullptr}; ///////////////////////////////////////////// // Constructor, Destructor, Initialization // ///////////////////////////////////////////// nsScriptSecurityManager::nsScriptSecurityManager(void) : mPrefInitialized(false), mIsJavaScriptEnabled(false) { static_assert( sizeof(intptr_t) == sizeof(void*), "intptr_t and void* have different lengths on this platform. " "This may cause a security failure with the SecurityLevel union."); } nsresult nsScriptSecurityManager::Init() { nsresult rv = CallGetService(NS_IOSERVICE_CONTRACTID, &sIOService); NS_ENSURE_SUCCESS(rv, rv); InitPrefs(); // Create our system principal singleton RefPtr system = SystemPrincipal::Create(); mSystemPrincipal = system; //-- Register security check callback in the JS engine // Currently this is used to control access to function.caller sContext = danger::GetJSContext(); static const JSSecurityCallbacks securityCallbacks = { ContentSecurityPolicyPermitsJSAction, JSPrincipalsSubsume, }; MOZ_ASSERT(!JS_GetSecurityCallbacks(sContext)); JS_SetSecurityCallbacks(sContext, &securityCallbacks); JS_InitDestroyPrincipalsCallback(sContext, nsJSPrincipals::Destroy); JS_SetTrustedPrincipals(sContext, system); return NS_OK; } static StaticRefPtr gScriptSecMan; nsScriptSecurityManager::~nsScriptSecurityManager(void) { Preferences::UnregisterPrefixCallbacks( PREF_CHANGE_METHOD(nsScriptSecurityManager::ScriptSecurityPrefChanged), kObservedPrefs, this); if (mDomainPolicy) { mDomainPolicy->Deactivate(); } // ContentChild might hold a reference to the domain policy, // and it might release it only after the security manager is // gone. But we can still assert this for the main process. MOZ_ASSERT_IF(XRE_IsParentProcess(), !mDomainPolicy); } void nsScriptSecurityManager::Shutdown() { if (sContext) { JS_SetSecurityCallbacks(sContext, nullptr); JS_SetTrustedPrincipals(sContext, nullptr); sContext = nullptr; } NS_IF_RELEASE(sIOService); BundleHelper::Shutdown(); } nsScriptSecurityManager* nsScriptSecurityManager::GetScriptSecurityManager() { return gScriptSecMan; } /* static */ void nsScriptSecurityManager::InitStatics() { RefPtr ssManager = new nsScriptSecurityManager(); nsresult rv = ssManager->Init(); if (NS_FAILED(rv)) { MOZ_CRASH("ssManager->Init() failed"); } ClearOnShutdown(&gScriptSecMan); gScriptSecMan = ssManager; } // Currently this nsGenericFactory constructor is used only from FastLoad // (XPCOM object deserialization) code, when "creating" the system principal // singleton. already_AddRefed nsScriptSecurityManager::SystemPrincipalSingletonConstructor() { if (gScriptSecMan) return do_AddRef(gScriptSecMan->mSystemPrincipal) .downcast(); return nullptr; } struct IsWhitespace { static bool Test(char aChar) { return NS_IsAsciiWhitespace(aChar); }; }; struct IsWhitespaceOrComma { static bool Test(char aChar) { return aChar == ',' || NS_IsAsciiWhitespace(aChar); }; }; template uint32_t SkipPast(const nsCString& str, uint32_t base) { while (base < str.Length() && Predicate::Test(str[base])) { ++base; } return base; } template uint32_t SkipUntil(const nsCString& str, uint32_t base) { while (base < str.Length() && !Predicate::Test(str[base])) { ++base; } return base; } inline void nsScriptSecurityManager::ScriptSecurityPrefChanged( const char* aPref) { MOZ_ASSERT(mPrefInitialized); mIsJavaScriptEnabled = Preferences::GetBool(sJSEnabledPrefName, mIsJavaScriptEnabled); sStrictFileOriginPolicy = Preferences::GetBool(sFileOriginPolicyPrefName, false); mFileURIAllowlist.reset(); } void nsScriptSecurityManager::AddSitesToFileURIAllowlist( const nsCString& aSiteList) { for (uint32_t base = SkipPast(aSiteList, 0), bound = 0; base < aSiteList.Length(); base = SkipPast(aSiteList, bound)) { // Grab the current site. bound = SkipUntil(aSiteList, base); nsAutoCString site(Substring(aSiteList, base, bound - base)); // Check if the URI is schemeless. If so, add both http and https. nsAutoCString unused; if (NS_FAILED(sIOService->ExtractScheme(site, unused))) { AddSitesToFileURIAllowlist(NS_LITERAL_CSTRING("http://") + site); AddSitesToFileURIAllowlist(NS_LITERAL_CSTRING("https://") + site); continue; } // Convert it to a URI and add it to our list. nsCOMPtr uri; nsresult rv = NS_NewURI(getter_AddRefs(uri), site, nullptr, nullptr, sIOService); if (NS_SUCCEEDED(rv)) { mFileURIAllowlist.ref().AppendElement(uri); } else { nsCOMPtr console( do_GetService("@mozilla.org/consoleservice;1")); if (console) { nsAutoString msg = NS_LITERAL_STRING( "Unable to to add site to file:// URI allowlist: ") + NS_ConvertASCIItoUTF16(site); console->LogStringMessage(msg.get()); } } } } nsresult nsScriptSecurityManager::InitPrefs() { nsIPrefBranch* branch = Preferences::GetRootBranch(); NS_ENSURE_TRUE(branch, NS_ERROR_FAILURE); mPrefInitialized = true; // Set the initial value of the "javascript.enabled" prefs ScriptSecurityPrefChanged(); // set observer callbacks in case the value of the prefs change Preferences::RegisterPrefixCallbacks( PREF_CHANGE_METHOD(nsScriptSecurityManager::ScriptSecurityPrefChanged), kObservedPrefs, this); OriginAttributes::InitPrefs(); return NS_OK; } NS_IMETHODIMP nsScriptSecurityManager::GetDomainPolicyActive(bool* aRv) { *aRv = !!mDomainPolicy; return NS_OK; } NS_IMETHODIMP nsScriptSecurityManager::ActivateDomainPolicy(nsIDomainPolicy** aRv) { if (!XRE_IsParentProcess()) { return NS_ERROR_SERVICE_NOT_AVAILABLE; } return ActivateDomainPolicyInternal(aRv); } NS_IMETHODIMP nsScriptSecurityManager::ActivateDomainPolicyInternal(nsIDomainPolicy** aRv) { // We only allow one domain policy at a time. The holder of the previous // policy must explicitly deactivate it first. if (mDomainPolicy) { return NS_ERROR_SERVICE_NOT_AVAILABLE; } mDomainPolicy = new DomainPolicy(); nsCOMPtr ptr = mDomainPolicy; ptr.forget(aRv); return NS_OK; } // Intentionally non-scriptable. Script must have a reference to the // nsIDomainPolicy to deactivate it. void nsScriptSecurityManager::DeactivateDomainPolicy() { mDomainPolicy = nullptr; } void nsScriptSecurityManager::CloneDomainPolicy(DomainPolicyClone* aClone) { MOZ_ASSERT(aClone); if (mDomainPolicy) { mDomainPolicy->CloneDomainPolicy(aClone); } else { aClone->active() = false; } } NS_IMETHODIMP nsScriptSecurityManager::PolicyAllowsScript(nsIURI* aURI, bool* aRv) { nsresult rv; // Compute our rule. If we don't have any domain policy set up that might // provide exceptions to this rule, we're done. *aRv = mIsJavaScriptEnabled; if (!mDomainPolicy) { return NS_OK; } // We have a domain policy. Grab the appropriate set of exceptions to the // rule (either the blocklist or the allowlist, depending on whether script // is enabled or disabled by default). nsCOMPtr exceptions; nsCOMPtr superExceptions; if (*aRv) { mDomainPolicy->GetBlocklist(getter_AddRefs(exceptions)); mDomainPolicy->GetSuperBlocklist(getter_AddRefs(superExceptions)); } else { mDomainPolicy->GetAllowlist(getter_AddRefs(exceptions)); mDomainPolicy->GetSuperAllowlist(getter_AddRefs(superExceptions)); } bool contains; rv = exceptions->Contains(aURI, &contains); NS_ENSURE_SUCCESS(rv, rv); if (contains) { *aRv = !*aRv; return NS_OK; } rv = superExceptions->ContainsSuperDomain(aURI, &contains); NS_ENSURE_SUCCESS(rv, rv); if (contains) { *aRv = !*aRv; } return NS_OK; } const nsTArray>& nsScriptSecurityManager::EnsureFileURIAllowlist() { if (mFileURIAllowlist.isSome()) { return mFileURIAllowlist.ref(); } // // Rebuild the set of principals for which we allow file:// URI loads. This // implements a small subset of an old pref-based CAPS people that people // have come to depend on. See bug 995943. // mFileURIAllowlist.emplace(); nsAutoCString policies; mozilla::Preferences::GetCString("capability.policy.policynames", policies); for (uint32_t base = SkipPast(policies, 0), bound = 0; base < policies.Length(); base = SkipPast(policies, bound)) { // Grab the current policy name. bound = SkipUntil(policies, base); auto policyName = Substring(policies, base, bound - base); // Figure out if this policy allows loading file:// URIs. If not, we can // skip it. nsCString checkLoadURIPrefName = NS_LITERAL_CSTRING("capability.policy.") + policyName + NS_LITERAL_CSTRING(".checkloaduri.enabled"); nsAutoString value; nsresult rv = Preferences::GetString(checkLoadURIPrefName.get(), value); if (NS_FAILED(rv) || !value.LowerCaseEqualsLiteral("allaccess")) { continue; } // Grab the list of domains associated with this policy. nsCString domainPrefName = NS_LITERAL_CSTRING("capability.policy.") + policyName + NS_LITERAL_CSTRING(".sites"); nsAutoCString siteList; Preferences::GetCString(domainPrefName.get(), siteList); AddSitesToFileURIAllowlist(siteList); } return mFileURIAllowlist.ref(); }