/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* 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 "xpcpublic.h" #include "XPCWrapper.h" #include "nsIAppsService.h" #include "nsIInputStreamChannel.h" #include "nsILoadContext.h" #include "nsIServiceManager.h" #include "nsIScriptObjectPrincipal.h" #include "nsIScriptContext.h" #include "nsIURL.h" #include "nsINestedURI.h" #include "nspr.h" #include "nsJSPrincipals.h" #include "mozilla/BasePrincipal.h" #include "nsSystemPrincipal.h" #include "nsPrincipal.h" #include "nsNullPrincipal.h" #include "DomainPolicy.h" #include "nsXPIDLString.h" #include "nsCRT.h" #include "nsCRTGlue.h" #include "nsDocShell.h" #include "nsError.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 "nsIObserverService.h" #include "nsIContent.h" #include "nsDOMJSUtils.h" #include "nsAboutProtocolUtils.h" #include "nsIClassInfo.h" #include "nsIURIFixup.h" #include "nsCDefaultURIFixup.h" #include "nsIChromeRegistry.h" #include "nsIContentSecurityPolicy.h" #include "nsIAsyncVerifyRedirectCallback.h" #include "mozIApplication.h" #include "mozilla/Preferences.h" #include "mozilla/dom/BindingUtils.h" #include "mozilla/dom/ContentParent.h" #include <stdint.h> #include "mozilla/dom/ScriptSettings.h" #include "mozilla/ClearOnShutdown.h" #include "mozilla/StaticPtr.h" #include "nsContentUtils.h" #include "nsJSUtils.h" #include "nsILoadInfo.h" #include "nsXPCOMStrings.h" using namespace mozilla; using namespace mozilla::dom; using namespace mozilla::ipc; nsIIOService *nsScriptSecurityManager::sIOService = nullptr; nsIStringBundle *nsScriptSecurityManager::sStrBundle = nullptr; JSContext *nsScriptSecurityManager::sContext = nullptr; bool nsScriptSecurityManager::sStrictFileOriginPolicy = true; /////////////////////////// // 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<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_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<nsIURI> 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()); } // Helper class to get stuff from the ClassInfo and not waste extra time with // virtual method calls for things it has already gotten class ClassInfoData { public: ClassInfoData(nsIClassInfo *aClassInfo, const char *aName) : mClassInfo(aClassInfo), mName(const_cast<char *>(aName)), mDidGetFlags(false), mMustFreeName(false) { } ~ClassInfoData() { if (mMustFreeName) free(mName); } uint32_t GetFlags() { if (!mDidGetFlags) { if (mClassInfo) { nsresult rv = mClassInfo->GetFlags(&mFlags); if (NS_FAILED(rv)) { mFlags = 0; } } else { mFlags = 0; } mDidGetFlags = true; } return mFlags; } bool IsDOMClass() { return !!(GetFlags() & nsIClassInfo::DOM_OBJECT); } const char* GetName() { if (!mName) { if (mClassInfo) { mClassInfo->GetClassDescription(&mName); } if (mName) { mMustFreeName = true; } else { mName = const_cast<char *>("UnnamedClass"); } } return mName; } private: nsIClassInfo *mClassInfo; // WEAK uint32_t mFlags; char *mName; bool mDidGetFlags; bool mMustFreeName; }; /* 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); } uint16_t nsScriptSecurityManager::AppStatusForPrincipal(nsIPrincipal *aPrin) { uint32_t appId = aPrin->GetAppId(); // After bug 1238160, the principal no longer knows how to answer "is this a // browser element", which is really what this code path wants. Currently, // desktop is the only platform where we intend to disable isolation on a // browser frame, so non-desktop should be able to assume that // inIsolatedMozBrowser is true for all mozbrowser frames. Additionally, // apps are no longer used on desktop, so appId is always NO_APP_ID. We use // a release assertion in nsFrameLoader::OwnerIsIsolatedMozBrowserFrame so // that platforms with apps can assume inIsolatedMozBrowser is true for all // mozbrowser frames. bool inIsolatedMozBrowser = aPrin->GetIsInIsolatedMozBrowserElement(); NS_WARNING_ASSERTION( appId != nsIScriptSecurityManager::UNKNOWN_APP_ID, "Asking for app status on a principal with an unknown app id"); // Installed apps have a valid app id (not NO_APP_ID or UNKNOWN_APP_ID) // and they are not inside a mozbrowser. if (appId == nsIScriptSecurityManager::NO_APP_ID || appId == nsIScriptSecurityManager::UNKNOWN_APP_ID || inIsolatedMozBrowser) { return nsIPrincipal::APP_STATUS_NOT_INSTALLED; } nsCOMPtr<nsIAppsService> appsService = do_GetService(APPS_SERVICE_CONTRACTID); NS_ENSURE_TRUE(appsService, nsIPrincipal::APP_STATUS_NOT_INSTALLED); nsCOMPtr<mozIApplication> app; appsService->GetAppByLocalId(appId, getter_AddRefs(app)); NS_ENSURE_TRUE(app, nsIPrincipal::APP_STATUS_NOT_INSTALLED); uint16_t status = nsIPrincipal::APP_STATUS_INSTALLED; NS_ENSURE_SUCCESS(app->GetAppStatus(&status), nsIPrincipal::APP_STATUS_NOT_INSTALLED); nsString appOrigin; NS_ENSURE_SUCCESS(app->GetOrigin(appOrigin), nsIPrincipal::APP_STATUS_NOT_INSTALLED); nsCOMPtr<nsIURI> appURI; NS_ENSURE_SUCCESS(NS_NewURI(getter_AddRefs(appURI), appOrigin), nsIPrincipal::APP_STATUS_NOT_INSTALLED); // The app could contain a cross-origin iframe - make sure that the content // is actually same-origin with the app. MOZ_ASSERT(inIsolatedMozBrowser == false, "Checked this above"); nsAutoCString suffix; PrincipalOriginAttributes attrs; NS_ENSURE_TRUE(attrs.PopulateFromOrigin(NS_ConvertUTF16toUTF8(appOrigin), suffix), nsIPrincipal::APP_STATUS_NOT_INSTALLED); attrs.mAppId = appId; attrs.mInIsolatedMozBrowser = false; nsCOMPtr<nsIPrincipal> appPrin = BasePrincipal::CreateCodebasePrincipal(appURI, attrs); NS_ENSURE_TRUE(appPrin, nsIPrincipal::APP_STATUS_NOT_INSTALLED); return aPrin->Equals(appPrin) ? status : nsIPrincipal::APP_STATUS_NOT_INSTALLED; } /* * 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); } nsresult nsScriptSecurityManager::GetChannelResultPrincipal(nsIChannel* aChannel, nsIPrincipal** aPrincipal, bool aIgnoreSandboxing) { NS_PRECONDITION(aChannel, "Must have channel!"); // Check whether we have an nsILoadInfo that says what we should do. nsCOMPtr<nsILoadInfo> loadInfo = aChannel->GetLoadInfo(); if (loadInfo && loadInfo->GetForceInheritPrincipalOverruleOwner()) { nsCOMPtr<nsIPrincipal> principalToInherit = loadInfo->PrincipalToInherit(); if (!principalToInherit) { principalToInherit = loadInfo->TriggeringPrincipal(); } 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 (loadInfo) { if (!aIgnoreSandboxing && loadInfo->GetLoadingSandboxed()) { RefPtr<nsNullPrincipal> prin; if (loadInfo->LoadingPrincipal()) { prin = nsNullPrincipal::CreateWithInheritedAttributes(loadInfo->LoadingPrincipal()); } else { NeckoOriginAttributes nAttrs; loadInfo->GetOriginAttributes(&nAttrs); PrincipalOriginAttributes pAttrs; pAttrs.InheritFromNecko(nAttrs); prin = nsNullPrincipal::Create(pAttrs); } prin.forget(aPrincipal); // if the new NullPrincipal (above) loads an iframe[srcdoc], we // need to inherit an existing CSP to avoid bypasses (bug 1073952). // We continue inheriting for nested frames with e.g., data: URLs. if (loadInfo->GetExternalContentPolicyType() == nsIContentPolicy::TYPE_SUBDOCUMENT) { nsCOMPtr<nsIURI> uri; aChannel->GetURI(getter_AddRefs(uri)); nsAutoCString URISpec; uri->GetSpec(URISpec); bool isData = (NS_SUCCEEDED(uri->SchemeIs("data", &isData)) && isData); if (URISpec.EqualsLiteral("about:srcdoc") || isData) { nsCOMPtr<nsIPrincipal> principalToInherit = loadInfo->PrincipalToInherit(); if (!principalToInherit) { principalToInherit = loadInfo->TriggeringPrincipal(); } nsCOMPtr<nsIContentSecurityPolicy> originalCSP; principalToInherit->GetCsp(getter_AddRefs(originalCSP)); if (originalCSP) { // if the principalToInherit had a CSP, // add it to the newly created NullPrincipal // (unless it already has one) nsCOMPtr<nsIContentSecurityPolicy> nullPrincipalCSP; (*aPrincipal)->GetCsp(getter_AddRefs(nullPrincipalCSP)); if (nullPrincipalCSP) { MOZ_ASSERT(nullPrincipalCSP == originalCSP, "There should be no other CSP here."); // CSPs are equal, no need to set it again. return NS_OK; } else { nsresult rv = (*aPrincipal)->SetCsp(originalCSP); NS_ENSURE_SUCCESS(rv, rv); } } } } 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->PrincipalToInherit(); if (!principalToInherit) { principalToInherit = loadInfo->TriggeringPrincipal(); } principalToInherit.forget(aPrincipal); return NS_OK; } nsSecurityFlags securityFlags = 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() && (securityFlags == nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_INHERITS || securityFlags == nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS || securityFlags == nsILoadInfo::SEC_REQUIRE_CORS_DATA_INHERITS)) { nsCOMPtr<nsIURI> uri; nsresult rv = NS_GetFinalChannelURI(aChannel, getter_AddRefs(uri)); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr<nsIPrincipal> principalToInherit = loadInfo->PrincipalToInherit(); if (!principalToInherit) { principalToInherit = loadInfo->TriggeringPrincipal(); } bool inheritForAboutBlank = loadInfo->GetAboutBlankInherits(); if (nsContentUtils::ChannelShouldInheritPrincipal(principalToInherit, uri, inheritForAboutBlank, false)) { principalToInherit.forget(aPrincipal); return NS_OK; } } } return GetChannelURIPrincipal(aChannel, aPrincipal); } nsresult nsScriptSecurityManager::MaybeSetAddonIdFromURI(PrincipalOriginAttributes& aAttrs, nsIURI* aURI) { nsAutoCString scheme; nsresult rv = aURI->GetScheme(scheme); NS_ENSURE_SUCCESS(rv, rv); if (scheme.EqualsLiteral("moz-extension") && GetAddonPolicyService()) { rv = GetAddonPolicyService()->ExtensionURIToAddonId(aURI, aAttrs.mAddonId); NS_ENSURE_SUCCESS(rv, rv); } 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) { NS_PRECONDITION(aChannel, "Must have channel!"); // Get the principal from the URI. Make sure this does the same thing // as nsDocument::Reset and XULDocument::StartDocumentLoad. nsCOMPtr<nsIURI> uri; nsresult rv = NS_GetFinalChannelURI(aChannel, getter_AddRefs(uri)); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr<nsILoadInfo> loadInfo; aChannel->GetLoadInfo(getter_AddRefs(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. PrincipalOriginAttributes attrs; // For addons loadInfo might be null. if (loadInfo) { attrs.InheritFromNecko(loadInfo->GetOriginAttributes()); } rv = MaybeSetAddonIdFromURI(attrs, uri); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr<nsIPrincipal> prin = BasePrincipal::CreateCodebasePrincipal(uri, attrs); prin.forget(aPrincipal); return *aPrincipal ? NS_OK : NS_ERROR_FAILURE; } NS_IMETHODIMP nsScriptSecurityManager::IsSystemPrincipal(nsIPrincipal* aPrincipal, bool* aIsSystem) { *aIsSystem = (aPrincipal == mSystemPrincipal); return NS_OK; } ///////////////////////////// // nsScriptSecurityManager // ///////////////////////////// //////////////////////////////////// // Methods implementing ISupports // //////////////////////////////////// NS_IMPL_ISUPPORTS(nsScriptSecurityManager, nsIScriptSecurityManager, nsIObserver) /////////////////////////////////////////////////// // Methods implementing nsIScriptSecurityManager // /////////////////////////////////////////////////// ///////////////// Security Checks ///////////////// bool nsScriptSecurityManager::ContentSecurityPolicyPermitsJSAction(JSContext *cx) { MOZ_ASSERT(cx == nsContentUtils::GetCurrentJSContext()); nsCOMPtr<nsIPrincipal> subjectPrincipal = nsContentUtils::SubjectPrincipal(); nsCOMPtr<nsIContentSecurityPolicy> 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; 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) { nsAutoString fileName; unsigned lineNum = 0; NS_NAMED_LITERAL_STRING(scriptSample, "call to eval() or related function blocked by CSP"); JS::AutoFilename scriptFilename; if (JS::DescribeScriptedCaller(cx, &scriptFilename, &lineNum)) { if (const char *file = scriptFilename.get()) { CopyUTF8toUTF16(nsDependentCString(file), fileName); } } else { MOZ_ASSERT(!JS_IsExceptionPending(cx)); } csp->LogViolationDetails(nsIContentSecurityPolicy::VIOLATION_TYPE_EVAL, fileName, scriptSample, lineNum, 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) { if (!SecurityCompareURIs(aSourceURI, aTargetURI)) { if (reportError) { ReportError(nullptr, NS_LITERAL_STRING("CheckSameOriginError"), aSourceURI, aTargetURI); } return NS_ERROR_DOM_BAD_URI; } return NS_OK; } /*static*/ uint32_t nsScriptSecurityManager::HashPrincipalByOrigin(nsIPrincipal* aPrincipal) { nsCOMPtr<nsIURI> 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; } // See if we're attempting to load a file: URI. If so, let a // UniversalXPConnect capability trump the above check. bool isFile = false; bool isRes = false; if (NS_FAILED(aURI->SchemeIs("file", &isFile)) || NS_FAILED(aURI->SchemeIs("resource", &isRes))) return NS_ERROR_FAILURE; if (isFile || isRes) { if (nsContentUtils::IsCallerChrome()) 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) { NS_PRECONDITION(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) { // Make a clone of the incoming URI, because we're going to mutate it. nsCOMPtr<nsIURI> probe; nsresult rv = aProbeArg->Clone(getter_AddRefs(probe)); NS_ENSURE_SUCCESS(rv, false); 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 = probe->SetHost(newHost); NS_ENSURE_SUCCESS(rv, false); } } NS_IMETHODIMP nsScriptSecurityManager::CheckLoadURIWithPrincipal(nsIPrincipal* aPrincipal, nsIURI *aTargetURI, uint32_t aFlags) { NS_PRECONDITION(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; aPrincipal->GetURI(getter_AddRefs(sourceURI)); if (!sourceURI) { nsCOMPtr<nsIExpandedPrincipal> expanded = do_QueryInterface(aPrincipal); if (expanded) { nsTArray< nsCOMPtr<nsIPrincipal> > *whiteList; expanded->GetWhiteList(&whiteList); for (uint32_t i = 0; i < whiteList->Length(); ++i) { nsresult rv = CheckLoadURIWithPrincipal((*whiteList)[i], aTargetURI, aFlags); if (NS_SUCCEEDED(rv)) { // Allow access if it succeeded with one of the white listed principals return NS_OK; } } // None of our whitelisted 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<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 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); 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("http") && !sourceScheme.EqualsIgnoreCase("https")) && targetScheme.EqualsIgnoreCase("moz-icon")) { // Exception for linking to moz-icon://.ext?size=... // Note that because targetScheme is the base (innermost) URI scheme, // this does NOT allow e.g. file -> moz-icon:file:///... links. // This is intentional. return NS_OK; } // If we get here, check all the schemes can link to each other, from the top down: nsCaseInsensitiveCStringComparator stringComparator; nsCOMPtr<nsIURI> currentURI = sourceURI; nsCOMPtr<nsIURI> 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<nsIAboutModule> 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); } // Otherwise... check if we can nest another level: nsCOMPtr<nsINestedURI> nestedURI = do_QueryInterface(currentURI); nsCOMPtr<nsINestedURI> 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) { // 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); NS_NAMED_LITERAL_STRING(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(nullptr, errorTag, aSourceURI, aTargetURI); } return rv; } // Check for chrome target URI bool hasFlags = false; rv = NS_URIChainHasFlags(aTargetBaseURI, nsIProtocolHandler::URI_IS_UI_RESOURCE, &hasFlags); NS_ENSURE_SUCCESS(rv, rv); if (hasFlags) { if (aFlags & nsIScriptSecurityManager::ALLOW_CHROME) { // For now, don't change behavior for resource:// and // just allow it. This is required for extensions injecting // extension-internal resource URLs in snippets in pages, e.g. // Adding custom controls in-page. if (!targetScheme.EqualsLiteral("chrome") && !targetScheme.EqualsLiteral("moz-icon")) { return NS_OK; } // 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; } // Allow the load only if the chrome package is whitelisted. nsCOMPtr<nsIXULChromeRegistry> reg(do_GetService( NS_CHROMEREGISTRY_CONTRACTID)); if (reg) { bool accessAllowed = false; reg->AllowContentToAccess(aTargetBaseURI, &accessAllowed); if (accessAllowed) { return NS_OK; } } } // Special-case the hidden window: it's allowed to load // URI_IS_UI_RESOURCE no matter what. Bug 1145470 tracks removing this. nsAutoCString sourceSpec; if (NS_SUCCEEDED(aSourceBaseURI->GetSpec(sourceSpec)) && sourceSpec.EqualsLiteral("resource://gre-resources/hiddenWindow.html")) { return NS_OK; } if (reportErrors) { ReportError(nullptr, errorTag, aSourceURI, aTargetURI); } 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 whitelisted in the prefs. In 99.9% of cases, // this array is empty. for (nsIURI* uri : EnsureFileURIWhitelist()) { if (EqualOrSubdomain(aSourceURI, uri)) { return NS_OK; } } // Allow chrome:// bool isChrome = false; if (NS_SUCCEEDED(aSourceBaseURI->SchemeIs("chrome", &isChrome)) && isChrome) { return NS_OK; } // Nothing else. if (reportErrors) { ReportError(nullptr, errorTag, aSourceURI, aTargetURI); } 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) { nsXPIDLString message; NS_ConvertASCIItoUTF16 ucsTargetScheme(targetScheme); const char16_t* formatStrings[] = { ucsTargetScheme.get() }; rv = sStrBundle-> FormatStringFromName(u"ProtocolFlagError", formatStrings, ArrayLength(formatStrings), getter_Copies(message)); if (NS_SUCCEEDED(rv)) { nsCOMPtr<nsIConsoleService> console( do_GetService("@mozilla.org/consoleservice;1")); NS_ENSURE_TRUE(console, NS_ERROR_FAILURE); console->LogStringMessage(message.get()); } } return NS_OK; } nsresult nsScriptSecurityManager::ReportError(JSContext* cx, const nsAString& messageTag, nsIURI* aSource, nsIURI* aTarget) { 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); // Localize the error message nsXPIDLString message; NS_ConvertASCIItoUTF16 ucsSourceSpec(sourceSpec); NS_ConvertASCIItoUTF16 ucsTargetSpec(targetSpec); const char16_t *formatStrings[] = { ucsSourceSpec.get(), ucsTargetSpec.get() }; rv = sStrBundle->FormatStringFromName(PromiseFlatString(messageTag).get(), formatStrings, ArrayLength(formatStrings), getter_Copies(message)); NS_ENSURE_SUCCESS(rv, rv); // If a JS context was passed in, set a JS exception. // Otherwise, print the error message directly to the JS console // and to standard output if (cx) { SetPendingException(cx, message.get()); } else // Print directly to the console { nsCOMPtr<nsIConsoleService> console( do_GetService("@mozilla.org/consoleservice;1")); NS_ENSURE_TRUE(console, NS_ERROR_FAILURE); console->LogStringMessage(message.get()); } return NS_OK; } NS_IMETHODIMP nsScriptSecurityManager::CheckLoadURIStrWithPrincipal(nsIPrincipal* aPrincipal, const nsACString& aTargetURIStr, uint32_t aFlags) { nsresult rv; nsCOMPtr<nsIURI> 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<nsIURIFixup> fixup = do_GetService(NS_URIFIXUP_CONTRACTID); 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; } ///////////////// Principals /////////////////////// NS_IMETHODIMP nsScriptSecurityManager::GetSystemPrincipal(nsIPrincipal **result) { NS_ADDREF(*result = mSystemPrincipal); return NS_OK; } NS_IMETHODIMP nsScriptSecurityManager::GetNoAppCodebasePrincipal(nsIURI* aURI, nsIPrincipal** aPrincipal) { PrincipalOriginAttributes attrs(NO_APP_ID, false); nsCOMPtr<nsIPrincipal> prin = BasePrincipal::CreateCodebasePrincipal(aURI, attrs); prin.forget(aPrincipal); return *aPrincipal ? NS_OK : NS_ERROR_FAILURE; } NS_IMETHODIMP nsScriptSecurityManager::GetCodebasePrincipal(nsIURI* aURI, nsIPrincipal** aPrincipal) { return GetNoAppCodebasePrincipal(aURI, aPrincipal); } NS_IMETHODIMP nsScriptSecurityManager::CreateCodebasePrincipal(nsIURI* aURI, JS::Handle<JS::Value> aOriginAttributes, JSContext* aCx, nsIPrincipal** aPrincipal) { PrincipalOriginAttributes attrs; if (!aOriginAttributes.isObject() || !attrs.Init(aCx, aOriginAttributes)) { return NS_ERROR_INVALID_ARG; } nsCOMPtr<nsIPrincipal> 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<nsIPrincipal> prin = BasePrincipal::CreateCodebasePrincipal(aOrigin); prin.forget(aPrincipal); return *aPrincipal ? NS_OK : NS_ERROR_FAILURE; } NS_IMETHODIMP nsScriptSecurityManager::CreateNullPrincipal(JS::Handle<JS::Value> aOriginAttributes, JSContext* aCx, nsIPrincipal** aPrincipal) { PrincipalOriginAttributes attrs; if (!aOriginAttributes.isObject() || !attrs.Init(aCx, aOriginAttributes)) { return NS_ERROR_INVALID_ARG; } nsCOMPtr<nsIPrincipal> prin = nsNullPrincipal::Create(attrs); prin.forget(aPrincipal); return NS_OK; } NS_IMETHODIMP nsScriptSecurityManager::GetAppCodebasePrincipal(nsIURI* aURI, uint32_t aAppId, bool aInIsolatedMozBrowser, nsIPrincipal** aPrincipal) { NS_ENSURE_TRUE(aAppId != nsIScriptSecurityManager::UNKNOWN_APP_ID, NS_ERROR_INVALID_ARG); PrincipalOriginAttributes attrs(aAppId, aInIsolatedMozBrowser); nsCOMPtr<nsIPrincipal> prin = BasePrincipal::CreateCodebasePrincipal(aURI, attrs); prin.forget(aPrincipal); return *aPrincipal ? NS_OK : NS_ERROR_FAILURE; } NS_IMETHODIMP nsScriptSecurityManager:: GetLoadContextCodebasePrincipal(nsIURI* aURI, nsILoadContext* aLoadContext, nsIPrincipal** aPrincipal) { NS_ENSURE_STATE(aLoadContext); DocShellOriginAttributes docShellAttrs; bool result = aLoadContext->GetOriginAttributes(docShellAttrs);; NS_ENSURE_TRUE(result, NS_ERROR_FAILURE); PrincipalOriginAttributes attrs; attrs.InheritFromDocShellToDoc(docShellAttrs, aURI); nsresult rv = MaybeSetAddonIdFromURI(attrs, aURI); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr<nsIPrincipal> prin = BasePrincipal::CreateCodebasePrincipal(aURI, attrs); prin.forget(aPrincipal); return *aPrincipal ? NS_OK : NS_ERROR_FAILURE; } NS_IMETHODIMP nsScriptSecurityManager::GetDocShellCodebasePrincipal(nsIURI* aURI, nsIDocShell* aDocShell, nsIPrincipal** aPrincipal) { PrincipalOriginAttributes attrs; attrs.InheritFromDocShellToDoc(nsDocShell::Cast(aDocShell)->GetOriginAttributes(), aURI); nsresult rv = MaybeSetAddonIdFromURI(attrs, aURI); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr<nsIPrincipal> prin = BasePrincipal::CreateCodebasePrincipal(aURI, attrs); prin.forget(aPrincipal); return *aPrincipal ? NS_OK : NS_ERROR_FAILURE; } // static nsIPrincipal* nsScriptSecurityManager::doGetObjectPrincipal(JSObject *aObj) { JSCompartment *compartment = js::GetObjectCompartment(aObj); JSPrincipals *principals = JS_GetCompartmentPrincipals(compartment); return nsJSPrincipals::get(principals); } NS_IMETHODIMP nsScriptSecurityManager::CanCreateWrapper(JSContext *cx, const nsIID &aIID, nsISupports *aObj, nsIClassInfo *aClassInfo) { // XXX Special case for nsIXPCException ? ClassInfoData objClassInfo = ClassInfoData(aClassInfo, nullptr); if (objClassInfo.IsDOMClass()) { return NS_OK; } // We give remote-XUL whitelisted domains a free pass here. See bug 932906. if (!xpc::AllowContentXBLScope(js::GetContextCompartment(cx))) { return NS_OK; } if (nsContentUtils::IsCallerChrome()) { return NS_OK; } //-- Access denied, report an error NS_ConvertUTF8toUTF16 strName("CreateWrapperDenied"); nsAutoCString origin; nsIPrincipal* subjectPrincipal = nsContentUtils::SubjectPrincipal(); GetPrincipalDomainOrigin(subjectPrincipal, origin); NS_ConvertUTF8toUTF16 originUnicode(origin); NS_ConvertUTF8toUTF16 classInfoName(objClassInfo.GetName()); const char16_t* formatStrings[] = { classInfoName.get(), originUnicode.get() }; uint32_t length = ArrayLength(formatStrings); if (originUnicode.IsEmpty()) { --length; } else { strName.AppendLiteral("ForOrigin"); } nsXPIDLString errorMsg; nsresult rv = sStrBundle->FormatStringFromName(strName.get(), formatStrings, length, getter_Copies(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; } ///////////////////////////////////// // Method implementing nsIObserver // ///////////////////////////////////// const char sJSEnabledPrefName[] = "javascript.enabled"; const char sFileOriginPolicyPrefName[] = "security.fileuri.strict_origin_policy"; static const char* kObservedPrefs[] = { sJSEnabledPrefName, sFileOriginPolicyPrefName, "capability.policy.", nullptr }; NS_IMETHODIMP nsScriptSecurityManager::Observe(nsISupports* aObject, const char* aTopic, const char16_t* aMessage) { ScriptSecurityPrefChanged(); return NS_OK; } ///////////////////////////////////////////// // 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(); nsCOMPtr<nsIStringBundleService> bundleService = mozilla::services::GetStringBundleService(); if (!bundleService) return NS_ERROR_FAILURE; rv = bundleService->CreateBundle("chrome://global/locale/security/caps.properties", &sStrBundle); NS_ENSURE_SUCCESS(rv, rv); // Create our system principal singleton RefPtr<nsSystemPrincipal> system = new nsSystemPrincipal(); 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<nsScriptSecurityManager> gScriptSecMan; nsScriptSecurityManager::~nsScriptSecurityManager(void) { Preferences::RemoveObservers(this, kObservedPrefs); 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); NS_IF_RELEASE(sStrBundle); } nsScriptSecurityManager * nsScriptSecurityManager::GetScriptSecurityManager() { return gScriptSecMan; } /* static */ void nsScriptSecurityManager::InitStatics() { RefPtr<nsScriptSecurityManager> 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. nsSystemPrincipal * nsScriptSecurityManager::SystemPrincipalSingletonConstructor() { nsIPrincipal *sysprin = nullptr; if (gScriptSecMan) NS_ADDREF(sysprin = gScriptSecMan->mSystemPrincipal); return static_cast<nsSystemPrincipal*>(sysprin); } struct IsWhitespace { static bool Test(char aChar) { return NS_IsAsciiWhitespace(aChar); }; }; struct IsWhitespaceOrComma { static bool Test(char aChar) { return aChar == ',' || NS_IsAsciiWhitespace(aChar); }; }; template <typename Predicate> uint32_t SkipPast(const nsCString& str, uint32_t base) { while (base < str.Length() && Predicate::Test(str[base])) { ++base; } return base; } template <typename Predicate> uint32_t SkipUntil(const nsCString& str, uint32_t base) { while (base < str.Length() && !Predicate::Test(str[base])) { ++base; } return base; } inline void nsScriptSecurityManager::ScriptSecurityPrefChanged() { MOZ_ASSERT(mPrefInitialized); mIsJavaScriptEnabled = Preferences::GetBool(sJSEnabledPrefName, mIsJavaScriptEnabled); sStrictFileOriginPolicy = Preferences::GetBool(sFileOriginPolicyPrefName, false); mFileURIWhitelist.reset(); } void nsScriptSecurityManager::AddSitesToFileURIWhitelist(const nsCString& aSiteList) { for (uint32_t base = SkipPast<IsWhitespace>(aSiteList, 0), bound = 0; base < aSiteList.Length(); base = SkipPast<IsWhitespace>(aSiteList, bound)) { // Grab the current site. bound = SkipUntil<IsWhitespace>(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))) { AddSitesToFileURIWhitelist(NS_LITERAL_CSTRING("http://") + site); AddSitesToFileURIWhitelist(NS_LITERAL_CSTRING("https://") + site); continue; } // Convert it to a URI and add it to our list. nsCOMPtr<nsIURI> uri; nsresult rv = NS_NewURI(getter_AddRefs(uri), site, nullptr, nullptr, sIOService); if (NS_SUCCEEDED(rv)) { mFileURIWhitelist.ref().AppendElement(uri); } else { nsCOMPtr<nsIConsoleService> console(do_GetService("@mozilla.org/consoleservice;1")); if (console) { nsAutoString msg = NS_LITERAL_STRING("Unable to to add site to file:// URI whitelist: ") + 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::AddStrongObservers(this, kObservedPrefs); 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<nsIDomainPolicy> 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 blacklist or the whitelist, depending on whether script // is enabled or disabled by default). nsCOMPtr<nsIDomainSet> exceptions; nsCOMPtr<nsIDomainSet> superExceptions; if (*aRv) { mDomainPolicy->GetBlacklist(getter_AddRefs(exceptions)); mDomainPolicy->GetSuperBlacklist(getter_AddRefs(superExceptions)); } else { mDomainPolicy->GetWhitelist(getter_AddRefs(exceptions)); mDomainPolicy->GetSuperWhitelist(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<nsCOMPtr<nsIURI>>& nsScriptSecurityManager::EnsureFileURIWhitelist() { if (mFileURIWhitelist.isSome()) { return mFileURIWhitelist.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. // mFileURIWhitelist.emplace(); auto policies = mozilla::Preferences::GetCString("capability.policy.policynames"); for (uint32_t base = SkipPast<IsWhitespaceOrComma>(policies, 0), bound = 0; base < policies.Length(); base = SkipPast<IsWhitespaceOrComma>(policies, bound)) { // Grab the current policy name. bound = SkipUntil<IsWhitespaceOrComma>(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"); if (!Preferences::GetString(checkLoadURIPrefName.get()).LowerCaseEqualsLiteral("allaccess")) { continue; } // Grab the list of domains associated with this policy. nsCString domainPrefName = NS_LITERAL_CSTRING("capability.policy.") + policyName + NS_LITERAL_CSTRING(".sites"); auto siteList = Preferences::GetCString(domainPrefName.get()); AddSitesToFileURIWhitelist(siteList); } return mFileURIWhitelist.ref(); }