/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* vim: set ts=4 et sw=4 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "mozilla/Util.h" #include "xpcprivate.h" #include "XPCWrapper.h" #include "nsScriptSecurityManager.h" #include "nsIServiceManager.h" #include "nsIScriptObjectPrincipal.h" #include "nsIScriptContext.h" #include "nsIURL.h" #include "nsINestedURI.h" #include "nspr.h" #include "nsJSPrincipals.h" #include "nsSystemPrincipal.h" #include "nsPrincipal.h" #include "nsNullPrincipal.h" #include "nsXPIDLString.h" #include "nsCRT.h" #include "nsCRTGlue.h" #include "nsError.h" #include "nsDOMCID.h" #include "jsdbgapi.h" #include "nsIXPConnect.h" #include "nsIXPCSecurityManager.h" #include "nsTextFormatter.h" #include "nsIStringBundle.h" #include "nsNetUtil.h" #include "nsIProperties.h" #include "nsDirectoryServiceDefs.h" #include "nsIFile.h" #include "nsIFileURL.h" #include "nsIZipReader.h" #include "nsIXPConnect.h" #include "nsIScriptGlobalObject.h" #include "nsPIDOMWindow.h" #include "nsIDocShell.h" #include "nsIPrompt.h" #include "nsIWindowWatcher.h" #include "nsIConsoleService.h" #include "nsISecurityCheckedComponent.h" #include "nsIJSRuntimeService.h" #include "nsIObserverService.h" #include "nsIContent.h" #include "nsAutoPtr.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 "mozilla/Preferences.h" #include "mozilla/dom/BindingUtils.h" #include "mozilla/StandardInteger.h" #include "mozilla/ClearOnShutdown.h" #include "mozilla/StaticPtr.h" #include "nsContentUtils.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; static NS_DEFINE_CID(kZipReaderCID, NS_ZIPREADER_CID); nsIIOService *nsScriptSecurityManager::sIOService = nullptr; nsIXPConnect *nsScriptSecurityManager::sXPConnect = nullptr; nsIStringBundle *nsScriptSecurityManager::sStrBundle = nullptr; JSRuntime *nsScriptSecurityManager::sRuntime = 0; bool nsScriptSecurityManager::sStrictFileOriginPolicy = true; bool nsScriptSecurityManager::SubjectIsPrivileged() { JSContext *cx = GetCurrentJSContext(); if (cx && xpc::IsUniversalXPConnectEnabled(cx)) return true; bool isSystem = false; return NS_SUCCEEDED(SubjectPrincipalIsSystem(&isSystem)) && isSystem; } /////////////////////////// // Convenience Functions // /////////////////////////// // Result of this function should not be freed. static inline const PRUnichar * IDToString(JSContext *cx, jsid id) { if (JSID_IS_STRING(id)) return JS_GetInternedStringChars(JSID_TO_STRING(id)); JSAutoRequest ar(cx); JS::Value idval; if (!JS_IdToValue(cx, id, &idval)) return nullptr; JSString *str = JS_ValueToString(cx, idval); if(!str) return nullptr; return JS_GetStringCharsZ(cx, str); } 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); } static nsIScriptContext * GetScriptContext(JSContext *cx) { return GetScriptContextFromJSContext(cx); } inline void SetPendingException(JSContext *cx, const char *aMsg) { JSAutoRequest ar(cx); JS_ReportError(cx, "%s", aMsg); } inline void SetPendingException(JSContext *cx, const PRUnichar *aMsg) { JSAutoRequest ar(cx); JS_ReportError(cx, "%hs", aMsg); } // DomainPolicy members uint32_t DomainPolicy::sGeneration = 0; // 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(aName)), mDidGetFlags(false), mMustFreeName(false) { } ~ClassInfoData() { if (mMustFreeName) nsMemory::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("UnnamedClass"); } } return mName; } private: nsIClassInfo *mClassInfo; // WEAK uint32_t mFlags; char *mName; bool mDidGetFlags; bool mMustFreeName; }; JSContext * nsScriptSecurityManager::GetCurrentJSContext() { // Get JSContext from stack. return sXPConnect->GetCurrentJSContext(); } JSContext * nsScriptSecurityManager::GetSafeJSContext() { // Get JSContext from stack. return sXPConnect->GetSafeJSContext(); } /* 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); } NS_IMETHODIMP nsScriptSecurityManager::GetChannelPrincipal(nsIChannel* aChannel, nsIPrincipal** aPrincipal) { NS_PRECONDITION(aChannel, "Must have channel!"); nsCOMPtr owner; aChannel->GetOwner(getter_AddRefs(owner)); if (owner) { CallQueryInterface(owner, aPrincipal); if (*aPrincipal) { return NS_OK; } } // OK, get the principal from the URI. Make sure this does the same thing // as nsDocument::Reset and XULDocument::StartDocumentLoad. nsCOMPtr uri; nsresult rv = NS_GetFinalChannelURI(aChannel, getter_AddRefs(uri)); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr docShell; NS_QueryNotificationCallbacks(aChannel, docShell); if (docShell) { return GetDocShellCodebasePrincipal(uri, docShell, aPrincipal); } return GetCodebasePrincipalInternal(uri, UNKNOWN_APP_ID, /* isInBrowserElement */ false, aPrincipal); } NS_IMETHODIMP nsScriptSecurityManager::IsSystemPrincipal(nsIPrincipal* aPrincipal, bool* aIsSystem) { *aIsSystem = (aPrincipal == mSystemPrincipal); return NS_OK; } NS_IMETHODIMP_(nsIPrincipal *) nsScriptSecurityManager::GetCxSubjectPrincipal(JSContext *cx) { NS_ASSERTION(cx == GetCurrentJSContext(), "Uh, cx is not the current JS context!"); nsresult rv = NS_ERROR_FAILURE; nsIPrincipal *principal = GetSubjectPrincipal(cx, &rv); if (NS_FAILED(rv)) return nullptr; return principal; } //////////////////// // Policy Storage // //////////////////// // Table of security levels static bool DeleteCapability(nsHashKey *aKey, void *aData, void* closure) { NS_Free(aData); return true; } //-- Per-Domain Policy - applies to one or more protocols or hosts struct DomainEntry { DomainEntry(const char* aOrigin, DomainPolicy* aDomainPolicy) : mOrigin(aOrigin), mDomainPolicy(aDomainPolicy), mNext(nullptr) { mDomainPolicy->Hold(); } ~DomainEntry() { mDomainPolicy->Drop(); } bool Matches(const char *anOrigin) { int len = strlen(anOrigin); int thisLen = mOrigin.Length(); if (len < thisLen) return false; if (mOrigin.RFindChar(':', thisLen-1, 1) != -1) //-- Policy applies to all URLs of this scheme, compare scheme only return mOrigin.EqualsIgnoreCase(anOrigin, thisLen); //-- Policy applies to a particular host; compare domains if (!mOrigin.Equals(anOrigin + (len - thisLen))) return false; if (len == thisLen) return true; char charBefore = anOrigin[len-thisLen-1]; return (charBefore == '.' || charBefore == ':' || charBefore == '/'); } nsCString mOrigin; DomainPolicy* mDomainPolicy; DomainEntry* mNext; }; static bool DeleteDomainEntry(nsHashKey *aKey, void *aData, void* closure) { DomainEntry *entry = (DomainEntry*) aData; do { DomainEntry *next = entry->mNext; delete entry; entry = next; } while (entry); return true; } ///////////////////////////// // nsScriptSecurityManager // ///////////////////////////// //////////////////////////////////// // Methods implementing ISupports // //////////////////////////////////// NS_IMPL_ISUPPORTS4(nsScriptSecurityManager, nsIScriptSecurityManager, nsIXPCSecurityManager, nsIChannelEventSink, nsIObserver) /////////////////////////////////////////////////// // Methods implementing nsIScriptSecurityManager // /////////////////////////////////////////////////// ///////////////// Security Checks ///////////////// JSBool nsScriptSecurityManager::ContentSecurityPolicyPermitsJSAction(JSContext *cx) { // Get the security manager nsScriptSecurityManager *ssm = nsScriptSecurityManager::GetScriptSecurityManager(); NS_ASSERTION(ssm, "Failed to get security manager service"); if (!ssm) return JS_FALSE; nsresult rv; nsIPrincipal* subjectPrincipal = ssm->GetSubjectPrincipal(cx, &rv); NS_ASSERTION(NS_SUCCEEDED(rv), "CSP: Failed to get nsIPrincipal from js context"); if (NS_FAILED(rv)) return JS_FALSE; // Not just absence of principal, but failure. if (!subjectPrincipal) return JS_TRUE; nsCOMPtr csp; 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 JS_TRUE; bool evalOK = true; bool reportViolation = false; rv = csp->GetAllowsEval(&reportViolation, &evalOK); if (NS_FAILED(rv)) { NS_WARNING("CSP: failed to get allowsEval"); return JS_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"); JSScript *script; if (JS_DescribeScriptedCaller(cx, &script, &lineNum)) { if (const char *file = JS_GetScriptFilename(cx, script)) { CopyUTF8toUTF16(nsDependentCString(file), fileName); } } csp->LogViolationDetails(nsIContentSecurityPolicy::VIOLATION_TYPE_EVAL, fileName, scriptSample, lineNum); } return evalOK; } JSBool nsScriptSecurityManager::CheckObjectAccess(JSContext *cx, JSHandleObject obj, JSHandleId id, JSAccessMode mode, JSMutableHandleValue vp) { // Get the security manager nsScriptSecurityManager *ssm = nsScriptSecurityManager::GetScriptSecurityManager(); NS_ASSERTION(ssm, "Failed to get security manager service"); if (!ssm) return JS_FALSE; // Get the object being accessed. We protect these cases: // 1. The Function.prototype.caller property's value, which might lead // an attacker up a call-stack to a function or another object from // a different trust domain. // 2. A user-defined getter or setter function accessible on another // trust domain's window or document object. // vp can be a primitive, in that case, we use obj as the target // object. JSObject* target = JSVAL_IS_PRIMITIVE(vp) ? obj : JSVAL_TO_OBJECT(vp); // Do the same-origin check -- this sets a JS exception if the check fails. // Pass the parent object's class name, as we have no class-info for it. nsresult rv = ssm->CheckPropertyAccess(cx, target, js::GetObjectClass(obj)->name, id, (mode & JSACC_WRITE) ? (int32_t)nsIXPCSecurityManager::ACCESS_SET_PROPERTY : (int32_t)nsIXPCSecurityManager::ACCESS_GET_PROPERTY); if (NS_FAILED(rv)) return JS_FALSE; // Security check failed (XXX was an error reported?) return JS_TRUE; } NS_IMETHODIMP nsScriptSecurityManager::CheckPropertyAccess(JSContext* cx, JSObject* aJSObject, const char* aClassName, jsid aProperty, uint32_t aAction) { return CheckPropertyAccessImpl(aAction, nullptr, cx, aJSObject, nullptr, nullptr, aClassName, aProperty, nullptr); } NS_IMETHODIMP nsScriptSecurityManager::CheckSameOrigin(JSContext* cx, nsIURI* aTargetURI) { nsresult rv; // Get a context if necessary if (!cx) { cx = GetCurrentJSContext(); if (!cx) return NS_OK; // No JS context, so allow access } // Get a principal from the context nsIPrincipal* sourcePrincipal = GetSubjectPrincipal(cx, &rv); if (NS_FAILED(rv)) return rv; if (!sourcePrincipal) { NS_WARNING("CheckSameOrigin called on script w/o principals; should this happen?"); return NS_OK; } if (sourcePrincipal == mSystemPrincipal) { // This is a system (chrome) script, so allow access return NS_OK; } // Get the original URI from the source principal. // This has the effect of ignoring any change to document.domain // which must be done to avoid DNS spoofing (bug 154930) nsCOMPtr sourceURI; sourcePrincipal->GetDomain(getter_AddRefs(sourceURI)); if (!sourceURI) { sourcePrincipal->GetURI(getter_AddRefs(sourceURI)); NS_ENSURE_TRUE(sourceURI, NS_ERROR_FAILURE); } // Compare origins if (!SecurityCompareURIs(sourceURI, aTargetURI)) { ReportError(cx, NS_LITERAL_STRING("CheckSameOriginError"), sourceURI, aTargetURI); return NS_ERROR_DOM_BAD_URI; } return NS_OK; } 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; } nsresult nsScriptSecurityManager::CheckPropertyAccessImpl(uint32_t aAction, nsAXPCNativeCallContext* aCallContext, JSContext* cx, JSObject* aJSObject, nsISupports* aObj, nsIClassInfo* aClassInfo, const char* aClassName, jsid aProperty, void** aCachedClassPolicy) { nsresult rv; nsIPrincipal* subjectPrincipal = GetSubjectPrincipal(cx, &rv); if (NS_FAILED(rv)) return rv; if (!subjectPrincipal || subjectPrincipal == mSystemPrincipal) // We have native code or the system principal: just allow access return NS_OK; nsCOMPtr objectPrincipal; // Hold the class info data here so we don't have to go back to virtual // methods all the time ClassInfoData classInfoData(aClassInfo, aClassName); //-- Look up the security policy for this class and subject domain SecurityLevel securityLevel; rv = LookupPolicy(subjectPrincipal, classInfoData, aProperty, aAction, (ClassPolicy**)aCachedClassPolicy, &securityLevel); if (NS_FAILED(rv)) return rv; if (securityLevel.level == SCRIPT_SECURITY_UNDEFINED_ACCESS) { // No policy found for this property so use the default of last resort. // If we were called from somewhere other than XPConnect // (no XPC call context), assume this is a DOM class. Otherwise, // ask the ClassInfo. if (!aCallContext || classInfoData.IsDOMClass()) securityLevel.level = SCRIPT_SECURITY_SAME_ORIGIN_ACCESS; else securityLevel.level = SCRIPT_SECURITY_NO_ACCESS; } if (SECURITY_ACCESS_LEVEL_FLAG(securityLevel)) // This flag means securityLevel is allAccess, noAccess, or sameOrigin { switch (securityLevel.level) { case SCRIPT_SECURITY_NO_ACCESS: rv = NS_ERROR_DOM_PROP_ACCESS_DENIED; break; case SCRIPT_SECURITY_ALL_ACCESS: rv = NS_OK; break; case SCRIPT_SECURITY_SAME_ORIGIN_ACCESS: { nsCOMPtr principalHolder; if(aJSObject) { objectPrincipal = doGetObjectPrincipal(aJSObject); if (!objectPrincipal) rv = NS_ERROR_DOM_SECURITY_ERR; } else { NS_ERROR("CheckPropertyAccessImpl called without a target object or URL"); return NS_ERROR_FAILURE; } if(NS_SUCCEEDED(rv)) rv = CheckSameOriginDOMProp(subjectPrincipal, objectPrincipal, aAction); break; } default: NS_ERROR("Bad Security Level Value"); return NS_ERROR_FAILURE; } } else // if SECURITY_ACCESS_LEVEL_FLAG is false, securityLevel is a capability { rv = SubjectIsPrivileged() ? NS_OK : NS_ERROR_DOM_SECURITY_ERR; } if (NS_SUCCEEDED(rv)) { return rv; } //--See if the object advertises a non-default level of access // using nsISecurityCheckedComponent nsCOMPtr checkedComponent = do_QueryInterface(aObj); nsXPIDLCString objectSecurityLevel; if (checkedComponent) { nsCOMPtr wrapper; nsCOMPtr interfaceInfo; const nsIID* objIID = nullptr; rv = aCallContext->GetCalleeWrapper(getter_AddRefs(wrapper)); if (NS_SUCCEEDED(rv) && wrapper) rv = wrapper->FindInterfaceWithMember(aProperty, getter_AddRefs(interfaceInfo)); if (NS_SUCCEEDED(rv) && interfaceInfo) rv = interfaceInfo->GetIIDShared(&objIID); if (NS_SUCCEEDED(rv) && objIID) { switch (aAction) { case nsIXPCSecurityManager::ACCESS_GET_PROPERTY: checkedComponent->CanGetProperty(objIID, IDToString(cx, aProperty), getter_Copies(objectSecurityLevel)); break; case nsIXPCSecurityManager::ACCESS_SET_PROPERTY: checkedComponent->CanSetProperty(objIID, IDToString(cx, aProperty), getter_Copies(objectSecurityLevel)); break; case nsIXPCSecurityManager::ACCESS_CALL_METHOD: checkedComponent->CanCallMethod(objIID, IDToString(cx, aProperty), getter_Copies(objectSecurityLevel)); } } } rv = CheckXPCPermissions(cx, aObj, aJSObject, subjectPrincipal, objectSecurityLevel); if (NS_FAILED(rv)) //-- Security tests failed, access is denied, report error { nsAutoString stringName; switch(aAction) { case nsIXPCSecurityManager::ACCESS_GET_PROPERTY: stringName.AssignLiteral("GetPropertyDeniedOrigins"); break; case nsIXPCSecurityManager::ACCESS_SET_PROPERTY: stringName.AssignLiteral("SetPropertyDeniedOrigins"); break; case nsIXPCSecurityManager::ACCESS_CALL_METHOD: stringName.AssignLiteral("CallMethodDeniedOrigins"); } // Null out objectPrincipal for now, so we don't leak information about // it. Whenever we can report different error strings to content and // the UI we can take this out again. objectPrincipal = nullptr; NS_ConvertUTF8toUTF16 className(classInfoData.GetName()); nsAutoCString subjectOrigin; nsAutoCString subjectDomain; if (!nsAutoInPrincipalDomainOriginSetter::sInPrincipalDomainOrigin) { nsCOMPtr uri, domain; subjectPrincipal->GetURI(getter_AddRefs(uri)); if (uri) { // Object principal might be expanded GetOriginFromURI(uri, subjectOrigin); } subjectPrincipal->GetDomain(getter_AddRefs(domain)); if (domain) { GetOriginFromURI(domain, subjectDomain); } } else { subjectOrigin.AssignLiteral("the security manager"); } NS_ConvertUTF8toUTF16 subjectOriginUnicode(subjectOrigin); NS_ConvertUTF8toUTF16 subjectDomainUnicode(subjectDomain); nsAutoCString objectOrigin; nsAutoCString objectDomain; if (!nsAutoInPrincipalDomainOriginSetter::sInPrincipalDomainOrigin && objectPrincipal) { nsCOMPtr uri, domain; objectPrincipal->GetURI(getter_AddRefs(uri)); if (uri) { // Object principal might be system GetOriginFromURI(uri, objectOrigin); } objectPrincipal->GetDomain(getter_AddRefs(domain)); if (domain) { GetOriginFromURI(domain, objectDomain); } } NS_ConvertUTF8toUTF16 objectOriginUnicode(objectOrigin); NS_ConvertUTF8toUTF16 objectDomainUnicode(objectDomain); nsXPIDLString errorMsg; const PRUnichar *formatStrings[] = { subjectOriginUnicode.get(), className.get(), IDToString(cx, aProperty), objectOriginUnicode.get(), subjectDomainUnicode.get(), objectDomainUnicode.get() }; uint32_t length = ArrayLength(formatStrings); // XXXbz Our localization system is stupid and can't handle not showing // some strings that get passed in. Which means that we have to get // our length precisely right: it has to be exactly the number of // strings our format string wants. This means we'll have to move // strings in the array as needed, sadly... if (nsAutoInPrincipalDomainOriginSetter::sInPrincipalDomainOrigin || !objectPrincipal) { stringName.AppendLiteral("OnlySubject"); length -= 3; } else { // default to a length that doesn't include the domains, then // increase it as needed. length -= 2; if (!subjectDomainUnicode.IsEmpty()) { stringName.AppendLiteral("SubjectDomain"); length += 1; } if (!objectDomainUnicode.IsEmpty()) { stringName.AppendLiteral("ObjectDomain"); length += 1; if (length != ArrayLength(formatStrings)) { // We have an object domain but not a subject domain. // Scoot our string over one slot. See the XXX comment // above for why we need to do this. formatStrings[length-1] = formatStrings[length]; } } } // We need to keep our existing failure rv and not override it // with a likely success code from the following string bundle // call in order to throw the correct security exception later. nsresult rv2 = sStrBundle->FormatStringFromName(stringName.get(), formatStrings, length, getter_Copies(errorMsg)); if (NS_FAILED(rv2)) { // Might just be missing the string... Do our best errorMsg = stringName; } SetPendingException(cx, errorMsg.get()); } return rv; } /* static */ nsresult nsScriptSecurityManager::CheckSameOriginPrincipal(nsIPrincipal* aSubject, nsIPrincipal* aObject) { /* ** Get origin of subject and object and compare. */ if (aSubject == aObject) return NS_OK; if (!AppAttributesEqual(aSubject, aObject)) { return NS_ERROR_DOM_PROP_ACCESS_DENIED; } // Default to false, and change if that turns out wrong. bool subjectSetDomain = false; bool objectSetDomain = false; nsCOMPtr subjectURI; nsCOMPtr objectURI; aSubject->GetDomain(getter_AddRefs(subjectURI)); if (!subjectURI) { aSubject->GetURI(getter_AddRefs(subjectURI)); } else { subjectSetDomain = true; } aObject->GetDomain(getter_AddRefs(objectURI)); if (!objectURI) { aObject->GetURI(getter_AddRefs(objectURI)); } else { objectSetDomain = true; } if (SecurityCompareURIs(subjectURI, objectURI)) { // If either the subject or the object has changed its principal by // explicitly setting document.domain then the other must also have // done so in order to be considered the same origin. This prevents // DNS spoofing based on document.domain (154930) // If both or neither explicitly set their domain, allow the access if (subjectSetDomain == objectSetDomain) return NS_OK; } /* ** Access tests failed, so now report error. */ return NS_ERROR_DOM_PROP_ACCESS_DENIED; } // It's important that // // CheckSameOriginPrincipal(A, B) == NS_OK // // imply // // HashPrincipalByOrigin(A) == HashPrincipalByOrigin(B) // // if principals A and B could ever be used as keys in a hashtable. // Violation of this invariant leads to spurious failures of hashtable // lookups. See bug 454850. /*static*/ uint32_t nsScriptSecurityManager::HashPrincipalByOrigin(nsIPrincipal* aPrincipal) { nsCOMPtr uri; aPrincipal->GetDomain(getter_AddRefs(uri)); if (!uri) aPrincipal->GetURI(getter_AddRefs(uri)); return SecurityHashURI(uri); } /* static */ bool nsScriptSecurityManager::AppAttributesEqual(nsIPrincipal* aFirst, nsIPrincipal* aSecond) { MOZ_ASSERT(aFirst && aSecond, "Don't pass null pointers!"); uint32_t firstAppId = nsIScriptSecurityManager::UNKNOWN_APP_ID; if (!aFirst->GetUnknownAppId()) { firstAppId = aFirst->GetAppId(); } uint32_t secondAppId = nsIScriptSecurityManager::UNKNOWN_APP_ID; if (!aSecond->GetUnknownAppId()) { secondAppId = aSecond->GetAppId(); } return ((firstAppId == secondAppId) && (aFirst->GetIsInBrowserElement() == aSecond->GetIsInBrowserElement())); } nsresult nsScriptSecurityManager::CheckSameOriginDOMProp(nsIPrincipal* aSubject, nsIPrincipal* aObject, uint32_t aAction) { nsresult rv; bool subsumes; rv = aSubject->Subsumes(aObject, &subsumes); if (NS_SUCCEEDED(rv) && !subsumes) { rv = NS_ERROR_DOM_PROP_ACCESS_DENIED; } if (NS_SUCCEEDED(rv)) return NS_OK; /* * Content can't ever touch chrome (we check for UniversalXPConnect later) */ if (aObject == mSystemPrincipal) return NS_ERROR_DOM_PROP_ACCESS_DENIED; /* ** Access tests failed, so now report error. */ return NS_ERROR_DOM_PROP_ACCESS_DENIED; } nsresult nsScriptSecurityManager::LookupPolicy(nsIPrincipal* aPrincipal, ClassInfoData& aClassData, jsid aProperty, uint32_t aAction, ClassPolicy** aCachedClassPolicy, SecurityLevel* result) { nsresult rv; result->level = SCRIPT_SECURITY_UNDEFINED_ACCESS; DomainPolicy* dpolicy = nullptr; //-- Initialize policies if necessary if (mPolicyPrefsChanged) { if (!mPrefInitialized) { rv = InitPrefs(); NS_ENSURE_SUCCESS(rv, rv); } rv = InitPolicies(); if (NS_FAILED(rv)) return rv; } else { aPrincipal->GetSecurityPolicy((void**)&dpolicy); } if (!dpolicy && mOriginToPolicyMap) { //-- Look up the relevant domain policy, if any if (nsCOMPtr exp = do_QueryInterface(aPrincipal)) { // For expanded principals domain origin is not defined so let's just // use the default policy dpolicy = mDefaultPolicy; } else { nsAutoCString origin; rv = GetPrincipalDomainOrigin(aPrincipal, origin); NS_ENSURE_SUCCESS(rv, rv); char *start = origin.BeginWriting(); const char *nextToLastDot = nullptr; const char *lastDot = nullptr; const char *colon = nullptr; char *p = start; //-- search domain (stop at the end of the string or at the 3rd slash) for (uint32_t slashes=0; *p; p++) { if (*p == '/' && ++slashes == 3) { *p = '\0'; // truncate at 3rd slash break; } if (*p == '.') { nextToLastDot = lastDot; lastDot = p; } else if (!colon && *p == ':') colon = p; } nsCStringKey key(nextToLastDot ? nextToLastDot+1 : start); DomainEntry *de = (DomainEntry*) mOriginToPolicyMap->Get(&key); if (!de) { nsAutoCString scheme(start, colon-start+1); nsCStringKey schemeKey(scheme); de = (DomainEntry*) mOriginToPolicyMap->Get(&schemeKey); } while (de) { if (de->Matches(start)) { dpolicy = de->mDomainPolicy; break; } de = de->mNext; } if (!dpolicy) dpolicy = mDefaultPolicy; } aPrincipal->SetSecurityPolicy((void*)dpolicy); } ClassPolicy* cpolicy = nullptr; if ((dpolicy == mDefaultPolicy) && aCachedClassPolicy) { // No per-domain policy for this principal (the more common case) // so look for a cached class policy from the object wrapper cpolicy = *aCachedClassPolicy; } if (!cpolicy) { //-- No cached policy for this class, need to look it up cpolicy = static_cast (PL_DHashTableOperate(dpolicy, aClassData.GetName(), PL_DHASH_LOOKUP)); if (PL_DHASH_ENTRY_IS_FREE(cpolicy)) cpolicy = NO_POLICY_FOR_CLASS; if ((dpolicy == mDefaultPolicy) && aCachedClassPolicy) *aCachedClassPolicy = cpolicy; } NS_ASSERTION(JSID_IS_INT(aProperty) || JSID_IS_OBJECT(aProperty) || JSID_IS_STRING(aProperty), "Property must be a valid id"); // Only atomized strings are stored in the policies' hash tables. if (!JSID_IS_STRING(aProperty)) return NS_OK; JSString *propertyKey = JSID_TO_STRING(aProperty); // We look for a PropertyPolicy in the following places: // 1) The ClassPolicy for our class we got from our DomainPolicy // 2) The mWildcardPolicy of our DomainPolicy // 3) The ClassPolicy for our class we got from mDefaultPolicy // 4) The mWildcardPolicy of our mDefaultPolicy PropertyPolicy* ppolicy = nullptr; if (cpolicy != NO_POLICY_FOR_CLASS) { ppolicy = static_cast (PL_DHashTableOperate(cpolicy->mPolicy, propertyKey, PL_DHASH_LOOKUP)); } // If there is no class policy for this property, and we have a wildcard // policy, try that. if (dpolicy->mWildcardPolicy && (!ppolicy || PL_DHASH_ENTRY_IS_FREE(ppolicy))) { ppolicy = static_cast (PL_DHashTableOperate(dpolicy->mWildcardPolicy->mPolicy, propertyKey, PL_DHASH_LOOKUP)); } // If dpolicy is not the defauly policy and there's no class or wildcard // policy for this property, check the default policy for this class and // the default wildcard policy if (dpolicy != mDefaultPolicy && (!ppolicy || PL_DHASH_ENTRY_IS_FREE(ppolicy))) { cpolicy = static_cast (PL_DHashTableOperate(mDefaultPolicy, aClassData.GetName(), PL_DHASH_LOOKUP)); if (PL_DHASH_ENTRY_IS_BUSY(cpolicy)) { ppolicy = static_cast (PL_DHashTableOperate(cpolicy->mPolicy, propertyKey, PL_DHASH_LOOKUP)); } if ((!ppolicy || PL_DHASH_ENTRY_IS_FREE(ppolicy)) && mDefaultPolicy->mWildcardPolicy) { ppolicy = static_cast (PL_DHashTableOperate(mDefaultPolicy->mWildcardPolicy->mPolicy, propertyKey, PL_DHASH_LOOKUP)); } } if (!ppolicy || PL_DHASH_ENTRY_IS_FREE(ppolicy)) return NS_OK; // Get the correct security level from the property policy if (aAction == nsIXPCSecurityManager::ACCESS_SET_PROPERTY) *result = ppolicy->mSet; else *result = ppolicy->mGet; return NS_OK; } NS_IMETHODIMP nsScriptSecurityManager::CheckLoadURIFromScript(JSContext *cx, nsIURI *aURI) { // Get principal of currently executing script. nsresult rv; nsIPrincipal* principal = GetSubjectPrincipal(cx, &rv); if (NS_FAILED(rv)) return rv; // Native code can load all URIs. if (!principal) return NS_OK; 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 (SubjectIsPrivileged()) 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"); SetPendingException(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; } 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 sourceURI; aPrincipal->GetURI(getter_AddRefs(sourceURI)); if (!sourceURI) { nsCOMPtr expanded = do_QueryInterface(aPrincipal); if (expanded) { nsTArray< nsCOMPtr > *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 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; } NS_NAMED_LITERAL_STRING(errorTag, "CheckLoadURIError"); bool reportErrors = !(aFlags & nsIScriptSecurityManager::DONT_REPORT_ERRORS); // 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) { return aPrincipal->CheckMayLoad(targetBaseURI, true, false); } //-- get the source scheme nsAutoCString sourceScheme; rv = sourceBaseURI->GetScheme(sourceScheme); if (NS_FAILED(rv)) return rv; if (sourceScheme.LowerCaseEqualsLiteral(NS_NULLPRINCIPAL_SCHEME)) { // A null principal can target its own URI. if (sourceURI == aTargetURI) { return NS_OK; } } else if (targetScheme.Equals(sourceScheme, nsCaseInsensitiveCStringComparator())) { // every scheme can access another URI from the same scheme, // as long as they don't represent null principals... // Or they don't require an special permission to do so // See bug#773886 bool hasFlags; rv = NS_URIChainHasFlags(targetBaseURI, nsIProtocolHandler::URI_CROSS_ORIGIN_NEEDS_WEBAPPS_PERM, &hasFlags); NS_ENSURE_SUCCESS(rv, rv); if (hasFlags) { // In this case, we allow opening only if the source and target URIS // are on the same domain, or the opening URI has the webapps // permision granted if (!SecurityCompareURIs(sourceBaseURI,targetBaseURI) && !nsContentUtils::IsExactSitePermAllow(aPrincipal,WEBAPPS_PERM_NAME)){ return NS_ERROR_DOM_BAD_URI; } } return NS_OK; } // If the schemes don't match, the policy is specified by the protocol // flags on the target URI. Note that the order of policy checks here is // very important! We start from most restrictive and work our way down. // Note that since we're working with the innermost URI, we can just use // the methods that work on chains of nested URIs and they will only look // at the flags for our one URI. // Check for system target URI rv = DenyAccessIfURIHasFlags(targetBaseURI, nsIProtocolHandler::URI_DANGEROUS_TO_LOAD); if (NS_FAILED(rv)) { // Deny access, since the origin principal is not system if (reportErrors) { ReportError(nullptr, errorTag, sourceURI, aTargetURI); } return rv; } // Check for chrome target URI rv = NS_URIChainHasFlags(targetBaseURI, nsIProtocolHandler::URI_IS_UI_RESOURCE, &hasFlags); NS_ENSURE_SUCCESS(rv, rv); if (hasFlags) { if (aFlags & nsIScriptSecurityManager::ALLOW_CHROME) { if (!targetScheme.EqualsLiteral("chrome")) { // for now don't change behavior for resource: or moz-icon: return NS_OK; } // allow load only if chrome package is whitelisted nsCOMPtr reg(do_GetService( NS_CHROMEREGISTRY_CONTRACTID)); if (reg) { bool accessAllowed = false; reg->AllowContentToAccess(targetBaseURI, &accessAllowed); if (accessAllowed) { return NS_OK; } } } // resource: and chrome: are equivalent, securitywise // That's bogus!! Fix this. But watch out for // the view-source stylesheet? bool sourceIsChrome; rv = NS_URIChainHasFlags(sourceBaseURI, nsIProtocolHandler::URI_IS_UI_RESOURCE, &sourceIsChrome); NS_ENSURE_SUCCESS(rv, rv); if (sourceIsChrome) { return NS_OK; } if (reportErrors) { ReportError(nullptr, errorTag, sourceURI, aTargetURI); } return NS_ERROR_DOM_BAD_URI; } // Check for target URI pointing to a file rv = NS_URIChainHasFlags(targetBaseURI, nsIProtocolHandler::URI_IS_LOCAL_FILE, &hasFlags); NS_ENSURE_SUCCESS(rv, rv); if (hasFlags) { // resource: and chrome: are equivalent, securitywise // That's bogus!! Fix this. But watch out for // the view-source stylesheet? bool sourceIsChrome; rv = NS_URIChainHasFlags(sourceURI, nsIProtocolHandler::URI_IS_UI_RESOURCE, &sourceIsChrome); NS_ENSURE_SUCCESS(rv, rv); if (sourceIsChrome) { return NS_OK; } // Now check capability policies static const char loadURIPrefGroup[] = "checkloaduri"; ClassInfoData nameData(nullptr, loadURIPrefGroup); SecurityLevel secLevel; rv = LookupPolicy(aPrincipal, nameData, sEnabledID, nsIXPCSecurityManager::ACCESS_GET_PROPERTY, nullptr, &secLevel); if (NS_SUCCEEDED(rv) && secLevel.level == SCRIPT_SECURITY_ALL_ACCESS) { // OK for this site! return NS_OK; } if (reportErrors) { ReportError(nullptr, errorTag, sourceURI, 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(targetBaseURI, nsIProtocolHandler::URI_LOADABLE_BY_ANYONE, &hasFlags); NS_ENSURE_SUCCESS(rv, rv); if (!hasFlags) { nsXPIDLString message; NS_ConvertASCIItoUTF16 ucsTargetScheme(targetScheme); const PRUnichar* formatStrings[] = { ucsTargetScheme.get() }; rv = sStrBundle-> FormatStringFromName(NS_LITERAL_STRING("ProtocolFlagError").get(), formatStrings, ArrayLength(formatStrings), getter_Copies(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(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 PRUnichar *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 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 target; rv = NS_NewURI(getter_AddRefs(target), aTargetURIStr, nullptr, nullptr, sIOService); NS_ENSURE_SUCCESS(rv, rv); rv = CheckLoadURIWithPrincipal(aPrincipal, target, aFlags); 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 = do_GetService(NS_URIFIXUP_CONTRACTID); if (!fixup) { return rv; } uint32_t flags[] = { nsIURIFixup::FIXUP_FLAG_NONE, 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], getter_AddRefs(target)); NS_ENSURE_SUCCESS(rv, rv); rv = CheckLoadURIWithPrincipal(aPrincipal, target, aFlags); NS_ENSURE_SUCCESS(rv, rv); } return rv; } NS_IMETHODIMP nsScriptSecurityManager::CheckFunctionAccess(JSContext *aCx, void *aFunObj, void *aTargetObj) { // This check is called for event handlers nsresult rv; JS::Rooted rootedFunObj(aCx, static_cast(aFunObj)); nsIPrincipal* subject = GetFunctionObjectPrincipal(aCx, rootedFunObj, &rv); // If subject is null, get a principal from the function object's scope. if (NS_SUCCEEDED(rv) && !subject) { #ifdef DEBUG { JS_ASSERT(JS_ObjectIsFunction(aCx, rootedFunObj)); JS::Rooted fun(aCx, JS_GetObjectFunction(rootedFunObj)); JSScript *script = JS_GetFunctionScript(aCx, fun); NS_ASSERTION(!script, "Null principal for non-native function!"); } #endif subject = doGetObjectPrincipal(rootedFunObj); } if (!subject) return NS_ERROR_FAILURE; if (subject == mSystemPrincipal) // This is the system principal: just allow access return NS_OK; // Check if the principal the function was compiled under is // allowed to execute scripts. bool result; rv = CanExecuteScripts(aCx, subject, true, &result); if (NS_FAILED(rv)) return rv; if (!result) return NS_ERROR_DOM_SECURITY_ERR; if (!aTargetObj) { // We're done here return NS_OK; } /* ** Get origin of subject and object and compare. */ JSObject* obj = (JSObject*)aTargetObj; nsIPrincipal* object = doGetObjectPrincipal(obj); if (!object) return NS_ERROR_FAILURE; bool subsumes; rv = subject->Subsumes(object, &subsumes); if (NS_SUCCEEDED(rv) && !subsumes) { rv = NS_ERROR_DOM_PROP_ACCESS_DENIED; } return rv; } NS_IMETHODIMP nsScriptSecurityManager::CanExecuteScripts(JSContext* cx, nsIPrincipal *aPrincipal, bool *result) { return CanExecuteScripts(cx, aPrincipal, false, result); } nsresult nsScriptSecurityManager::CanExecuteScripts(JSContext* cx, nsIPrincipal *aPrincipal, bool aAllowIfNoScriptContext, bool *result) { *result = false; if (aPrincipal == mSystemPrincipal) { // Even if JavaScript is disabled, we must still execute system scripts *result = true; return NS_OK; } // Same thing for nsExpandedPrincipal, which is pseudo-privileged. nsCOMPtr ep = do_QueryInterface(aPrincipal); if (ep) { *result = true; return NS_OK; } //-- See if the current window allows JS execution nsIScriptContext *scriptContext = GetScriptContext(cx); if (!scriptContext) { if (aAllowIfNoScriptContext) { *result = true; return NS_OK; } return NS_ERROR_FAILURE; } if (!scriptContext->GetScriptsEnabled()) { // No scripting on this context, folks *result = false; return NS_OK; } nsIScriptGlobalObject *sgo = scriptContext->GetGlobalObject(); if (!sgo) { return NS_ERROR_FAILURE; } // window can be null here if we're running with a non-DOM window // as the script global (i.e. a XUL prototype document). nsCOMPtr window = do_QueryInterface(sgo); nsCOMPtr docshell; nsresult rv; if (window) { docshell = window->GetDocShell(); } if (docshell) { rv = docshell->GetCanExecuteScripts(result); if (NS_FAILED(rv)) return rv; if (!*result) return NS_OK; } // OK, the docshell doesn't have script execution explicitly disabled. // Check whether our URI is an "about:" URI that allows scripts. If it is, // we need to allow JS to run. In this case, don't apply the JS enabled // pref or policies. On failures, just press on and don't do this special // case. nsCOMPtr principalURI; aPrincipal->GetURI(getter_AddRefs(principalURI)); if (!principalURI) { // Broken principal of some sort. Disallow. *result = false; return NS_ERROR_UNEXPECTED; } bool isAbout; rv = principalURI->SchemeIs("about", &isAbout); if (NS_SUCCEEDED(rv) && isAbout) { nsCOMPtr module; rv = NS_GetAboutModule(principalURI, getter_AddRefs(module)); if (NS_SUCCEEDED(rv)) { uint32_t flags; rv = module->GetURIFlags(principalURI, &flags); if (NS_SUCCEEDED(rv) && (flags & nsIAboutModule::ALLOW_SCRIPT)) { *result = true; return NS_OK; } } } *result = mIsJavaScriptEnabled; if (!*result) return NS_OK; // Do not run scripts //-- Check for a per-site policy static const char jsPrefGroupName[] = "javascript"; ClassInfoData nameData(nullptr, jsPrefGroupName); SecurityLevel secLevel; rv = LookupPolicy(aPrincipal, nameData, sEnabledID, nsIXPCSecurityManager::ACCESS_GET_PROPERTY, nullptr, &secLevel); if (NS_FAILED(rv) || secLevel.level == SCRIPT_SECURITY_NO_ACCESS) { *result = false; return rv; } //-- Nobody vetoed, so allow the JS to run. *result = true; return NS_OK; } ///////////////// Principals /////////////////////// NS_IMETHODIMP nsScriptSecurityManager::GetSubjectPrincipal(nsIPrincipal **aSubjectPrincipal) { nsresult rv; *aSubjectPrincipal = doGetSubjectPrincipal(&rv); if (NS_SUCCEEDED(rv)) NS_IF_ADDREF(*aSubjectPrincipal); return rv; } nsIPrincipal* nsScriptSecurityManager::doGetSubjectPrincipal(nsresult* rv) { NS_PRECONDITION(rv, "Null out param"); JSContext *cx = GetCurrentJSContext(); if (!cx) { *rv = NS_OK; return nullptr; } return GetSubjectPrincipal(cx, rv); } NS_IMETHODIMP nsScriptSecurityManager::GetSystemPrincipal(nsIPrincipal **result) { NS_ADDREF(*result = mSystemPrincipal); return NS_OK; } NS_IMETHODIMP nsScriptSecurityManager::SubjectPrincipalIsSystem(bool* aIsSystem) { NS_ENSURE_ARG_POINTER(aIsSystem); *aIsSystem = false; if (!mSystemPrincipal) return NS_OK; nsCOMPtr subject; nsresult rv = GetSubjectPrincipal(getter_AddRefs(subject)); if (NS_FAILED(rv)) return rv; if(!subject) { // No subject principal means no JS is running; // this is the equivalent of system principal code *aIsSystem = true; return NS_OK; } return mSystemPrincipal->Equals(subject, aIsSystem); } nsresult nsScriptSecurityManager::CreateCodebasePrincipal(nsIURI* aURI, uint32_t aAppId, bool aInMozBrowser, nsIPrincipal **result) { // I _think_ it's safe to not create null principals here based on aURI. // At least all the callers would do the right thing in those cases, as far // as I can tell. --bz nsCOMPtr uriPrinc = do_QueryInterface(aURI); if (uriPrinc) { nsCOMPtr principal; uriPrinc->GetPrincipal(getter_AddRefs(principal)); if (!principal || principal == mSystemPrincipal) { return CallCreateInstance(NS_NULLPRINCIPAL_CONTRACTID, result); } principal.forget(result); return NS_OK; } nsRefPtr codebase = new nsPrincipal(); if (!codebase) return NS_ERROR_OUT_OF_MEMORY; nsresult rv = codebase->Init(aURI, aAppId, aInMozBrowser); if (NS_FAILED(rv)) return rv; NS_ADDREF(*result = codebase); return NS_OK; } NS_IMETHODIMP nsScriptSecurityManager::GetSimpleCodebasePrincipal(nsIURI* aURI, nsIPrincipal** aPrincipal) { return GetCodebasePrincipalInternal(aURI, nsIScriptSecurityManager::UNKNOWN_APP_ID, false, aPrincipal); } NS_IMETHODIMP nsScriptSecurityManager::GetNoAppCodebasePrincipal(nsIURI* aURI, nsIPrincipal** aPrincipal) { return GetCodebasePrincipalInternal(aURI, nsIScriptSecurityManager::NO_APP_ID, false, aPrincipal); } NS_IMETHODIMP nsScriptSecurityManager::GetCodebasePrincipal(nsIURI* aURI, nsIPrincipal** aPrincipal) { return GetNoAppCodebasePrincipal(aURI, aPrincipal); } NS_IMETHODIMP nsScriptSecurityManager::GetAppCodebasePrincipal(nsIURI* aURI, uint32_t aAppId, bool aInMozBrowser, nsIPrincipal** aPrincipal) { NS_ENSURE_TRUE(aAppId != nsIScriptSecurityManager::UNKNOWN_APP_ID, NS_ERROR_INVALID_ARG); return GetCodebasePrincipalInternal(aURI, aAppId, aInMozBrowser, aPrincipal); } NS_IMETHODIMP nsScriptSecurityManager::GetDocShellCodebasePrincipal(nsIURI* aURI, nsIDocShell* aDocShell, nsIPrincipal** aPrincipal) { return GetCodebasePrincipalInternal(aURI, aDocShell->GetAppId(), aDocShell->GetIsInBrowserElement(), aPrincipal); } nsresult nsScriptSecurityManager::GetCodebasePrincipalInternal(nsIURI *aURI, uint32_t aAppId, bool aInMozBrowser, nsIPrincipal **result) { NS_ENSURE_ARG(aURI); bool inheritsPrincipal; nsresult rv = NS_URIChainHasFlags(aURI, nsIProtocolHandler::URI_INHERITS_SECURITY_CONTEXT, &inheritsPrincipal); if (NS_FAILED(rv) || inheritsPrincipal) { return CallCreateInstance(NS_NULLPRINCIPAL_CONTRACTID, result); } nsCOMPtr principal; rv = CreateCodebasePrincipal(aURI, aAppId, aInMozBrowser, getter_AddRefs(principal)); NS_ENSURE_SUCCESS(rv, rv); NS_IF_ADDREF(*result = principal); return NS_OK; } // static nsIPrincipal* nsScriptSecurityManager::GetScriptPrincipal(JSScript *script, nsresult* rv) { NS_PRECONDITION(rv, "Null out param"); *rv = NS_OK; if (!script) { return nullptr; } JSPrincipals *jsp = JS_GetScriptPrincipals(script); if (!jsp) { *rv = NS_ERROR_FAILURE; NS_ERROR("Script compiled without principals!"); return nullptr; } return nsJSPrincipals::get(jsp); } // static nsIPrincipal* nsScriptSecurityManager::GetFunctionObjectPrincipal(JSContext *cx, JS::Handle obj, nsresult *rv) { NS_PRECONDITION(rv, "Null out param"); *rv = NS_OK; if (!JS_ObjectIsFunction(cx, obj)) { // Protect against pseudo-functions (like SJOWs). nsIPrincipal *result = doGetObjectPrincipal(obj); if (!result) *rv = NS_ERROR_FAILURE; return result; } JS::Rooted fun(cx, JS_GetObjectFunction(obj)); JSScript *script = JS_GetFunctionScript(cx, fun); if (!script) { // A native function: skip it in order to find its scripted caller. return nullptr; } if (!js::IsOriginalScriptFunction(fun)) { // Here, obj is a cloned function object. In this case, the // clone's prototype may have been precompiled from brutally // shared chrome, or else it is a lambda or nested function. // The general case here is a function compiled against a // different scope than the one it is parented by at runtime, // hence the creation of a clone to carry the correct scope // chain linkage. // // Since principals follow scope, we must get the object // principal from the clone's scope chain. There are no // reliable principals compiled into the function itself. nsIPrincipal *result = doGetObjectPrincipal(obj); if (!result) *rv = NS_ERROR_FAILURE; return result; } return GetScriptPrincipal(script, rv); } nsIPrincipal* nsScriptSecurityManager::GetSubjectPrincipal(JSContext *cx, nsresult* rv) { *rv = NS_OK; JSCompartment *compartment = js::GetContextCompartment(cx); // The context should always be in a compartment, either one it has entered // or the one associated with its global. MOZ_ASSERT(!!compartment); JSPrincipals *principals = JS_GetCompartmentPrincipals(compartment); return nsJSPrincipals::get(principals); } NS_IMETHODIMP nsScriptSecurityManager::GetObjectPrincipal(JSContext *aCx, JSObject *aObj, nsIPrincipal **result) { *result = doGetObjectPrincipal(aObj); if (!*result) return NS_ERROR_FAILURE; NS_ADDREF(*result); return NS_OK; } // static nsIPrincipal* nsScriptSecurityManager::doGetObjectPrincipal(JSObject *aObj) { JSCompartment *compartment = js::GetObjectCompartment(aObj); JSPrincipals *principals = JS_GetCompartmentPrincipals(compartment); nsIPrincipal *principal = nsJSPrincipals::get(principals); // We leave the old code in for a little while to make sure that pulling // object principals directly off the compartment always gives an equivalent // result (from a security perspective). #ifdef DEBUG nsIPrincipal *old = old_doGetObjectPrincipal(aObj); MOZ_ASSERT(NS_SUCCEEDED(CheckSameOriginPrincipal(principal, old))); #endif return principal; } #ifdef DEBUG // static nsIPrincipal* nsScriptSecurityManager::old_doGetObjectPrincipal(JSObject *aObj, bool aAllowShortCircuit) { NS_ASSERTION(aObj, "Bad call to doGetObjectPrincipal()!"); nsIPrincipal* result = nullptr; JSObject* origObj = aObj; js::Class *jsClass = js::GetObjectClass(aObj); // A common case seen in this code is that we enter this function // with aObj being a Function object, whose parent is a Call // object. Neither of those have object principals, so we can skip // those objects here before we enter the below loop. That way we // avoid wasting time checking properties of their classes etc in // the loop. if (jsClass == &js::FunctionClass) { aObj = js::GetObjectParent(aObj); if (!aObj) return nullptr; jsClass = js::GetObjectClass(aObj); if (jsClass == &js::CallClass) { aObj = js::GetObjectParentMaybeScope(aObj); if (!aObj) return nullptr; jsClass = js::GetObjectClass(aObj); } } do { // Note: jsClass is set before this loop, and also at the // *end* of this loop. if (IS_WRAPPER_CLASS(jsClass)) { result = sXPConnect->GetPrincipal(aObj, aAllowShortCircuit); if (result) { break; } } else { nsISupports *priv; if (!(~jsClass->flags & (JSCLASS_HAS_PRIVATE | JSCLASS_PRIVATE_IS_NSISUPPORTS))) { priv = (nsISupports *) js::GetObjectPrivate(aObj); } else if (!UnwrapDOMObjectToISupports(aObj, priv)) { priv = nullptr; } if (aAllowShortCircuit) { nsCOMPtr xpcWrapper = do_QueryInterface(priv); NS_ASSERTION(!xpcWrapper || !strcmp(jsClass->name, "XPCNativeWrapper"), "Uh, an nsIXPConnectWrappedNative with the " "wrong JSClass or getObjectOps hooks!"); } nsCOMPtr objPrin = do_QueryInterface(priv); if (objPrin) { result = objPrin->GetPrincipal(); if (result) { break; } } } aObj = js::GetObjectParentMaybeScope(aObj); if (!aObj) break; jsClass = js::GetObjectClass(aObj); } while (1); if (aAllowShortCircuit) { nsIPrincipal *principal = old_doGetObjectPrincipal(origObj, false); // Because of inner window reuse, we can have objects with one principal // living in a scope with a different (but same-origin) principal. So // just check same-origin here. NS_ASSERTION(NS_SUCCEEDED(CheckSameOriginPrincipal(result, principal)), "Principal mismatch. Not good"); } return result; } #endif /* DEBUG */ //////////////////////////////////////////////// // Methods implementing nsIXPCSecurityManager // //////////////////////////////////////////////// NS_IMETHODIMP nsScriptSecurityManager::CanCreateWrapper(JSContext *cx, const nsIID &aIID, nsISupports *aObj, nsIClassInfo *aClassInfo, void **aPolicy) { // XXX Special case for nsIXPCException ? ClassInfoData objClassInfo = ClassInfoData(aClassInfo, nullptr); if (objClassInfo.IsDOMClass()) { return NS_OK; } //--See if the object advertises a non-default level of access // using nsISecurityCheckedComponent nsCOMPtr checkedComponent = do_QueryInterface(aObj); nsXPIDLCString objectSecurityLevel; if (checkedComponent) checkedComponent->CanCreateWrapper((nsIID *)&aIID, getter_Copies(objectSecurityLevel)); nsresult rv = CheckXPCPermissions(cx, aObj, nullptr, nullptr, objectSecurityLevel); if (NS_FAILED(rv)) { //-- Access denied, report an error NS_ConvertUTF8toUTF16 strName("CreateWrapperDenied"); nsAutoCString origin; nsresult rv2; nsIPrincipal* subjectPrincipal = doGetSubjectPrincipal(&rv2); if (NS_SUCCEEDED(rv2) && subjectPrincipal) { GetPrincipalDomainOrigin(subjectPrincipal, origin); } NS_ConvertUTF8toUTF16 originUnicode(origin); NS_ConvertUTF8toUTF16 className(objClassInfo.GetName()); const PRUnichar* formatStrings[] = { className.get(), originUnicode.get() }; uint32_t length = ArrayLength(formatStrings); if (originUnicode.IsEmpty()) { --length; } else { strName.AppendLiteral("ForOrigin"); } nsXPIDLString errorMsg; // We need to keep our existing failure rv and not override it // with a likely success code from the following string bundle // call in order to throw the correct security exception later. rv2 = sStrBundle->FormatStringFromName(strName.get(), formatStrings, length, getter_Copies(errorMsg)); NS_ENSURE_SUCCESS(rv2, rv2); SetPendingException(cx, errorMsg.get()); } return rv; } NS_IMETHODIMP nsScriptSecurityManager::CanCreateInstance(JSContext *cx, const nsCID &aCID) { nsresult rv = CheckXPCPermissions(nullptr, nullptr, nullptr, nullptr, nullptr); if (NS_FAILED(rv)) { //-- 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); SetPendingException(cx, errorMsg.get()); } return rv; } NS_IMETHODIMP nsScriptSecurityManager::CanGetService(JSContext *cx, const nsCID &aCID) { nsresult rv = CheckXPCPermissions(nullptr, nullptr, nullptr, nullptr, nullptr); if (NS_FAILED(rv)) { //-- Access denied, report an error nsAutoCString errorMsg("Permission denied to get service. CID="); char cidStr[NSID_LENGTH]; aCID.ToProvidedString(cidStr); errorMsg.Append(cidStr); SetPendingException(cx, errorMsg.get()); } return rv; } NS_IMETHODIMP nsScriptSecurityManager::CanAccess(uint32_t aAction, nsAXPCNativeCallContext* aCallContext, JSContext* cx, JSObject* aJSObject, nsISupports* aObj, nsIClassInfo* aClassInfo, jsid aPropertyName, void** aPolicy) { return CheckPropertyAccessImpl(aAction, aCallContext, cx, aJSObject, aObj, aClassInfo, nullptr, aPropertyName, aPolicy); } nsresult nsScriptSecurityManager::CheckXPCPermissions(JSContext* cx, nsISupports* aObj, JSObject* aJSObject, nsIPrincipal* aSubjectPrincipal, const char* aObjectSecurityLevel) { // Check if the subject is privileged. if (SubjectIsPrivileged()) return NS_OK; //-- If the object implements nsISecurityCheckedComponent, it has a non-default policy. if (aObjectSecurityLevel) { if (PL_strcasecmp(aObjectSecurityLevel, "allAccess") == 0) return NS_OK; if (cx && PL_strcasecmp(aObjectSecurityLevel, "sameOrigin") == 0) { nsresult rv; if (!aJSObject) { nsCOMPtr xpcwrappedjs = do_QueryInterface(aObj); if (xpcwrappedjs) { rv = xpcwrappedjs->GetJSObject(&aJSObject); NS_ENSURE_SUCCESS(rv, rv); } } if (!aSubjectPrincipal) { // No subject principal passed in. Compute it. aSubjectPrincipal = GetSubjectPrincipal(cx, &rv); NS_ENSURE_SUCCESS(rv, rv); } if (aSubjectPrincipal && aJSObject) { nsIPrincipal* objectPrincipal = doGetObjectPrincipal(aJSObject); // Only do anything if we have both a subject and object // principal. if (objectPrincipal) { bool subsumes; rv = aSubjectPrincipal->Subsumes(objectPrincipal, &subsumes); NS_ENSURE_SUCCESS(rv, rv); if (subsumes) return NS_OK; } } } else if (PL_strcasecmp(aObjectSecurityLevel, "noAccess") != 0) { if (SubjectIsPrivileged()) return NS_OK; } } //-- Access tests failed return NS_ERROR_DOM_XPCONNECT_ACCESS_DENIED; } ///////////////////////////////////////////// // Method implementing nsIChannelEventSink // ///////////////////////////////////////////// NS_IMETHODIMP nsScriptSecurityManager::AsyncOnChannelRedirect(nsIChannel* oldChannel, nsIChannel* newChannel, uint32_t redirFlags, nsIAsyncVerifyRedirectCallback *cb) { nsCOMPtr oldPrincipal; GetChannelPrincipal(oldChannel, getter_AddRefs(oldPrincipal)); nsCOMPtr newURI; newChannel->GetURI(getter_AddRefs(newURI)); nsCOMPtr newOriginalURI; newChannel->GetOriginalURI(getter_AddRefs(newOriginalURI)); NS_ENSURE_STATE(oldPrincipal && newURI && newOriginalURI); const uint32_t flags = nsIScriptSecurityManager::LOAD_IS_AUTOMATIC_DOCUMENT_REPLACEMENT | nsIScriptSecurityManager::DISALLOW_SCRIPT; nsresult rv = CheckLoadURIWithPrincipal(oldPrincipal, newURI, flags); if (NS_SUCCEEDED(rv) && newOriginalURI != newURI) { rv = CheckLoadURIWithPrincipal(oldPrincipal, newOriginalURI, flags); } if (NS_FAILED(rv)) return rv; cb->OnRedirectVerifyCallback(NS_OK); return NS_OK; } ///////////////////////////////////// // Method implementing nsIObserver // ///////////////////////////////////// const char sJSEnabledPrefName[] = "javascript.enabled"; const char sFileOriginPolicyPrefName[] = "security.fileuri.strict_origin_policy"; static const char sPolicyPrefix[] = "capability.policy."; static const char* kObservedPrefs[] = { sJSEnabledPrefName, sFileOriginPolicyPrefName, sPolicyPrefix, nullptr }; NS_IMETHODIMP nsScriptSecurityManager::Observe(nsISupports* aObject, const char* aTopic, const PRUnichar* aMessage) { nsresult rv = NS_OK; NS_ConvertUTF16toUTF8 messageStr(aMessage); const char *message = messageStr.get(); static const char jsPrefix[] = "javascript."; static const char securityPrefix[] = "security."; if ((PL_strncmp(message, jsPrefix, sizeof(jsPrefix)-1) == 0) || (PL_strncmp(message, securityPrefix, sizeof(securityPrefix)-1) == 0) ) { ScriptSecurityPrefChanged(); } else if (PL_strncmp(message, sPolicyPrefix, sizeof(sPolicyPrefix)-1) == 0) { // This will force re-initialization of the pref table mPolicyPrefsChanged = true; } return rv; } ///////////////////////////////////////////// // Constructor, Destructor, Initialization // ///////////////////////////////////////////// nsScriptSecurityManager::nsScriptSecurityManager(void) : mOriginToPolicyMap(nullptr), mDefaultPolicy(nullptr), mCapabilities(nullptr), mPrefInitialized(false), mIsJavaScriptEnabled(false), mPolicyPrefsChanged(true) { MOZ_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() { nsXPConnect* xpconnect = nsXPConnect::GetXPConnect(); if (!xpconnect) return NS_ERROR_FAILURE; NS_ADDREF(sXPConnect = xpconnect); JSContext* cx = GetSafeJSContext(); if (!cx) return NS_ERROR_FAILURE; // this can happen of xpt loading fails ::JS_BeginRequest(cx); if (sEnabledID == JSID_VOID) sEnabledID = INTERNED_STRING_TO_JSID(cx, ::JS_InternString(cx, "enabled")); ::JS_EndRequest(cx); InitPrefs(); nsresult rv = CallGetService(NS_IOSERVICE_CONTRACTID, &sIOService); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr 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 nsRefPtr system = new nsSystemPrincipal(); NS_ENSURE_TRUE(system, NS_ERROR_OUT_OF_MEMORY); mSystemPrincipal = system; //-- Register security check callback in the JS engine // Currently this is used to control access to function.caller nsCOMPtr runtimeService = do_QueryInterface(sXPConnect, &rv); NS_ENSURE_SUCCESS(rv, rv); rv = runtimeService->GetRuntime(&sRuntime); NS_ENSURE_SUCCESS(rv, rv); static const JSSecurityCallbacks securityCallbacks = { CheckObjectAccess, ContentSecurityPolicyPermitsJSAction }; MOZ_ASSERT(!JS_GetSecurityCallbacks(sRuntime)); JS_SetSecurityCallbacks(sRuntime, &securityCallbacks); JS_InitDestroyPrincipalsCallback(sRuntime, nsJSPrincipals::Destroy); JS_SetTrustedPrincipals(sRuntime, system); return NS_OK; } static StaticRefPtr gScriptSecMan; jsid nsScriptSecurityManager::sEnabledID = JSID_VOID; nsScriptSecurityManager::~nsScriptSecurityManager(void) { Preferences::RemoveObservers(this, kObservedPrefs); delete mOriginToPolicyMap; if(mDefaultPolicy) mDefaultPolicy->Drop(); delete mCapabilities; } void nsScriptSecurityManager::Shutdown() { if (sRuntime) { JS_SetSecurityCallbacks(sRuntime, nullptr); JS_SetTrustedPrincipals(sRuntime, nullptr); sRuntime = nullptr; } sEnabledID = JSID_VOID; NS_IF_RELEASE(sIOService); NS_IF_RELEASE(sXPConnect); NS_IF_RELEASE(sStrBundle); } nsScriptSecurityManager * nsScriptSecurityManager::GetScriptSecurityManager() { if (!gScriptSecMan) { nsRefPtr ssManager = new nsScriptSecurityManager(); nsresult rv; rv = ssManager->Init(); NS_ASSERTION(NS_SUCCEEDED(rv), "Failed to initialize nsScriptSecurityManager"); if (NS_FAILED(rv)) { return nullptr; } rv = sXPConnect->SetDefaultSecurityManager(ssManager, nsIXPCSecurityManager::HOOK_ALL); if (NS_FAILED(rv)) { NS_WARNING("Failed to install xpconnect security manager!"); return nullptr; } ClearOnShutdown(&gScriptSecMan); gScriptSecMan = ssManager; } return gScriptSecMan; } // 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(sysprin); } nsresult nsScriptSecurityManager::InitPolicies() { // Clear any policies cached on XPConnect wrappers NS_ENSURE_STATE(sXPConnect); nsresult rv = sXPConnect->ClearAllWrappedNativeSecurityPolicies(); if (NS_FAILED(rv)) return rv; //-- Clear mOriginToPolicyMap: delete mapped DomainEntry items, //-- whose dtor decrements refcount of stored DomainPolicy object delete mOriginToPolicyMap; //-- Marks all the survivor DomainPolicy objects (those cached //-- by nsPrincipal objects) as invalid: they will be released //-- on first nsPrincipal::GetSecurityPolicy() attempt. DomainPolicy::InvalidateAll(); //-- Release old default policy if(mDefaultPolicy) { mDefaultPolicy->Drop(); mDefaultPolicy = nullptr; } //-- Initialize a new mOriginToPolicyMap mOriginToPolicyMap = new nsObjectHashtable(nullptr, nullptr, DeleteDomainEntry, nullptr); if (!mOriginToPolicyMap) return NS_ERROR_OUT_OF_MEMORY; //-- Create, refcount and initialize a new default policy mDefaultPolicy = new DomainPolicy(); if (!mDefaultPolicy) return NS_ERROR_OUT_OF_MEMORY; mDefaultPolicy->Hold(); if (!mDefaultPolicy->Init()) return NS_ERROR_UNEXPECTED; //-- Initialize the table of security levels if (!mCapabilities) { mCapabilities = new nsObjectHashtable(nullptr, nullptr, DeleteCapability, nullptr); if (!mCapabilities) return NS_ERROR_OUT_OF_MEMORY; } // Get a JS context - we need it to create internalized strings later. AutoSafeJSContext cx; rv = InitDomainPolicy(cx, "default", mDefaultPolicy); NS_ENSURE_SUCCESS(rv, rv); nsAdoptingCString policyNames = Preferences::GetCString("capability.policy.policynames"); nsAdoptingCString defaultPolicyNames = Preferences::GetCString("capability.policy.default_policynames"); policyNames += NS_LITERAL_CSTRING(" ") + defaultPolicyNames; //-- Initialize domain policies char* policyCurrent = policyNames.BeginWriting(); bool morePolicies = true; while (morePolicies) { while(*policyCurrent == ' ' || *policyCurrent == ',') policyCurrent++; if (*policyCurrent == '\0') break; char* nameBegin = policyCurrent; while(*policyCurrent != '\0' && *policyCurrent != ' ' && *policyCurrent != ',') policyCurrent++; morePolicies = (*policyCurrent != '\0'); *policyCurrent = '\0'; policyCurrent++; nsAutoCString sitesPrefName( NS_LITERAL_CSTRING(sPolicyPrefix) + nsDependentCString(nameBegin) + NS_LITERAL_CSTRING(".sites")); nsAdoptingCString domainList = Preferences::GetCString(sitesPrefName.get()); if (!domainList) { continue; } DomainPolicy* domainPolicy = new DomainPolicy(); if (!domainPolicy) return NS_ERROR_OUT_OF_MEMORY; if (!domainPolicy->Init()) { delete domainPolicy; return NS_ERROR_UNEXPECTED; } domainPolicy->Hold(); //-- Parse list of sites and create an entry in mOriginToPolicyMap for each char* domainStart = domainList.BeginWriting(); char* domainCurrent = domainStart; char* lastDot = nullptr; char* nextToLastDot = nullptr; bool moreDomains = true; while (moreDomains) { if (*domainCurrent == ' ' || *domainCurrent == '\0') { moreDomains = (*domainCurrent != '\0'); *domainCurrent = '\0'; nsCStringKey key(nextToLastDot ? nextToLastDot+1 : domainStart); DomainEntry *newEntry = new DomainEntry(domainStart, domainPolicy); if (!newEntry) { domainPolicy->Drop(); return NS_ERROR_OUT_OF_MEMORY; } DomainEntry *existingEntry = (DomainEntry *) mOriginToPolicyMap->Get(&key); if (!existingEntry) mOriginToPolicyMap->Put(&key, newEntry); else { if (existingEntry->Matches(domainStart)) { newEntry->mNext = existingEntry; mOriginToPolicyMap->Put(&key, newEntry); } else { while (existingEntry->mNext) { if (existingEntry->mNext->Matches(domainStart)) { newEntry->mNext = existingEntry->mNext; existingEntry->mNext = newEntry; break; } existingEntry = existingEntry->mNext; } if (!existingEntry->mNext) existingEntry->mNext = newEntry; } } domainStart = domainCurrent + 1; lastDot = nextToLastDot = nullptr; } else if (*domainCurrent == '.') { nextToLastDot = lastDot; lastDot = domainCurrent; } domainCurrent++; } rv = InitDomainPolicy(cx, nameBegin, domainPolicy); domainPolicy->Drop(); if (NS_FAILED(rv)) return rv; } // Reset the "dirty" flag mPolicyPrefsChanged = false; return NS_OK; } nsresult nsScriptSecurityManager::InitDomainPolicy(JSContext* cx, const char* aPolicyName, DomainPolicy* aDomainPolicy) { nsresult rv; nsAutoCString policyPrefix(NS_LITERAL_CSTRING(sPolicyPrefix) + nsDependentCString(aPolicyName) + NS_LITERAL_CSTRING(".")); uint32_t prefixLength = policyPrefix.Length() - 1; // subtract the '.' uint32_t prefCount; char** prefNames; nsIPrefBranch* branch = Preferences::GetRootBranch(); NS_ASSERTION(branch, "failed to get the root pref branch"); rv = branch->GetChildList(policyPrefix.get(), &prefCount, &prefNames); if (NS_FAILED(rv)) return rv; if (prefCount == 0) return NS_OK; //-- Populate the policy uint32_t currentPref = 0; for (; currentPref < prefCount; currentPref++) { // Get the class name const char* start = prefNames[currentPref] + prefixLength + 1; char* end = PL_strchr(start, '.'); if (!end) // malformed pref, bail on this one continue; static const char sitesStr[] = "sites"; // We dealt with "sites" in InitPolicies(), so no need to do // that again... if (PL_strncmp(start, sitesStr, sizeof(sitesStr)-1) == 0) continue; // Get the pref value nsAdoptingCString prefValue = Preferences::GetCString(prefNames[currentPref]); if (!prefValue) { continue; } SecurityLevel secLevel; if (PL_strcasecmp(prefValue, "noAccess") == 0) secLevel.level = SCRIPT_SECURITY_NO_ACCESS; else if (PL_strcasecmp(prefValue, "allAccess") == 0) secLevel.level = SCRIPT_SECURITY_ALL_ACCESS; else if (PL_strcasecmp(prefValue, "sameOrigin") == 0) secLevel.level = SCRIPT_SECURITY_SAME_ORIGIN_ACCESS; else { //-- pref value is the name of a capability nsCStringKey secLevelKey(prefValue); secLevel.capability = reinterpret_cast(mCapabilities->Get(&secLevelKey)); if (!secLevel.capability) { secLevel.capability = NS_strdup(prefValue); if (!secLevel.capability) break; mCapabilities->Put(&secLevelKey, secLevel.capability); } } *end = '\0'; // Find or store this class in the classes table ClassPolicy* cpolicy = static_cast (PL_DHashTableOperate(aDomainPolicy, start, PL_DHASH_ADD)); if (!cpolicy) break; // If this is the wildcard class (class '*'), save it in mWildcardPolicy // (we leave it stored in the hashtable too to take care of the cleanup) if ((*start == '*') && (end == start + 1)) { aDomainPolicy->mWildcardPolicy = cpolicy; // Make sure that cpolicy knows about aDomainPolicy so it can reset // the mWildcardPolicy pointer as needed if it gets moved in the // hashtable. cpolicy->mDomainWeAreWildcardFor = aDomainPolicy; } // Get the property name start = end + 1; end = PL_strchr(start, '.'); if (end) *end = '\0'; JSAutoRequest ar(cx); JSString* propertyKey = ::JS_InternString(cx, start); if (!propertyKey) return NS_ERROR_OUT_OF_MEMORY; // Store this property in the class policy PropertyPolicy* ppolicy = static_cast (PL_DHashTableOperate(cpolicy->mPolicy, propertyKey, PL_DHASH_ADD)); if (!ppolicy) break; if (end) // The pref specifies an access mode { start = end + 1; if (PL_strcasecmp(start, "set") == 0) ppolicy->mSet = secLevel; else ppolicy->mGet = secLevel; } else { if (ppolicy->mGet.level == SCRIPT_SECURITY_UNDEFINED_ACCESS) ppolicy->mGet = secLevel; if (ppolicy->mSet.level == SCRIPT_SECURITY_UNDEFINED_ACCESS) ppolicy->mSet = secLevel; } } NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(prefCount, prefNames); if (currentPref < prefCount) // Loop exited early because of out-of-memory error return NS_ERROR_OUT_OF_MEMORY; return NS_OK; } inline void nsScriptSecurityManager::ScriptSecurityPrefChanged() { // JavaScript defaults to enabled in failure cases. mIsJavaScriptEnabled = true; sStrictFileOriginPolicy = true; nsresult rv; if (!mPrefInitialized) { rv = InitPrefs(); if (NS_FAILED(rv)) return; } mIsJavaScriptEnabled = Preferences::GetBool(sJSEnabledPrefName, mIsJavaScriptEnabled); sStrictFileOriginPolicy = Preferences::GetBool(sFileOriginPolicyPrefName, false); } 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; } namespace mozilla { void GetExtendedOrigin(nsIURI* aURI, uint32_t aAppId, bool aInMozBrowser, nsACString& aExtendedOrigin) { MOZ_ASSERT(aURI); MOZ_ASSERT(aAppId != nsIScriptSecurityManager::UNKNOWN_APP_ID); if (aAppId == nsIScriptSecurityManager::UNKNOWN_APP_ID) { aAppId = nsIScriptSecurityManager::NO_APP_ID; } nsAutoCString origin; nsPrincipal::GetOriginForURI(aURI, getter_Copies(origin)); // Fallback. if (aAppId == nsIScriptSecurityManager::NO_APP_ID && !aInMozBrowser) { aExtendedOrigin.Assign(origin); return; } // aExtendedOrigin = appId + "+" + { 't', 'f' } "+" + origin; aExtendedOrigin.Truncate(); aExtendedOrigin.AppendInt(aAppId); aExtendedOrigin.Append('+'); aExtendedOrigin.Append(aInMozBrowser ? 't' : 'f'); aExtendedOrigin.Append('+'); aExtendedOrigin.Append(origin); return; } } // namespace mozilla NS_IMETHODIMP nsScriptSecurityManager::GetExtendedOrigin(nsIURI* aURI, uint32_t aAppId, bool aInMozBrowser, nsACString& aExtendedOrigin) { MOZ_ASSERT(aAppId != nsIScriptSecurityManager::UNKNOWN_APP_ID); mozilla::GetExtendedOrigin(aURI, aAppId, aInMozBrowser, aExtendedOrigin); return NS_OK; }